@nodetool-ai/telegram 0.8.1

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.
Files changed (59) hide show
  1. package/README.md +49 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/bot-api.d.ts +115 -0
  4. package/dist/bot-api.d.ts.map +1 -0
  5. package/dist/bot-api.js +145 -0
  6. package/dist/bot-api.js.map +1 -0
  7. package/dist/chunk.d.ts +36 -0
  8. package/dist/chunk.d.ts.map +1 -0
  9. package/dist/chunk.js +280 -0
  10. package/dist/chunk.js.map +1 -0
  11. package/dist/cli.d.ts +10 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +62 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/commands.d.ts +83 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +191 -0
  18. package/dist/commands.js.map +1 -0
  19. package/dist/config.d.ts +65 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +139 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/frame-renderer.d.ts +135 -0
  24. package/dist/frame-renderer.d.ts.map +1 -0
  25. package/dist/frame-renderer.js +407 -0
  26. package/dist/frame-renderer.js.map +1 -0
  27. package/dist/identity-client.d.ts +132 -0
  28. package/dist/identity-client.d.ts.map +1 -0
  29. package/dist/identity-client.js +204 -0
  30. package/dist/identity-client.js.map +1 -0
  31. package/dist/index.d.ts +56 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +90 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/markdown-html.d.ts +32 -0
  36. package/dist/markdown-html.d.ts.map +1 -0
  37. package/dist/markdown-html.js +251 -0
  38. package/dist/markdown-html.js.map +1 -0
  39. package/dist/nodetool-client.d.ts +32 -0
  40. package/dist/nodetool-client.d.ts.map +1 -0
  41. package/dist/nodetool-client.js +127 -0
  42. package/dist/nodetool-client.js.map +1 -0
  43. package/dist/register-commands.d.ts +24 -0
  44. package/dist/register-commands.d.ts.map +1 -0
  45. package/dist/register-commands.js +29 -0
  46. package/dist/register-commands.js.map +1 -0
  47. package/dist/strings.d.ts +7 -0
  48. package/dist/strings.d.ts.map +1 -0
  49. package/dist/strings.js +13 -0
  50. package/dist/strings.js.map +1 -0
  51. package/dist/telegram-adapter.d.ts +100 -0
  52. package/dist/telegram-adapter.d.ts.map +1 -0
  53. package/dist/telegram-adapter.js +366 -0
  54. package/dist/telegram-adapter.js.map +1 -0
  55. package/dist/turn-router.d.ts +167 -0
  56. package/dist/turn-router.d.ts.map +1 -0
  57. package/dist/turn-router.js +360 -0
  58. package/dist/turn-router.js.map +1 -0
  59. package/package.json +68 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * IdentityClient — the bridge's half of the identity layer (design §5).
3
+ *
4
+ * The bridge holds exactly one credential: the service token that identifies
5
+ * it to the NodeTool server. It never sees a user password, an OAuth code, or
6
+ * a long-lived user token. To act as a linked user it exchanges the Telegram
7
+ * user id for a short-lived delegated token, and caches that token until
8
+ * shortly before it expires.
9
+ *
10
+ * Every route this module calls is `packages/websocket/src/routes/integrations.ts`.
11
+ * All I/O goes through the injected `fetch`, so the whole flow is testable
12
+ * without a server.
13
+ */
14
+ export declare const TOKEN_EXPIRY_SLACK_MS: number;
15
+ /** The identity layer's provider column for this bridge. */
16
+ export declare const TELEGRAM_PROVIDER = "telegram";
17
+ export interface IdentityClientOptions {
18
+ /** NodeTool server base URL, no trailing slash. */
19
+ readonly apiUrl: string;
20
+ /** `NODETOOL_INTEGRATION_TOKEN` — the bridge's service token. */
21
+ readonly integrationToken: string;
22
+ /** Injected so tests need no network. */
23
+ readonly fetch: typeof fetch;
24
+ /** Identity provider name. Defaults to `telegram`. */
25
+ readonly provider?: string;
26
+ /** Clock, in ms. Defaults to `Date.now`. */
27
+ readonly now?: () => number;
28
+ /** Cache slack before expiry. Defaults to {@link TOKEN_EXPIRY_SLACK_MS}. */
29
+ readonly expirySlackMs?: number;
30
+ }
31
+ /** A delegated token good for acting as the linked NodeTool user. */
32
+ export interface LinkedIdentity {
33
+ readonly unlinked: false;
34
+ /** Bearer token for `/ws`, `/trpc` and asset URLs. */
35
+ readonly token: string;
36
+ /** The NodeTool user the token authenticates as. */
37
+ readonly userId: string;
38
+ /** Absolute expiry, in ms since the epoch. */
39
+ readonly expiresAtMs: number;
40
+ }
41
+ /** Why no token could be minted. Both cases are answered in the chat, not thrown. */
42
+ export type UnlinkedReason =
43
+ /** No `external_identities` row: the user must link first (HTTP 404). */
44
+ "not-linked"
45
+ /** Server runs in local single-user trust mode (HTTP 409, design §9). */
46
+ | "local-mode";
47
+ export interface UnlinkedIdentity {
48
+ readonly unlinked: true;
49
+ readonly reason: UnlinkedReason;
50
+ /** The server's own explanation, for relaying to the user. */
51
+ readonly message: string;
52
+ }
53
+ export type IdentityResolution = LinkedIdentity | UnlinkedIdentity;
54
+ /** A link code the user turns into an `external_identities` row in the browser. */
55
+ export interface LinkStart {
56
+ readonly code: string;
57
+ /** Confirmation page URL to send to the user. */
58
+ readonly url: string;
59
+ /** ISO timestamp the code stops working at, when the server reported one. */
60
+ readonly expiresAt: string | null;
61
+ }
62
+ /**
63
+ * Outcome of completing a `/start <code>` deep link.
64
+ *
65
+ * `unsupported` is the committed server's answer today: `/link/complete`
66
+ * requires a `user_id` the bridge does not know, because the code it mints is
67
+ * bound to an `external_id`, not to a user. See the module note below.
68
+ */
69
+ export type DeepLinkResult = {
70
+ readonly ok: true;
71
+ } | {
72
+ readonly ok: false;
73
+ readonly reason: "expired" | "mismatch" | "unsupported" | "server-error";
74
+ readonly message: string;
75
+ };
76
+ /** A transport or server failure the caller must surface rather than swallow. */
77
+ export declare class IdentityError extends Error {
78
+ /** HTTP status, or null when the request never got a response. */
79
+ readonly status: number | null;
80
+ constructor(message: string, status: number | null, options?: {
81
+ cause?: unknown;
82
+ });
83
+ }
84
+ export declare class IdentityClient {
85
+ private readonly apiUrl;
86
+ private readonly integrationToken;
87
+ private readonly fetchImpl;
88
+ private readonly provider;
89
+ private readonly now;
90
+ private readonly expirySlackMs;
91
+ private readonly cache;
92
+ constructor(options: IdentityClientOptions);
93
+ /**
94
+ * A delegated token for this Telegram user, from cache when one is still
95
+ * comfortably valid.
96
+ *
97
+ * @throws {IdentityError} on transport failure or an unexpected status —
98
+ * "not linked" and "local mode" are results, not errors.
99
+ */
100
+ resolve(telegramUserId: string): Promise<IdentityResolution>;
101
+ /**
102
+ * Forget the cached token for a user. Called when the server rejects it
103
+ * mid-session, so the next turn mints a fresh one instead of retrying a
104
+ * token the server has already refused.
105
+ */
106
+ invalidate(telegramUserId: string): void;
107
+ /** Mint a one-time link code and confirmation URL for the `/link` flow. */
108
+ linkStart(telegramUserId: string): Promise<LinkStart>;
109
+ /**
110
+ * Complete a link the *web* side started, from a `/start <code>` deep link.
111
+ *
112
+ * ROUTE GAP: against the *committed*
113
+ * `packages/websocket/src/routes/integrations.ts` this direction does not
114
+ * exist. `/link/start` binds a code to `(provider, external_id)` and
115
+ * `/link/complete` requires a `user_id` in the body, so only the
116
+ * bot-initiated direction closes — the bridge knows the Telegram id, never
117
+ * the NodeTool user id. The direction needs the route to accept
118
+ * `{external_id, code}` and read the user off a code the web side minted,
119
+ * which is what the M1 T1.4 change to that route adds.
120
+ *
121
+ * So the call is made in exactly the shape that direction takes, and a 400
122
+ * naming the missing `user_id` is reported as `unsupported` — which
123
+ * `/start <code>` handling turns into the ordinary `/link` flow rather than
124
+ * leaving the user holding a dead code.
125
+ */
126
+ completeDeepLink(telegramUserId: string, code: string): Promise<DeepLinkResult>;
127
+ /** Delete the mapping for this Telegram user. Returns whether a row went. */
128
+ unlink(telegramUserId: string): Promise<boolean>;
129
+ private post;
130
+ private request;
131
+ }
132
+ //# sourceMappingURL=identity-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-client.d.ts","sourceRoot":"","sources":["../src/identity-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * IdentityClient — the bridge's half of the identity layer (design §5).
3
+ *
4
+ * The bridge holds exactly one credential: the service token that identifies
5
+ * it to the NodeTool server. It never sees a user password, an OAuth code, or
6
+ * a long-lived user token. To act as a linked user it exchanges the Telegram
7
+ * user id for a short-lived delegated token, and caches that token until
8
+ * shortly before it expires.
9
+ *
10
+ * Every route this module calls is `packages/websocket/src/routes/integrations.ts`.
11
+ * All I/O goes through the injected `fetch`, so the whole flow is testable
12
+ * without a server.
13
+ */
14
+ /** How long before a token's stated expiry it stops being served from cache. */
15
+ import { trimTrailingSlashes } from "./strings.js";
16
+ export const TOKEN_EXPIRY_SLACK_MS = 5 * 60 * 1000;
17
+ /** The identity layer's provider column for this bridge. */
18
+ export const TELEGRAM_PROVIDER = "telegram";
19
+ /** A transport or server failure the caller must surface rather than swallow. */
20
+ export class IdentityError extends Error {
21
+ /** HTTP status, or null when the request never got a response. */
22
+ status;
23
+ constructor(message, status, options) {
24
+ super(message, options);
25
+ this.name = "IdentityError";
26
+ this.status = status;
27
+ }
28
+ }
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function stringField(body, field) {
33
+ if (!isRecord(body)) {
34
+ return null;
35
+ }
36
+ const value = body[field];
37
+ return typeof value === "string" && value.length > 0 ? value : null;
38
+ }
39
+ /** The server's `{error}` string, or a generic line naming the status. */
40
+ function errorMessage(body, status) {
41
+ return stringField(body, "error") ?? `NodeTool server answered ${status}`;
42
+ }
43
+ export class IdentityClient {
44
+ apiUrl;
45
+ integrationToken;
46
+ fetchImpl;
47
+ provider;
48
+ now;
49
+ expirySlackMs;
50
+ cache = new Map();
51
+ constructor(options) {
52
+ this.apiUrl = trimTrailingSlashes(options.apiUrl);
53
+ this.integrationToken = options.integrationToken;
54
+ this.fetchImpl = options.fetch;
55
+ this.provider = options.provider ?? TELEGRAM_PROVIDER;
56
+ this.now = options.now ?? Date.now;
57
+ this.expirySlackMs = options.expirySlackMs ?? TOKEN_EXPIRY_SLACK_MS;
58
+ }
59
+ /**
60
+ * A delegated token for this Telegram user, from cache when one is still
61
+ * comfortably valid.
62
+ *
63
+ * @throws {IdentityError} on transport failure or an unexpected status —
64
+ * "not linked" and "local mode" are results, not errors.
65
+ */
66
+ async resolve(telegramUserId) {
67
+ const cached = this.cache.get(telegramUserId);
68
+ if (cached && cached.expiresAtMs - this.expirySlackMs > this.now()) {
69
+ return {
70
+ unlinked: false,
71
+ token: cached.token,
72
+ userId: cached.userId,
73
+ expiresAtMs: cached.expiresAtMs
74
+ };
75
+ }
76
+ this.cache.delete(telegramUserId);
77
+ const { status, body } = await this.post("token", { external_id: telegramUserId });
78
+ if (status === 404) {
79
+ return { unlinked: true, reason: "not-linked", message: errorMessage(body, status) };
80
+ }
81
+ if (status === 409) {
82
+ return { unlinked: true, reason: "local-mode", message: errorMessage(body, status) };
83
+ }
84
+ if (status !== 200) {
85
+ throw new IdentityError(errorMessage(body, status), status);
86
+ }
87
+ const token = stringField(body, "token");
88
+ const userId = stringField(body, "user_id");
89
+ if (token === null || userId === null) {
90
+ throw new IdentityError("Token response is missing `token` or `user_id`", status);
91
+ }
92
+ const expiresAt = stringField(body, "expires_at");
93
+ const parsed = expiresAt === null ? Number.NaN : Date.parse(expiresAt);
94
+ // A token whose expiry the server did not state is treated as expiring
95
+ // immediately after the slack window, so it is re-minted rather than used
96
+ // forever on a guess.
97
+ const expiresAtMs = Number.isFinite(parsed) ? parsed : this.now() + this.expirySlackMs;
98
+ this.cache.set(telegramUserId, { token, userId, expiresAtMs });
99
+ return { unlinked: false, token, userId, expiresAtMs };
100
+ }
101
+ /**
102
+ * Forget the cached token for a user. Called when the server rejects it
103
+ * mid-session, so the next turn mints a fresh one instead of retrying a
104
+ * token the server has already refused.
105
+ */
106
+ invalidate(telegramUserId) {
107
+ this.cache.delete(telegramUserId);
108
+ }
109
+ /** Mint a one-time link code and confirmation URL for the `/link` flow. */
110
+ async linkStart(telegramUserId) {
111
+ const { status, body } = await this.post("link/start", {
112
+ external_id: telegramUserId
113
+ });
114
+ if (status !== 200) {
115
+ throw new IdentityError(errorMessage(body, status), status);
116
+ }
117
+ const code = stringField(body, "code");
118
+ const url = stringField(body, "url");
119
+ if (code === null || url === null) {
120
+ throw new IdentityError("Link response is missing `code` or `url`", status);
121
+ }
122
+ return { code, url, expiresAt: stringField(body, "expires_at") };
123
+ }
124
+ /**
125
+ * Complete a link the *web* side started, from a `/start <code>` deep link.
126
+ *
127
+ * ROUTE GAP: against the *committed*
128
+ * `packages/websocket/src/routes/integrations.ts` this direction does not
129
+ * exist. `/link/start` binds a code to `(provider, external_id)` and
130
+ * `/link/complete` requires a `user_id` in the body, so only the
131
+ * bot-initiated direction closes — the bridge knows the Telegram id, never
132
+ * the NodeTool user id. The direction needs the route to accept
133
+ * `{external_id, code}` and read the user off a code the web side minted,
134
+ * which is what the M1 T1.4 change to that route adds.
135
+ *
136
+ * So the call is made in exactly the shape that direction takes, and a 400
137
+ * naming the missing `user_id` is reported as `unsupported` — which
138
+ * `/start <code>` handling turns into the ordinary `/link` flow rather than
139
+ * leaving the user holding a dead code.
140
+ */
141
+ async completeDeepLink(telegramUserId, code) {
142
+ const { status, body } = await this.post("link/complete", {
143
+ external_id: telegramUserId,
144
+ code
145
+ });
146
+ if (status === 200) {
147
+ return { ok: true };
148
+ }
149
+ if (status === 410) {
150
+ return { ok: false, reason: "expired", message: errorMessage(body, status) };
151
+ }
152
+ if (status === 400) {
153
+ const message = errorMessage(body, status);
154
+ const reason = message.includes("different account") ? "mismatch" : "unsupported";
155
+ return { ok: false, reason, message };
156
+ }
157
+ return { ok: false, reason: "server-error", message: errorMessage(body, status) };
158
+ }
159
+ /** Delete the mapping for this Telegram user. Returns whether a row went. */
160
+ async unlink(telegramUserId) {
161
+ const { status, body } = await this.request("DELETE", "link", {
162
+ external_id: telegramUserId
163
+ });
164
+ if (status !== 200) {
165
+ throw new IdentityError(errorMessage(body, status), status);
166
+ }
167
+ this.cache.delete(telegramUserId);
168
+ return isRecord(body) && body.unlinked === true;
169
+ }
170
+ post(path, payload) {
171
+ return this.request("POST", path, payload);
172
+ }
173
+ async request(method, path, payload) {
174
+ const url = `${this.apiUrl}/api/integrations/${this.provider}/${path}`;
175
+ let response;
176
+ try {
177
+ response = await this.fetchImpl(url, {
178
+ method,
179
+ headers: {
180
+ "content-type": "application/json",
181
+ authorization: `Bearer ${this.integrationToken}`
182
+ },
183
+ body: JSON.stringify(payload)
184
+ });
185
+ }
186
+ catch (err) {
187
+ throw new IdentityError(`Cannot reach the NodeTool server at ${this.apiUrl}: ${err.message}`, null, { cause: err });
188
+ }
189
+ const text = await response.text();
190
+ let body = null;
191
+ if (text.length > 0) {
192
+ try {
193
+ body = JSON.parse(text);
194
+ }
195
+ catch {
196
+ // A non-JSON body (a proxy's HTML error page) carries no field to
197
+ // read; the status alone is what the caller reports.
198
+ body = null;
199
+ }
200
+ }
201
+ return { status: response.status, body };
202
+ }
203
+ }
204
+ //# sourceMappingURL=identity-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-client.js","sourceRoot":"","sources":["../src/identity-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @nodetool-ai/telegram — the Telegram bridge.
3
+ *
4
+ * A bridge process, not a second agent runtime: it translates Telegram updates
5
+ * into `chat_message` commands on the NodeTool server's `/ws` chat protocol and
6
+ * the resulting frames back into Telegram messages. The agent loop, tools,
7
+ * permissions, thread persistence and cost tracking stay on the server.
8
+ *
9
+ * See `docs/telegram-bot-design.md`.
10
+ */
11
+ export { loadConfig, TelegramConfigError, DEFAULT_API_URL, DEFAULT_CONFIG_FILE, DEFAULT_EDIT_THROTTLE_MS, DEFAULT_MAX_QUEUED_TURNS } from "./config.js";
12
+ export type { TelegramBotConfig, TelegramBotFileConfig, LoadConfigOptions } from "./config.js";
13
+ export { chunkText, splitOnce, DEFAULT_CHUNK_MAX, TELEGRAM_MESSAGE_LIMIT } from "./chunk.js";
14
+ export { markdownToTelegramHtml, escapeHtml } from "./markdown-html.js";
15
+ export type { TelegramHtmlResult } from "./markdown-html.js";
16
+ export { createRendererState, foldFrame, foldFrames, collectAssetRefs } from "./frame-renderer.js";
17
+ export type { RenderOp, RenderTarget, ParseMode, TypingOp, SendOp, EditOp, FinalizeOp, AttachOp, StopNoteOp, AssetAttachment, RenderFrame, FrameEnvelope, FinalMessageFrame, GenerationStoppedFrame, RendererState, RendererOptions, FoldResult } from "./frame-renderer.js";
18
+ export { IdentityClient, IdentityError, TELEGRAM_PROVIDER, TOKEN_EXPIRY_SLACK_MS } from "./identity-client.js";
19
+ export type { IdentityClientOptions, IdentityResolution, LinkedIdentity, UnlinkedIdentity, UnlinkedReason, LinkStart, DeepLinkResult } from "./identity-client.js";
20
+ export { TurnRouter, deriveThreadId, highestThreadIndex, userHash8 } from "./turn-router.js";
21
+ export type { BridgeChatSocket, BridgeClient, BridgeSendOptions, DeliveryContext, ExecuteOps, IdentityResolver, MakeBridgeClient, SubmitInput, SubmitResult, TurnRouterConfig, TurnRouterOptions } from "./turn-router.js";
22
+ export { BotApi, BotApiError, TELEGRAM_API_BASE, TELEGRAM_UPLOAD_LIMIT_BYTES } from "./bot-api.js";
23
+ export type { BotApiOptions, BotCommandSpec, TelegramCallbackQuery, TelegramMessage, TelegramUpdate } from "./bot-api.js";
24
+ export { TelegramAdapter, POLL_TIMEOUT_SECONDS, STOP_CALLBACK_DATA } from "./telegram-adapter.js";
25
+ export type { AdapterConfig, PollStopReason, ResolveAsset, ResolvedAsset, TelegramAdapterOptions } from "./telegram-adapter.js";
26
+ export { BOT_COMMANDS, MESSAGES, PUBLIC_COMMANDS, handleCommand, isAllowedUser, parseCommand } from "./commands.js";
27
+ export type { CommandDeps, CommandIdentity, CommandInput, CommandOutcome, CommandRouter } from "./commands.js";
28
+ export { registerCommands } from "./register-commands.js";
29
+ export type { RegisterCommandsOptions } from "./register-commands.js";
30
+ export { assetIdOf, createAssetResolver, createBridgeClientFactory, wrapChatSocket } from "./nodetool-client.js";
31
+ import type { TelegramBotConfig } from "./config.js";
32
+ import { type PollStopReason } from "./telegram-adapter.js";
33
+ /** A running bridge. Returned by {@link startTelegramBot}. */
34
+ export interface TelegramBotHandle {
35
+ /** Stop polling and close every open socket. */
36
+ readonly stop: () => Promise<void>;
37
+ /** Resolves when the poll loop ends, with why it ended. */
38
+ readonly finished: Promise<PollStopReason>;
39
+ }
40
+ export interface StartTelegramBotOptions {
41
+ /** Injected for tests; defaults to the global `fetch`. */
42
+ readonly fetch?: typeof fetch;
43
+ /** Bot API base URL, for tests. */
44
+ readonly botApiBaseUrl?: string;
45
+ }
46
+ /**
47
+ * Start the bridge against a running NodeTool server.
48
+ *
49
+ * Long polling only: webhook mode is M3 (design §3), and config already
50
+ * validates the URL/secret pair, so a webhook-configured process is refused
51
+ * here rather than silently polling.
52
+ */
53
+ export declare function startTelegramBot(config: TelegramBotConfig, options?: StartTelegramBotOptions): TelegramBotHandle;
54
+ /** Stop a running bridge. */
55
+ export declare function stopTelegramBot(handle: TelegramBotHandle): Promise<void>;
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @nodetool-ai/telegram — the Telegram bridge.
3
+ *
4
+ * A bridge process, not a second agent runtime: it translates Telegram updates
5
+ * into `chat_message` commands on the NodeTool server's `/ws` chat protocol and
6
+ * the resulting frames back into Telegram messages. The agent loop, tools,
7
+ * permissions, thread persistence and cost tracking stay on the server.
8
+ *
9
+ * See `docs/telegram-bot-design.md`.
10
+ */
11
+ export { loadConfig, TelegramConfigError, DEFAULT_API_URL, DEFAULT_CONFIG_FILE, DEFAULT_EDIT_THROTTLE_MS, DEFAULT_MAX_QUEUED_TURNS } from "./config.js";
12
+ export { chunkText, splitOnce, DEFAULT_CHUNK_MAX, TELEGRAM_MESSAGE_LIMIT } from "./chunk.js";
13
+ export { markdownToTelegramHtml, escapeHtml } from "./markdown-html.js";
14
+ export { createRendererState, foldFrame, foldFrames, collectAssetRefs } from "./frame-renderer.js";
15
+ export { IdentityClient, IdentityError, TELEGRAM_PROVIDER, TOKEN_EXPIRY_SLACK_MS } from "./identity-client.js";
16
+ export { TurnRouter, deriveThreadId, highestThreadIndex, userHash8 } from "./turn-router.js";
17
+ export { BotApi, BotApiError, TELEGRAM_API_BASE, TELEGRAM_UPLOAD_LIMIT_BYTES } from "./bot-api.js";
18
+ export { TelegramAdapter, POLL_TIMEOUT_SECONDS, STOP_CALLBACK_DATA } from "./telegram-adapter.js";
19
+ export { BOT_COMMANDS, MESSAGES, PUBLIC_COMMANDS, handleCommand, isAllowedUser, parseCommand } from "./commands.js";
20
+ export { registerCommands } from "./register-commands.js";
21
+ export { assetIdOf, createAssetResolver, createBridgeClientFactory, wrapChatSocket } from "./nodetool-client.js";
22
+ import { BotApi } from "./bot-api.js";
23
+ import { IdentityClient } from "./identity-client.js";
24
+ import { TelegramAdapter } from "./telegram-adapter.js";
25
+ import { TurnRouter } from "./turn-router.js";
26
+ import { createAssetResolver, createBridgeClientFactory } from "./nodetool-client.js";
27
+ /**
28
+ * Start the bridge against a running NodeTool server.
29
+ *
30
+ * Long polling only: webhook mode is M3 (design §3), and config already
31
+ * validates the URL/secret pair, so a webhook-configured process is refused
32
+ * here rather than silently polling.
33
+ */
34
+ export function startTelegramBot(config, options = {}) {
35
+ if (config.webhookUrl !== null) {
36
+ throw new Error("TELEGRAM_WEBHOOK_URL is set, but webhook mode arrives in M3. Unset it " +
37
+ "to run the bridge in long-polling mode.");
38
+ }
39
+ const fetchImpl = options.fetch ?? fetch;
40
+ const apiInit = {
41
+ botToken: config.botToken,
42
+ fetch: fetchImpl
43
+ };
44
+ if (options.botApiBaseUrl !== undefined) {
45
+ apiInit.baseUrl = options.botApiBaseUrl;
46
+ }
47
+ const api = new BotApi(apiInit);
48
+ const identity = new IdentityClient({
49
+ apiUrl: config.apiUrl,
50
+ integrationToken: config.integrationToken,
51
+ fetch: fetchImpl
52
+ });
53
+ // The adapter executes the router's ops, and the router routes the adapter's
54
+ // messages: the two are wired through a mutable slot so neither needs the
55
+ // other at construction.
56
+ let adapter = null;
57
+ const router = new TurnRouter({
58
+ identity,
59
+ makeClient: createBridgeClientFactory({ apiUrl: config.apiUrl, fetch: fetchImpl }),
60
+ config: {
61
+ editThrottleMs: config.editThrottleMs,
62
+ maxQueuedTurns: config.maxQueuedTurns,
63
+ permissionMode: "auto"
64
+ },
65
+ executeOps: (context, ops) => adapter?.executeOps(context, ops)
66
+ });
67
+ adapter = new TelegramAdapter({
68
+ api,
69
+ identity,
70
+ router,
71
+ config: { allowUsers: config.allowUsers, apiUrl: config.apiUrl },
72
+ fetch: fetchImpl,
73
+ resolveAsset: createAssetResolver({ apiUrl: config.apiUrl, fetch: fetchImpl })
74
+ });
75
+ const started = adapter;
76
+ const finished = started.poll();
77
+ return {
78
+ finished,
79
+ stop: async () => {
80
+ started.stop();
81
+ router.close();
82
+ await started.flush();
83
+ }
84
+ };
85
+ }
86
+ /** Stop a running bridge. */
87
+ export function stopTelegramBot(handle) {
88
+ return handle.stop();
89
+ }
90
+ //# 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,32 @@
1
+ /**
2
+ * Agent markdown → Telegram HTML parse mode.
3
+ *
4
+ * MarkdownV2 makes every `.`, `-` and `(` in prose an escaping obligation, so
5
+ * one missed character fails the whole message. Telegram's HTML mode needs
6
+ * three characters escaped and supports the five tags this converter emits:
7
+ * `<b>`, `<i>`, `<code>`, `<pre>` and `<a href>`. Anything the converter
8
+ * cannot express — an unbalanced fence, markup it does not recognize as
9
+ * balanced — falls back to plain text, because a turn must never fail on
10
+ * formatting.
11
+ *
12
+ * Hand-rolled on purpose: the supported subset is small enough that a parser
13
+ * dependency would cost more than it covers.
14
+ */
15
+ /** Converted HTML, or the plain-text fallback when conversion is not safe. */
16
+ export type TelegramHtmlResult = {
17
+ readonly ok: true;
18
+ readonly html: string;
19
+ } | {
20
+ readonly ok: false;
21
+ readonly text: string;
22
+ };
23
+ /** Escape the three characters Telegram's HTML mode reserves. */
24
+ export declare function escapeHtml(text: string): string;
25
+ /**
26
+ * Convert agent markdown to Telegram HTML.
27
+ *
28
+ * @returns `{ok: true, html}` when the safe subset covered the input, or
29
+ * `{ok: false, text}` with the original text for a plain-text send.
30
+ */
31
+ export declare function markdownToTelegramHtml(markdown: string): TelegramHtmlResult;
32
+ //# sourceMappingURL=markdown-html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-html.d.ts","sourceRoot":"","sources":["../src/markdown-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA6BlD,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAiND;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAoB3E"}