teleton 0.5.2 → 0.7.0

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 (40) hide show
  1. package/README.md +243 -105
  2. package/dist/chunk-FNV5FF35.js +331 -0
  3. package/dist/chunk-LRCPA7SC.js +149 -0
  4. package/dist/{chunk-WUTMT6DW.js → chunk-N3F7E7DR.js} +114 -566
  5. package/dist/chunk-ND2X5FWB.js +368 -0
  6. package/dist/chunk-NERLQY2H.js +421 -0
  7. package/dist/{chunk-YBA6IBGT.js → chunk-OCLG5GKI.js} +24 -24
  8. package/dist/{chunk-WOXBZOQX.js → chunk-OGIG552S.js} +2152 -4488
  9. package/dist/chunk-RCMD3U65.js +141 -0
  10. package/dist/{chunk-O4R7V5Y2.js → chunk-RO62LO6Z.js} +11 -1
  11. package/dist/chunk-TCD4NZDA.js +3226 -0
  12. package/dist/chunk-UCN6TI25.js +143 -0
  13. package/dist/{chunk-WL2Q3VRD.js → chunk-UDD7FYOU.js} +12 -4
  14. package/dist/chunk-VAUJSSD3.js +20 -0
  15. package/dist/chunk-XBE4JB7C.js +8 -0
  16. package/dist/chunk-XBKSS6DM.js +58 -0
  17. package/dist/cli/index.js +1179 -412
  18. package/dist/client-3VWE7NC4.js +29 -0
  19. package/dist/{get-my-gifts-KVULMBJ3.js → get-my-gifts-RI7FAXAL.js} +3 -1
  20. package/dist/index.js +17 -8
  21. package/dist/{memory-Y5J7CXAR.js → memory-RD7ZSTRV.js} +16 -10
  22. package/dist/{migrate-UEQCDWL2.js → migrate-GO4NOBT7.js} +14 -6
  23. package/dist/{server-BQY7CM2N.js → server-OWVEZTR3.js} +869 -93
  24. package/dist/setup-server-C7ZTPHD5.js +934 -0
  25. package/dist/{task-dependency-resolver-TRPILAHM.js → task-dependency-resolver-WKZWJLLM.js} +19 -15
  26. package/dist/{task-executor-N7XNVK5N.js → task-executor-PD3H4MLO.js} +5 -2
  27. package/dist/tool-adapter-Y3TCEQOC.js +145 -0
  28. package/dist/tool-index-MIVK3D7H.js +250 -0
  29. package/dist/{transcript-7V4UNID4.js → transcript-UDJZP6NK.js} +2 -1
  30. package/dist/web/assets/complete-fZLnb5Ot.js +1 -0
  31. package/dist/web/assets/index-B_FcaX5D.css +1 -0
  32. package/dist/web/assets/index-CbeAP4_n.js +67 -0
  33. package/dist/web/assets/index.es-oXiZF7Hc.js +11 -0
  34. package/dist/web/assets/login-telegram-BP7CJDmx.js +1 -0
  35. package/dist/web/assets/run-DOrDowjK.js +1 -0
  36. package/dist/web/index.html +2 -2
  37. package/package.json +21 -15
  38. package/dist/chunk-5WWR4CU3.js +0 -124
  39. package/dist/web/assets/index-CDMbujHf.css +0 -1
  40. package/dist/web/assets/index-DDX8oQ2z.js +0 -67
@@ -0,0 +1,3226 @@
1
+ import {
2
+ ConfigSchema,
3
+ getKeyPair,
4
+ getTonPrice,
5
+ getWalletAddress,
6
+ getWalletBalance,
7
+ loadWallet
8
+ } from "./chunk-NERLQY2H.js";
9
+ import {
10
+ getCachedHttpEndpoint
11
+ } from "./chunk-QUAPFI2N.js";
12
+ import {
13
+ require_BigInteger
14
+ } from "./chunk-TSKJCWQQ.js";
15
+ import {
16
+ getErrorMessage
17
+ } from "./chunk-XBE4JB7C.js";
18
+ import {
19
+ getProviderMetadata
20
+ } from "./chunk-LRCPA7SC.js";
21
+ import {
22
+ createDbWrapper,
23
+ migrateFromMainDb,
24
+ openModuleDb
25
+ } from "./chunk-UCN6TI25.js";
26
+ import {
27
+ tonapiFetch
28
+ } from "./chunk-XBKSS6DM.js";
29
+ import {
30
+ PAYMENT_TOLERANCE_RATIO
31
+ } from "./chunk-RO62LO6Z.js";
32
+ import {
33
+ RETRY_BLOCKCHAIN_BASE_DELAY_MS,
34
+ RETRY_BLOCKCHAIN_MAX_DELAY_MS,
35
+ RETRY_BLOCKCHAIN_TIMEOUT_MS,
36
+ RETRY_DEFAULT_BASE_DELAY_MS,
37
+ RETRY_DEFAULT_MAX_ATTEMPTS,
38
+ RETRY_DEFAULT_MAX_DELAY_MS,
39
+ RETRY_DEFAULT_TIMEOUT_MS
40
+ } from "./chunk-4DU3C27M.js";
41
+ import {
42
+ ALLOWED_EXTENSIONS,
43
+ TELETON_ROOT,
44
+ WORKSPACE_PATHS,
45
+ WORKSPACE_ROOT
46
+ } from "./chunk-EYWNOHMJ.js";
47
+ import {
48
+ createLogger
49
+ } from "./chunk-RCMD3U65.js";
50
+ import {
51
+ __require,
52
+ __toESM
53
+ } from "./chunk-QGM4M3NI.js";
54
+
55
+ // src/config/loader.ts
56
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
57
+ import { parse, stringify } from "yaml";
58
+ import { homedir } from "os";
59
+ import { dirname, join } from "path";
60
+ var log = createLogger("Config");
61
+ var DEFAULT_CONFIG_PATH = join(TELETON_ROOT, "config.yaml");
62
+ function expandPath(path) {
63
+ if (path.startsWith("~")) {
64
+ return join(homedir(), path.slice(1));
65
+ }
66
+ return path;
67
+ }
68
+ function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
69
+ const fullPath = expandPath(configPath);
70
+ if (!existsSync(fullPath)) {
71
+ throw new Error(`Config file not found: ${fullPath}
72
+ Run 'teleton setup' to create one.`);
73
+ }
74
+ let content;
75
+ try {
76
+ content = readFileSync(fullPath, "utf-8");
77
+ } catch (error) {
78
+ throw new Error(`Cannot read config file ${fullPath}: ${error.message}`);
79
+ }
80
+ let raw;
81
+ try {
82
+ raw = parse(content);
83
+ } catch (error) {
84
+ throw new Error(`Invalid YAML in ${fullPath}: ${error.message}`);
85
+ }
86
+ if (raw && typeof raw === "object" && "market" in raw) {
87
+ log.warn("config.market is deprecated and ignored. Use market-api plugin instead.");
88
+ delete raw.market;
89
+ }
90
+ const result = ConfigSchema.safeParse(raw);
91
+ if (!result.success) {
92
+ throw new Error(`Invalid config: ${result.error.message}`);
93
+ }
94
+ const config = result.data;
95
+ const provider = config.agent.provider;
96
+ if (provider !== "anthropic" && !raw.agent?.model) {
97
+ const meta = getProviderMetadata(provider);
98
+ config.agent.model = meta.defaultModel;
99
+ }
100
+ config.telegram.session_path = expandPath(config.telegram.session_path);
101
+ config.storage.sessions_file = expandPath(config.storage.sessions_file);
102
+ config.storage.pairing_file = expandPath(config.storage.pairing_file);
103
+ config.storage.memory_file = expandPath(config.storage.memory_file);
104
+ if (process.env.TELETON_API_KEY) {
105
+ config.agent.api_key = process.env.TELETON_API_KEY;
106
+ }
107
+ if (process.env.TELETON_TG_API_ID) {
108
+ const apiId = parseInt(process.env.TELETON_TG_API_ID, 10);
109
+ if (isNaN(apiId)) {
110
+ throw new Error(
111
+ `Invalid TELETON_TG_API_ID environment variable: "${process.env.TELETON_TG_API_ID}" is not a valid integer`
112
+ );
113
+ }
114
+ config.telegram.api_id = apiId;
115
+ }
116
+ if (process.env.TELETON_TG_API_HASH) {
117
+ config.telegram.api_hash = process.env.TELETON_TG_API_HASH;
118
+ }
119
+ if (process.env.TELETON_TG_PHONE) {
120
+ config.telegram.phone = process.env.TELETON_TG_PHONE;
121
+ }
122
+ if (process.env.TELETON_WEBUI_ENABLED) {
123
+ config.webui.enabled = process.env.TELETON_WEBUI_ENABLED === "true";
124
+ }
125
+ if (process.env.TELETON_WEBUI_PORT) {
126
+ const port = parseInt(process.env.TELETON_WEBUI_PORT, 10);
127
+ if (!isNaN(port) && port >= 1024 && port <= 65535) {
128
+ config.webui.port = port;
129
+ }
130
+ }
131
+ if (process.env.TELETON_WEBUI_HOST) {
132
+ config.webui.host = process.env.TELETON_WEBUI_HOST;
133
+ if (!["127.0.0.1", "localhost", "::1"].includes(config.webui.host)) {
134
+ log.warn(
135
+ { host: config.webui.host },
136
+ "WebUI bound to non-loopback address \u2014 ensure auth_token is set"
137
+ );
138
+ }
139
+ }
140
+ if (process.env.TELETON_BASE_URL) {
141
+ try {
142
+ new URL(process.env.TELETON_BASE_URL);
143
+ config.agent.base_url = process.env.TELETON_BASE_URL;
144
+ } catch {
145
+ throw new Error(
146
+ `Invalid TELETON_BASE_URL: "${process.env.TELETON_BASE_URL}" is not a valid URL`
147
+ );
148
+ }
149
+ }
150
+ if (process.env.TELETON_TAVILY_API_KEY) {
151
+ config.tavily_api_key = process.env.TELETON_TAVILY_API_KEY;
152
+ }
153
+ if (process.env.TELETON_TONAPI_KEY) {
154
+ config.tonapi_key = process.env.TELETON_TONAPI_KEY;
155
+ }
156
+ return config;
157
+ }
158
+ function configExists(configPath = DEFAULT_CONFIG_PATH) {
159
+ return existsSync(expandPath(configPath));
160
+ }
161
+ function getDefaultConfigPath() {
162
+ return DEFAULT_CONFIG_PATH;
163
+ }
164
+
165
+ // src/soul/loader.ts
166
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
167
+
168
+ // src/memory/daily-logs.ts
169
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync2 } from "fs";
170
+ import { join as join2 } from "path";
171
+
172
+ // src/workspace/validator.ts
173
+ import { existsSync as existsSync2, lstatSync, readdirSync } from "fs";
174
+ import { resolve, normalize, relative, extname, basename } from "path";
175
+ import { homedir as homedir2 } from "os";
176
+ var WorkspaceSecurityError = class extends Error {
177
+ constructor(message, attemptedPath) {
178
+ super(message);
179
+ this.attemptedPath = attemptedPath;
180
+ this.name = "WorkspaceSecurityError";
181
+ }
182
+ };
183
+ function decodeRecursive(str) {
184
+ let decoded = str;
185
+ let prev = "";
186
+ let iterations = 0;
187
+ const maxIterations = 10;
188
+ while (decoded !== prev && iterations < maxIterations) {
189
+ prev = decoded;
190
+ try {
191
+ decoded = decodeURIComponent(decoded);
192
+ } catch {
193
+ break;
194
+ }
195
+ iterations++;
196
+ }
197
+ return decoded;
198
+ }
199
+ function validatePath(inputPath, allowCreate = false) {
200
+ if (!inputPath || inputPath.trim() === "") {
201
+ throw new WorkspaceSecurityError("Path cannot be empty.", inputPath);
202
+ }
203
+ const trimmedPath = inputPath.trim().replace(/\\/g, "/");
204
+ const decodedPath = decodeRecursive(trimmedPath);
205
+ let absolutePath;
206
+ if (decodedPath.startsWith("/")) {
207
+ absolutePath = resolve(normalize(decodedPath));
208
+ } else if (decodedPath.startsWith("~/")) {
209
+ const expanded = decodedPath.replace(/^~(?=$|[\\/])/, homedir2());
210
+ absolutePath = resolve(expanded);
211
+ } else {
212
+ absolutePath = resolve(WORKSPACE_ROOT, normalize(decodedPath));
213
+ }
214
+ const relativePath = relative(WORKSPACE_ROOT, absolutePath);
215
+ if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
216
+ throw new WorkspaceSecurityError(
217
+ `Access denied: Path '${inputPath}' is outside the workspace. Only files in ~/.teleton/workspace/ are accessible.`,
218
+ inputPath
219
+ );
220
+ }
221
+ const exists = existsSync2(absolutePath);
222
+ if (!exists && !allowCreate) {
223
+ throw new WorkspaceSecurityError(
224
+ `File not found: '${inputPath}' does not exist in workspace.`,
225
+ inputPath
226
+ );
227
+ }
228
+ if (exists) {
229
+ const stats = lstatSync(absolutePath);
230
+ if (stats.isSymbolicLink()) {
231
+ throw new WorkspaceSecurityError(
232
+ `Access denied: Symbolic links are not allowed for security reasons.`,
233
+ inputPath
234
+ );
235
+ }
236
+ }
237
+ return {
238
+ absolutePath,
239
+ relativePath,
240
+ exists,
241
+ isDirectory: exists ? lstatSync(absolutePath).isDirectory() : false,
242
+ extension: extname(absolutePath).toLowerCase(),
243
+ filename: basename(absolutePath)
244
+ };
245
+ }
246
+ function validateReadPath(inputPath) {
247
+ const validated = validatePath(inputPath, false);
248
+ if (validated.isDirectory) {
249
+ throw new WorkspaceSecurityError(`Cannot read directory as file: '${inputPath}'`, inputPath);
250
+ }
251
+ return validated;
252
+ }
253
+ var IMMUTABLE_FILES = ["SOUL.md", "STRATEGY.md", "SECURITY.md"];
254
+ function validateWritePath(inputPath, fileType) {
255
+ const validated = validatePath(inputPath, true);
256
+ if (IMMUTABLE_FILES.includes(validated.filename)) {
257
+ throw new WorkspaceSecurityError(
258
+ `Cannot write to ${validated.filename}. This file is configured by the owner. Use memory_write instead.`,
259
+ inputPath
260
+ );
261
+ }
262
+ if (fileType && ALLOWED_EXTENSIONS[fileType]) {
263
+ const allowedExts = ALLOWED_EXTENSIONS[fileType];
264
+ if (!allowedExts.includes(validated.extension)) {
265
+ throw new WorkspaceSecurityError(
266
+ `Invalid file type: '${validated.extension}' is not allowed for ${fileType}. Allowed: ${allowedExts.join(", ")}`,
267
+ inputPath
268
+ );
269
+ }
270
+ }
271
+ return validated;
272
+ }
273
+ function validateDirectory(inputPath) {
274
+ const validated = validatePath(inputPath, true);
275
+ if (validated.exists && !validated.isDirectory) {
276
+ throw new WorkspaceSecurityError(
277
+ `Path exists but is not a directory: '${inputPath}'`,
278
+ inputPath
279
+ );
280
+ }
281
+ return validated;
282
+ }
283
+
284
+ // src/memory/daily-logs.ts
285
+ var log2 = createLogger("Memory");
286
+ var MEMORY_DIR = WORKSPACE_PATHS.MEMORY_DIR;
287
+ function formatDate(date) {
288
+ const year = date.getFullYear();
289
+ const month = String(date.getMonth() + 1).padStart(2, "0");
290
+ const day = String(date.getDate()).padStart(2, "0");
291
+ return `${year}-${month}-${day}`;
292
+ }
293
+ function getDailyLogPath(date = /* @__PURE__ */ new Date()) {
294
+ return join2(MEMORY_DIR, `${formatDate(date)}.md`);
295
+ }
296
+ function ensureMemoryDir() {
297
+ if (!existsSync3(MEMORY_DIR)) {
298
+ mkdirSync2(MEMORY_DIR, { recursive: true });
299
+ }
300
+ }
301
+ function appendToDailyLog(content, date = /* @__PURE__ */ new Date()) {
302
+ try {
303
+ ensureMemoryDir();
304
+ const logPath = getDailyLogPath(date);
305
+ const timestamp = date.toLocaleTimeString("en-US", { hour12: false });
306
+ if (!existsSync3(logPath)) {
307
+ const header = `# Daily Log - ${formatDate(date)}
308
+
309
+ `;
310
+ appendFileSync(logPath, header, "utf-8");
311
+ }
312
+ const entry = `## ${timestamp}
313
+
314
+ ${content}
315
+
316
+ ---
317
+
318
+ `;
319
+ appendFileSync(logPath, entry, "utf-8");
320
+ log2.info(`Daily log updated: ${logPath}`);
321
+ } catch (error) {
322
+ log2.error({ err: error }, "Failed to write daily log");
323
+ }
324
+ }
325
+ function readDailyLog(date = /* @__PURE__ */ new Date()) {
326
+ try {
327
+ const logPath = getDailyLogPath(date);
328
+ if (!existsSync3(logPath)) return null;
329
+ return readFileSync2(logPath, "utf-8");
330
+ } catch {
331
+ return null;
332
+ }
333
+ }
334
+ var DAILY_LOG_LINE_LIMIT = 100;
335
+ function truncateDailyLog(content) {
336
+ const lines = content.split("\n");
337
+ if (lines.length <= DAILY_LOG_LINE_LIMIT) return content;
338
+ const truncated = lines.slice(-DAILY_LOG_LINE_LIMIT).join("\n");
339
+ const dropped = lines.length - DAILY_LOG_LINE_LIMIT;
340
+ return `_[... ${dropped} earlier lines omitted]_
341
+
342
+ ${truncated}`;
343
+ }
344
+ function readRecentMemory() {
345
+ const today = /* @__PURE__ */ new Date();
346
+ const yesterday = new Date(today);
347
+ yesterday.setDate(yesterday.getDate() - 1);
348
+ const parts = [];
349
+ const yesterdayLog = readDailyLog(yesterday);
350
+ if (yesterdayLog) {
351
+ parts.push(`## Yesterday (${formatDate(yesterday)})
352
+
353
+ ${truncateDailyLog(yesterdayLog)}`);
354
+ }
355
+ const todayLog = readDailyLog(today);
356
+ if (todayLog) {
357
+ parts.push(`## Today (${formatDate(today)})
358
+
359
+ ${truncateDailyLog(todayLog)}`);
360
+ }
361
+ if (parts.length === 0) {
362
+ return null;
363
+ }
364
+ return `# Recent Memory
365
+
366
+ ${parts.join("\n\n---\n\n")}`;
367
+ }
368
+ function writeSummaryToDailyLog(summary) {
369
+ appendToDailyLog(`### Memory Flush (Pre-Compaction)
370
+
371
+ ${summary}`);
372
+ }
373
+
374
+ // src/utils/sanitize.ts
375
+ function sanitizeForPrompt(text) {
376
+ return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").replace(/[\u00AD\u034F\u061C\u180E\u200B-\u200F\u2060-\u2064\uFEFF]/g, "").replace(/[\u202A-\u202E\u2066-\u2069]/g, "").replace(/[\r\n\u2028\u2029]+/g, " ").replace(/#{1,6}\s/g, "").replace(/<\/?[a-zA-Z_][^>]*>/g, "").replace(/`{3,}/g, "`").trim().slice(0, 128);
377
+ }
378
+ function sanitizeForContext(text) {
379
+ return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").replace(/[\u00AD\u034F\u061C\u180E\u200B-\u200F\u2060-\u2064\uFEFF]/g, "").replace(/[\u202A-\u202E\u2066-\u2069]/g, "").replace(/[\u2028\u2029]/g, "\n").replace(/<\/?[a-zA-Z_][^>]*>/g, "").replace(/`{3,}/g, "``").trim();
380
+ }
381
+
382
+ // src/soul/loader.ts
383
+ var SOUL_PATHS = [WORKSPACE_PATHS.SOUL];
384
+ var STRATEGY_PATHS = [WORKSPACE_PATHS.STRATEGY];
385
+ var SECURITY_PATHS = [WORKSPACE_PATHS.SECURITY];
386
+ var MEMORY_PATH = WORKSPACE_PATHS.MEMORY;
387
+ var DEFAULT_SOUL = `# Teleton AI
388
+
389
+ You are Teleton, a personal AI assistant that operates through Telegram.
390
+
391
+ ## Personality
392
+ - Helpful and concise
393
+ - Direct and honest
394
+ - Friendly but professional
395
+
396
+ ## Guidelines
397
+ - Keep responses short and actionable
398
+ - Use markdown when appropriate
399
+ - Respect user privacy
400
+ - Be transparent about capabilities and limitations
401
+ `;
402
+ var fileCache = /* @__PURE__ */ new Map();
403
+ var FILE_CACHE_TTL = 6e4;
404
+ function cachedReadFile(path) {
405
+ const now = Date.now();
406
+ const cached = fileCache.get(path);
407
+ if (cached && now < cached.expiry) return cached.content;
408
+ let content = null;
409
+ try {
410
+ if (existsSync4(path)) content = readFileSync3(path, "utf-8");
411
+ } catch {
412
+ }
413
+ fileCache.set(path, { content, expiry: now + FILE_CACHE_TTL });
414
+ return content;
415
+ }
416
+ function clearPromptCache() {
417
+ fileCache.clear();
418
+ }
419
+ function loadSoul() {
420
+ for (const path of SOUL_PATHS) {
421
+ const content = cachedReadFile(path);
422
+ if (content) return content;
423
+ }
424
+ return DEFAULT_SOUL;
425
+ }
426
+ function loadStrategy() {
427
+ for (const path of STRATEGY_PATHS) {
428
+ const content = cachedReadFile(path);
429
+ if (content) return content;
430
+ }
431
+ return null;
432
+ }
433
+ function loadSecurity() {
434
+ for (const path of SECURITY_PATHS) {
435
+ const content = cachedReadFile(path);
436
+ if (content) return content;
437
+ }
438
+ return null;
439
+ }
440
+ var MEMORY_HARD_LIMIT = 150;
441
+ function loadPersistentMemory() {
442
+ const content = cachedReadFile(MEMORY_PATH);
443
+ if (!content) return null;
444
+ const lines = content.split("\n");
445
+ if (lines.length <= MEMORY_HARD_LIMIT) {
446
+ return content;
447
+ }
448
+ const truncated = lines.slice(0, MEMORY_HARD_LIMIT).join("\n");
449
+ const remaining = lines.length - MEMORY_HARD_LIMIT;
450
+ return `${truncated}
451
+
452
+ _[... ${remaining} more lines not loaded. Consider consolidating MEMORY.md to keep it under ${MEMORY_HARD_LIMIT} lines.]_`;
453
+ }
454
+ function loadMemoryContext() {
455
+ const parts = [];
456
+ const persistentMemory = loadPersistentMemory();
457
+ if (persistentMemory) {
458
+ parts.push(`## Persistent Memory
459
+
460
+ ${sanitizeForContext(persistentMemory)}`);
461
+ }
462
+ const recentMemory = readRecentMemory();
463
+ if (recentMemory) {
464
+ parts.push(sanitizeForContext(recentMemory));
465
+ }
466
+ if (parts.length === 0) {
467
+ return null;
468
+ }
469
+ return parts.join("\n\n---\n\n");
470
+ }
471
+ function buildSystemPrompt(options) {
472
+ const soul = options.soul ?? loadSoul();
473
+ const parts = [soul];
474
+ const security = loadSecurity();
475
+ if (security) {
476
+ parts.push(`
477
+ ${security}`);
478
+ }
479
+ const includeStrategy = options.includeStrategy ?? true;
480
+ if (includeStrategy) {
481
+ const strategy = options.strategy ?? loadStrategy();
482
+ if (strategy) {
483
+ parts.push(`
484
+ ${strategy}`);
485
+ }
486
+ }
487
+ parts.push(`
488
+ ## Your Workspace
489
+
490
+ You have a personal workspace at \`~/.teleton/workspace/\` where you can store and manage files.
491
+
492
+ **Structure:**
493
+ - \`SOUL.md\` - Your personality and behavior guidelines
494
+ - \`MEMORY.md\` - Persistent memory (long-term facts you've learned)
495
+ - \`STRATEGY.md\` - Business strategy and trading rules
496
+ - \`memory/\` - Daily logs (auto-created per day)
497
+ - \`downloads/\` - Media downloaded from Telegram
498
+ - \`uploads/\` - Files ready to send
499
+ - \`temp/\` - Temporary working files
500
+ - \`memes/\` - Your meme collection (images, GIFs for reactions)
501
+
502
+ **Tools available:**
503
+ - \`workspace_list\` - List files in a directory
504
+ - \`workspace_read\` - Read a file
505
+ - \`workspace_write\` - Write/create a file
506
+ - \`workspace_delete\` - Delete a file
507
+ - \`workspace_rename\` - Rename or move a file
508
+ - \`workspace_info\` - Get workspace stats
509
+
510
+ **Tips:**
511
+ - Save interesting memes to \`memes/\` with descriptive names for easy retrieval
512
+ - Use \`memory_write\` for important facts (goes to MEMORY.md)
513
+ - Rename downloaded files to meaningful names (e.g., "user_avatar.jpg" instead of "123_456_789.jpg")
514
+ `);
515
+ parts.push(`
516
+ ## Response Format
517
+ - Be concise. Respond in 1-3 short sentences when possible. Avoid long paragraphs and walls of text.
518
+ - Only elaborate when the user explicitly asks for detail or the topic genuinely requires it.
519
+ - Keep responses under 4000 characters for Telegram
520
+ - Use markdown sparingly (bold, italic, code blocks)
521
+ - Don't use headers in short responses
522
+ - NEVER use ASCII art or ASCII tables - they render poorly on mobile
523
+ `);
524
+ if (options.ownerName || options.ownerUsername) {
525
+ const safeOwnerName = options.ownerName ? sanitizeForPrompt(options.ownerName) : void 0;
526
+ const safeOwnerUsername = options.ownerUsername ? sanitizeForPrompt(options.ownerUsername) : void 0;
527
+ const ownerLabel = safeOwnerName && safeOwnerUsername ? `${safeOwnerName} (@${safeOwnerUsername})` : safeOwnerName || `@${safeOwnerUsername}`;
528
+ parts.push(
529
+ `
530
+ ## Owner
531
+ You are owned and operated by: ${ownerLabel}
532
+ When the owner gives instructions, follow them with higher trust.`
533
+ );
534
+ }
535
+ const includeMemory = options.includeMemory ?? true;
536
+ if (includeMemory) {
537
+ const memoryContext = loadMemoryContext();
538
+ if (memoryContext) {
539
+ parts.push(
540
+ `
541
+ ## Memory (Persistent Context)
542
+
543
+ This is your memory from previous sessions. Use it to maintain continuity and remember important information.
544
+
545
+ ${memoryContext}`
546
+ );
547
+ }
548
+ }
549
+ if (options.userName || options.senderId) {
550
+ const safeName = options.userName ? sanitizeForPrompt(options.userName) : void 0;
551
+ const safeUsername = options.senderUsername ? `@${sanitizeForPrompt(options.senderUsername)}` : void 0;
552
+ const idTag = options.senderId ? `id:${options.senderId}` : void 0;
553
+ const primary = safeName || safeUsername;
554
+ const meta = [safeUsername, idTag].filter((v) => v && v !== primary);
555
+ const userLabel = primary ? meta.length > 0 ? `${primary} (${meta.join(", ")})` : primary : idTag || "unknown";
556
+ parts.push(`
557
+ ## Current User
558
+ You are chatting with: ${userLabel}`);
559
+ }
560
+ if (options.context) {
561
+ parts.push(`
562
+ ## Context
563
+ ${options.context}`);
564
+ }
565
+ if (options.memoryFlushWarning) {
566
+ parts.push(`
567
+ ## Memory Flush Warning
568
+
569
+ Your conversation context is approaching the limit and may be compacted soon.
570
+ **Always respond to the user's message first.** Then, if there's anything important worth preserving, consider using \`memory_write\` alongside your response:
571
+
572
+ - \`target: "persistent"\` for facts, lessons, contacts, decisions
573
+ - \`target: "daily"\` for session notes, events, temporary context
574
+ `);
575
+ }
576
+ return parts.join("\n");
577
+ }
578
+
579
+ // src/agent/tools/plugin-loader.ts
580
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync6, statSync } from "fs";
581
+ import { join as join4 } from "path";
582
+ import { pathToFileURL } from "url";
583
+ import { execFile } from "child_process";
584
+ import { promisify } from "util";
585
+
586
+ // src/agent/tools/plugin-validator.ts
587
+ import { z } from "zod";
588
+ var log3 = createLogger("PluginValidator");
589
+ var ManifestSchema = z.object({
590
+ name: z.string().min(1).max(64).regex(
591
+ /^[a-z0-9][a-z0-9-]*$/,
592
+ "Must be lowercase alphanumeric with hyphens, starting with a letter or number"
593
+ ),
594
+ version: z.string().regex(/^\d+\.\d+\.\d+$/, "Must be semver (e.g., 1.0.0)"),
595
+ author: z.string().max(128).optional(),
596
+ description: z.string().max(256).optional(),
597
+ dependencies: z.array(z.string()).optional(),
598
+ defaultConfig: z.record(z.string(), z.unknown()).optional(),
599
+ sdkVersion: z.string().max(32).optional(),
600
+ secrets: z.record(
601
+ z.string(),
602
+ z.object({
603
+ required: z.boolean(),
604
+ description: z.string().max(256),
605
+ env: z.string().max(128).optional()
606
+ })
607
+ ).optional()
608
+ });
609
+ function validateManifest(raw) {
610
+ return ManifestSchema.parse(raw);
611
+ }
612
+ function validateToolDefs(defs, pluginName) {
613
+ const valid = [];
614
+ for (const def of defs) {
615
+ if (!def || typeof def !== "object") {
616
+ log3.warn(`[${pluginName}] tool is not an object, skipping`);
617
+ continue;
618
+ }
619
+ const t = def;
620
+ if (!t.name || typeof t.name !== "string") {
621
+ log3.warn(`[${pluginName}] tool missing 'name', skipping`);
622
+ continue;
623
+ }
624
+ if (!t.description || typeof t.description !== "string") {
625
+ log3.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
626
+ continue;
627
+ }
628
+ if (!t.execute || typeof t.execute !== "function") {
629
+ log3.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
630
+ continue;
631
+ }
632
+ valid.push(t);
633
+ }
634
+ return valid;
635
+ }
636
+ function sanitizeConfigForPlugins(config) {
637
+ return {
638
+ agent: {
639
+ provider: config.agent.provider,
640
+ model: config.agent.model,
641
+ max_tokens: config.agent.max_tokens
642
+ },
643
+ telegram: {
644
+ admin_ids: config.telegram.admin_ids
645
+ },
646
+ deals: { enabled: config.deals.enabled }
647
+ };
648
+ }
649
+
650
+ // packages/sdk/dist/index.js
651
+ var PluginSDKError = class extends Error {
652
+ constructor(message, code) {
653
+ super(message);
654
+ this.code = code;
655
+ }
656
+ name = "PluginSDKError";
657
+ };
658
+ var SDK_VERSION = "1.0.0";
659
+
660
+ // src/ton/transfer.ts
661
+ import { WalletContractV5R1, TonClient, toNano, internal } from "@ton/ton";
662
+ import { Address, SendMode } from "@ton/core";
663
+ var log4 = createLogger("TON");
664
+ async function sendTon(params) {
665
+ try {
666
+ const { toAddress, amount, comment = "", bounce = false } = params;
667
+ if (!Number.isFinite(amount) || amount <= 0) {
668
+ log4.error({ amount }, "Invalid transfer amount");
669
+ return null;
670
+ }
671
+ let recipientAddress;
672
+ try {
673
+ recipientAddress = Address.parse(toAddress);
674
+ } catch (e) {
675
+ log4.error({ err: e }, `Invalid recipient address: ${toAddress}`);
676
+ return null;
677
+ }
678
+ const keyPair = await getKeyPair();
679
+ if (!keyPair) {
680
+ log4.error("Wallet not initialized");
681
+ return null;
682
+ }
683
+ const wallet = WalletContractV5R1.create({
684
+ workchain: 0,
685
+ publicKey: keyPair.publicKey
686
+ });
687
+ const endpoint = await getCachedHttpEndpoint();
688
+ const client = new TonClient({ endpoint });
689
+ const contract = client.open(wallet);
690
+ const seqno = await contract.getSeqno();
691
+ await contract.sendTransfer({
692
+ seqno,
693
+ secretKey: keyPair.secretKey,
694
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
695
+ messages: [
696
+ internal({
697
+ to: recipientAddress,
698
+ value: toNano(amount),
699
+ body: comment,
700
+ bounce
701
+ })
702
+ ]
703
+ });
704
+ const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
705
+ log4.info(`Sent ${amount} TON to ${toAddress.slice(0, 8)}... - seqno: ${seqno}`);
706
+ return pseudoHash;
707
+ } catch (error) {
708
+ log4.error({ err: error }, "Error sending TON");
709
+ return null;
710
+ }
711
+ }
712
+
713
+ // src/utils/retry.ts
714
+ var log5 = createLogger("Utils");
715
+ var DEFAULT_OPTIONS = {
716
+ maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
717
+ baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
718
+ maxDelayMs: RETRY_DEFAULT_MAX_DELAY_MS,
719
+ timeout: RETRY_DEFAULT_TIMEOUT_MS
720
+ };
721
+ function sleep(ms) {
722
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
723
+ }
724
+ async function withRetry(fn, options = {}) {
725
+ const opts = { ...DEFAULT_OPTIONS, ...options };
726
+ let lastError;
727
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
728
+ try {
729
+ const result = await Promise.race([
730
+ fn(),
731
+ new Promise(
732
+ (_, reject) => setTimeout(() => reject(new Error("Operation timeout")), opts.timeout)
733
+ )
734
+ ]);
735
+ return result;
736
+ } catch (error) {
737
+ lastError = error instanceof Error ? error : new Error(String(error));
738
+ log5.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
739
+ if (attempt < opts.maxAttempts) {
740
+ const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
741
+ await sleep(delay);
742
+ }
743
+ }
744
+ }
745
+ throw lastError || new Error("All retry attempts failed");
746
+ }
747
+ async function withBlockchainRetry(fn, operation = "blockchain operation") {
748
+ try {
749
+ return await withRetry(fn, {
750
+ maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
751
+ baseDelayMs: RETRY_BLOCKCHAIN_BASE_DELAY_MS,
752
+ maxDelayMs: RETRY_BLOCKCHAIN_MAX_DELAY_MS,
753
+ timeout: RETRY_BLOCKCHAIN_TIMEOUT_MS
754
+ });
755
+ } catch (error) {
756
+ const message = getErrorMessage(error);
757
+ throw new Error(`${operation} failed after retries: ${message}`);
758
+ }
759
+ }
760
+
761
+ // src/sdk/ton.ts
762
+ var DEFAULT_MAX_AGE_MINUTES = 10;
763
+ var DEFAULT_TX_RETENTION_DAYS = 30;
764
+ var CLEANUP_PROBABILITY = 0.1;
765
+ function cleanupOldTransactions(db, retentionDays, log7) {
766
+ if (Math.random() > CLEANUP_PROBABILITY) return;
767
+ try {
768
+ const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
769
+ const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
770
+ if (result.changes > 0) {
771
+ log7.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
772
+ }
773
+ } catch (err) {
774
+ log7.error("Transaction cleanup failed:", err);
775
+ }
776
+ }
777
+ function createTonSDK(log7, db) {
778
+ return {
779
+ getAddress() {
780
+ try {
781
+ return getWalletAddress();
782
+ } catch (err) {
783
+ log7.error("ton.getAddress() failed:", err);
784
+ return null;
785
+ }
786
+ },
787
+ async getBalance(address) {
788
+ try {
789
+ const addr = address ?? getWalletAddress();
790
+ if (!addr) return null;
791
+ return await getWalletBalance(addr);
792
+ } catch (err) {
793
+ log7.error("ton.getBalance() failed:", err);
794
+ return null;
795
+ }
796
+ },
797
+ async getPrice() {
798
+ try {
799
+ return await getTonPrice();
800
+ } catch (err) {
801
+ log7.error("ton.getPrice() failed:", err);
802
+ return null;
803
+ }
804
+ },
805
+ async sendTON(to, amount, comment) {
806
+ const walletAddr = getWalletAddress();
807
+ if (!walletAddr) {
808
+ throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
809
+ }
810
+ if (!Number.isFinite(amount) || amount <= 0) {
811
+ throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
812
+ }
813
+ try {
814
+ const { Address: Address2 } = await import("@ton/core");
815
+ Address2.parse(to);
816
+ } catch {
817
+ throw new PluginSDKError("Invalid TON address format", "INVALID_ADDRESS");
818
+ }
819
+ try {
820
+ const txRef = await sendTon({
821
+ toAddress: to,
822
+ amount,
823
+ comment,
824
+ bounce: false
825
+ });
826
+ if (!txRef) {
827
+ throw new PluginSDKError(
828
+ "Transaction failed \u2014 no reference returned",
829
+ "OPERATION_FAILED"
830
+ );
831
+ }
832
+ return { txRef, amount };
833
+ } catch (err) {
834
+ if (err instanceof PluginSDKError) throw err;
835
+ throw new PluginSDKError(
836
+ `Failed to send TON: ${err instanceof Error ? err.message : String(err)}`,
837
+ "OPERATION_FAILED"
838
+ );
839
+ }
840
+ },
841
+ async getTransactions(address, limit) {
842
+ try {
843
+ const { TonClient: TonClient2 } = await import("@ton/ton");
844
+ const { Address: Address2 } = await import("@ton/core");
845
+ const { getCachedHttpEndpoint: getCachedHttpEndpoint2 } = await import("./endpoint-FLYNEZ2F.js");
846
+ const { formatTransactions } = await import("./format-transactions-FD74HI5N.js");
847
+ const addressObj = Address2.parse(address);
848
+ const endpoint = await getCachedHttpEndpoint2();
849
+ const client = new TonClient2({ endpoint });
850
+ const transactions = await withBlockchainRetry(
851
+ () => client.getTransactions(addressObj, {
852
+ limit: Math.min(limit ?? 10, 50)
853
+ }),
854
+ "sdk.ton.getTransactions"
855
+ );
856
+ return formatTransactions(transactions);
857
+ } catch (err) {
858
+ log7.error("ton.getTransactions() failed:", err);
859
+ return [];
860
+ }
861
+ },
862
+ async verifyPayment(params) {
863
+ if (!db) {
864
+ throw new PluginSDKError(
865
+ "No database available \u2014 verifyPayment requires migrate() with used_transactions table",
866
+ "OPERATION_FAILED"
867
+ );
868
+ }
869
+ const address = getWalletAddress();
870
+ if (!address) {
871
+ throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
872
+ }
873
+ const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
874
+ cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log7);
875
+ try {
876
+ const txs = await this.getTransactions(address, 20);
877
+ for (const tx of txs) {
878
+ if (tx.type !== "ton_received") continue;
879
+ if (!tx.amount || !tx.from) continue;
880
+ const tonAmount = parseFloat(tx.amount.replace(/ TON$/, ""));
881
+ if (isNaN(tonAmount)) continue;
882
+ if (tonAmount < params.amount * PAYMENT_TOLERANCE_RATIO) continue;
883
+ if (tx.secondsAgo > maxAgeMinutes * 60) continue;
884
+ const memo = (tx.comment ?? "").trim().toLowerCase().replace(/^@/, "");
885
+ const expected = params.memo.toLowerCase().replace(/^@/, "");
886
+ if (memo !== expected) continue;
887
+ const txHash = tx.hash;
888
+ const result = db.prepare(
889
+ `INSERT OR IGNORE INTO used_transactions (tx_hash, user_id, amount, game_type, used_at)
890
+ VALUES (?, ?, ?, ?, unixepoch())`
891
+ ).run(txHash, params.memo, tonAmount, params.gameType);
892
+ if (result.changes === 0) continue;
893
+ return {
894
+ verified: true,
895
+ txHash,
896
+ amount: tonAmount,
897
+ playerWallet: tx.from,
898
+ date: tx.date,
899
+ secondsAgo: tx.secondsAgo
900
+ };
901
+ }
902
+ return {
903
+ verified: false,
904
+ error: `Payment not found. Send ${params.amount} TON to ${address} with memo: ${params.memo}`
905
+ };
906
+ } catch (err) {
907
+ if (err instanceof PluginSDKError) throw err;
908
+ log7.error("ton.verifyPayment() failed:", err);
909
+ return {
910
+ verified: false,
911
+ error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
912
+ };
913
+ }
914
+ },
915
+ // ─── Jettons ─────────────────────────────────────────────────
916
+ async getJettonBalances(ownerAddress) {
917
+ try {
918
+ const addr = ownerAddress ?? getWalletAddress();
919
+ if (!addr) return [];
920
+ const response = await tonapiFetch(`/accounts/${addr}/jettons`);
921
+ if (!response.ok) {
922
+ log7.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
923
+ return [];
924
+ }
925
+ const data = await response.json();
926
+ const balances = [];
927
+ for (const item of data.balances || []) {
928
+ const { balance, wallet_address, jetton } = item;
929
+ if (jetton.verification === "blacklist") continue;
930
+ const decimals = jetton.decimals || 9;
931
+ const rawBalance = BigInt(balance);
932
+ const divisor = BigInt(10 ** decimals);
933
+ const wholePart = rawBalance / divisor;
934
+ const fractionalPart = rawBalance % divisor;
935
+ const balanceFormatted = fractionalPart === BigInt(0) ? wholePart.toString() : `${wholePart}.${fractionalPart.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
936
+ balances.push({
937
+ jettonAddress: jetton.address,
938
+ walletAddress: wallet_address.address,
939
+ balance,
940
+ balanceFormatted,
941
+ symbol: jetton.symbol || "UNKNOWN",
942
+ name: jetton.name || "Unknown Token",
943
+ decimals,
944
+ verified: jetton.verification === "whitelist",
945
+ usdPrice: item.price?.prices?.USD ? Number(item.price.prices.USD) : void 0
946
+ });
947
+ }
948
+ return balances;
949
+ } catch (err) {
950
+ log7.error("ton.getJettonBalances() failed:", err);
951
+ return [];
952
+ }
953
+ },
954
+ async getJettonInfo(jettonAddress) {
955
+ try {
956
+ const response = await tonapiFetch(`/jettons/${jettonAddress}`);
957
+ if (response.status === 404) return null;
958
+ if (!response.ok) {
959
+ log7.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
960
+ return null;
961
+ }
962
+ const data = await response.json();
963
+ const metadata = data.metadata || {};
964
+ const decimals = parseInt(metadata.decimals || "9");
965
+ return {
966
+ address: metadata.address || jettonAddress,
967
+ name: metadata.name || "Unknown",
968
+ symbol: metadata.symbol || "UNKNOWN",
969
+ decimals,
970
+ totalSupply: data.total_supply || "0",
971
+ holdersCount: data.holders_count || 0,
972
+ verified: data.verification === "whitelist",
973
+ description: metadata.description || void 0,
974
+ image: data.preview || metadata.image || void 0
975
+ };
976
+ } catch (err) {
977
+ log7.error("ton.getJettonInfo() failed:", err);
978
+ return null;
979
+ }
980
+ },
981
+ async sendJetton(jettonAddress, to, amount, opts) {
982
+ const { Address: Address2, beginCell, SendMode: SendMode2 } = await import("@ton/core");
983
+ const { WalletContractV5R1: WalletContractV5R12, TonClient: TonClient2, toNano: toNano2, internal: internal2 } = await import("@ton/ton");
984
+ const { getCachedHttpEndpoint: getCachedHttpEndpoint2 } = await import("./endpoint-FLYNEZ2F.js");
985
+ const walletData = loadWallet();
986
+ if (!walletData) {
987
+ throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
988
+ }
989
+ if (!Number.isFinite(amount) || amount <= 0) {
990
+ throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
991
+ }
992
+ try {
993
+ Address2.parse(to);
994
+ } catch {
995
+ throw new PluginSDKError("Invalid recipient address", "INVALID_ADDRESS");
996
+ }
997
+ const jettonsResponse = await tonapiFetch(`/accounts/${walletData.address}/jettons`);
998
+ if (!jettonsResponse.ok) {
999
+ throw new PluginSDKError(
1000
+ `Failed to fetch jetton balances: ${jettonsResponse.status}`,
1001
+ "OPERATION_FAILED"
1002
+ );
1003
+ }
1004
+ const jettonsData = await jettonsResponse.json();
1005
+ const jettonBalance = jettonsData.balances?.find(
1006
+ (b) => b.jetton.address.toLowerCase() === jettonAddress.toLowerCase() || Address2.parse(b.jetton.address).toString() === Address2.parse(jettonAddress).toString()
1007
+ );
1008
+ if (!jettonBalance) {
1009
+ throw new PluginSDKError(
1010
+ `You don't own any of this jetton: ${jettonAddress}`,
1011
+ "OPERATION_FAILED"
1012
+ );
1013
+ }
1014
+ const senderJettonWallet = jettonBalance.wallet_address.address;
1015
+ const decimals = jettonBalance.jetton.decimals || 9;
1016
+ const currentBalance = BigInt(jettonBalance.balance);
1017
+ const amountStr = amount.toFixed(decimals);
1018
+ const [whole, frac = ""] = amountStr.split(".");
1019
+ const amountInUnits = BigInt(whole + (frac + "0".repeat(decimals)).slice(0, decimals));
1020
+ if (amountInUnits > currentBalance) {
1021
+ throw new PluginSDKError(
1022
+ `Insufficient balance. Have ${Number(currentBalance) / 10 ** decimals}, need ${amount}`,
1023
+ "OPERATION_FAILED"
1024
+ );
1025
+ }
1026
+ const comment = opts?.comment;
1027
+ let forwardPayload = beginCell().endCell();
1028
+ if (comment) {
1029
+ forwardPayload = beginCell().storeUint(0, 32).storeStringTail(comment).endCell();
1030
+ }
1031
+ const JETTON_TRANSFER_OP = 260734629;
1032
+ const messageBody = beginCell().storeUint(JETTON_TRANSFER_OP, 32).storeUint(0, 64).storeCoins(amountInUnits).storeAddress(Address2.parse(to)).storeAddress(Address2.parse(walletData.address)).storeBit(false).storeCoins(comment ? toNano2("0.01") : BigInt(1)).storeBit(comment ? true : false).storeMaybeRef(comment ? forwardPayload : null).endCell();
1033
+ const keyPair = await getKeyPair();
1034
+ if (!keyPair) {
1035
+ throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
1036
+ }
1037
+ const wallet = WalletContractV5R12.create({
1038
+ workchain: 0,
1039
+ publicKey: keyPair.publicKey
1040
+ });
1041
+ const endpoint = await getCachedHttpEndpoint2();
1042
+ const client = new TonClient2({ endpoint });
1043
+ const walletContract = client.open(wallet);
1044
+ const seqno = await walletContract.getSeqno();
1045
+ await walletContract.sendTransfer({
1046
+ seqno,
1047
+ secretKey: keyPair.secretKey,
1048
+ sendMode: SendMode2.PAY_GAS_SEPARATELY + SendMode2.IGNORE_ERRORS,
1049
+ messages: [
1050
+ internal2({
1051
+ to: Address2.parse(senderJettonWallet),
1052
+ value: toNano2("0.05"),
1053
+ body: messageBody,
1054
+ bounce: true
1055
+ })
1056
+ ]
1057
+ });
1058
+ return { success: true, seqno };
1059
+ },
1060
+ async getJettonWalletAddress(ownerAddress, jettonAddress) {
1061
+ try {
1062
+ const response = await tonapiFetch(`/accounts/${ownerAddress}/jettons`);
1063
+ if (!response.ok) {
1064
+ log7.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
1065
+ return null;
1066
+ }
1067
+ const { Address: Address2 } = await import("@ton/core");
1068
+ const data = await response.json();
1069
+ const match = (data.balances || []).find(
1070
+ (b) => b.jetton.address.toLowerCase() === jettonAddress.toLowerCase() || Address2.parse(b.jetton.address).toString() === Address2.parse(jettonAddress).toString()
1071
+ );
1072
+ return match ? match.wallet_address.address : null;
1073
+ } catch (err) {
1074
+ log7.error("ton.getJettonWalletAddress() failed:", err);
1075
+ return null;
1076
+ }
1077
+ },
1078
+ // ─── NFT ─────────────────────────────────────────────────────
1079
+ async getNftItems(ownerAddress) {
1080
+ try {
1081
+ const addr = ownerAddress ?? getWalletAddress();
1082
+ if (!addr) return [];
1083
+ const response = await tonapiFetch(
1084
+ `/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
1085
+ );
1086
+ if (!response.ok) {
1087
+ log7.error(`ton.getNftItems() TonAPI error: ${response.status}`);
1088
+ return [];
1089
+ }
1090
+ const data = await response.json();
1091
+ if (!Array.isArray(data.nft_items)) return [];
1092
+ return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
1093
+ } catch (err) {
1094
+ log7.error("ton.getNftItems() failed:", err);
1095
+ return [];
1096
+ }
1097
+ },
1098
+ async getNftInfo(nftAddress) {
1099
+ try {
1100
+ const response = await tonapiFetch(`/nfts/${nftAddress}`);
1101
+ if (response.status === 404) return null;
1102
+ if (!response.ok) {
1103
+ log7.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
1104
+ return null;
1105
+ }
1106
+ const item = await response.json();
1107
+ return mapNftItem(item);
1108
+ } catch (err) {
1109
+ log7.error("ton.getNftInfo() failed:", err);
1110
+ return null;
1111
+ }
1112
+ },
1113
+ // ─── Utilities ───────────────────────────────────────────────
1114
+ toNano(amount) {
1115
+ try {
1116
+ const { toNano: convert } = __require("@ton/ton");
1117
+ return convert(String(amount));
1118
+ } catch (err) {
1119
+ throw new PluginSDKError(
1120
+ `toNano conversion failed: ${err instanceof Error ? err.message : String(err)}`,
1121
+ "OPERATION_FAILED"
1122
+ );
1123
+ }
1124
+ },
1125
+ fromNano(nano) {
1126
+ const { fromNano: convert } = __require("@ton/ton");
1127
+ return convert(nano);
1128
+ },
1129
+ validateAddress(address) {
1130
+ try {
1131
+ const { Address: Address2 } = __require("@ton/core");
1132
+ Address2.parse(address);
1133
+ return true;
1134
+ } catch {
1135
+ return false;
1136
+ }
1137
+ }
1138
+ };
1139
+ }
1140
+ function mapNftItem(item) {
1141
+ const meta = item.metadata || {};
1142
+ const coll = item.collection || {};
1143
+ const previews = item.previews || [];
1144
+ const preview = previews.length > 1 && previews[1].url || previews.length > 0 && previews[0].url || void 0;
1145
+ return {
1146
+ address: item.address,
1147
+ index: item.index ?? 0,
1148
+ ownerAddress: item.owner?.address || void 0,
1149
+ collectionAddress: coll.address || void 0,
1150
+ collectionName: coll.name || void 0,
1151
+ name: meta.name || void 0,
1152
+ description: meta.description ? meta.description.slice(0, 200) : void 0,
1153
+ image: preview || meta.image || void 0,
1154
+ verified: item.trust === "whitelist"
1155
+ };
1156
+ }
1157
+
1158
+ // src/utils/gramjs-bigint.ts
1159
+ var import_big_integer = __toESM(require_BigInteger(), 1);
1160
+ import { randomBytes } from "crypto";
1161
+ function toLong(value) {
1162
+ return (0, import_big_integer.default)(String(value));
1163
+ }
1164
+ function randomLong() {
1165
+ return toLong(randomBytes(8).readBigUInt64BE());
1166
+ }
1167
+
1168
+ // src/sdk/telegram-messages.ts
1169
+ function createTelegramMessagesSDK(bridge, log7) {
1170
+ function requireBridge() {
1171
+ if (!bridge.isAvailable()) {
1172
+ throw new PluginSDKError(
1173
+ "Telegram bridge not connected. SDK telegram methods can only be called at runtime (inside tool executors or start()), not during plugin loading.",
1174
+ "BRIDGE_NOT_CONNECTED"
1175
+ );
1176
+ }
1177
+ }
1178
+ function getClient() {
1179
+ return bridge.getClient().getClient();
1180
+ }
1181
+ function toSimpleMessage(msg) {
1182
+ return {
1183
+ id: msg.id,
1184
+ text: msg.message ?? "",
1185
+ senderId: Number(msg.fromId?.userId ?? msg.fromId?.channelId ?? 0),
1186
+ timestamp: new Date(msg.date * 1e3)
1187
+ };
1188
+ }
1189
+ return {
1190
+ // ─── Messages ──────────────────────────────────────────────
1191
+ async deleteMessage(chatId, messageId, revoke = true) {
1192
+ requireBridge();
1193
+ try {
1194
+ const gramJsClient = getClient();
1195
+ const { Api } = await import("telegram");
1196
+ const isChannel = chatId.startsWith("-100");
1197
+ if (isChannel) {
1198
+ const channel = await gramJsClient.getEntity(chatId);
1199
+ await gramJsClient.invoke(
1200
+ new Api.channels.DeleteMessages({
1201
+ channel,
1202
+ id: [messageId]
1203
+ })
1204
+ );
1205
+ } else {
1206
+ await gramJsClient.invoke(
1207
+ new Api.messages.DeleteMessages({
1208
+ id: [messageId],
1209
+ revoke
1210
+ })
1211
+ );
1212
+ }
1213
+ } catch (err) {
1214
+ if (err instanceof PluginSDKError) throw err;
1215
+ throw new PluginSDKError(
1216
+ `Failed to delete message: ${err instanceof Error ? err.message : String(err)}`,
1217
+ "OPERATION_FAILED"
1218
+ );
1219
+ }
1220
+ },
1221
+ async forwardMessage(fromChatId, toChatId, messageId) {
1222
+ requireBridge();
1223
+ try {
1224
+ const gramJsClient = getClient();
1225
+ const { Api } = await import("telegram");
1226
+ const result = await gramJsClient.invoke(
1227
+ new Api.messages.ForwardMessages({
1228
+ fromPeer: fromChatId,
1229
+ toPeer: toChatId,
1230
+ id: [messageId],
1231
+ randomId: [randomLong()]
1232
+ })
1233
+ );
1234
+ if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
1235
+ for (const update of result.updates) {
1236
+ if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
1237
+ return update.message.id;
1238
+ }
1239
+ }
1240
+ }
1241
+ return 0;
1242
+ } catch (err) {
1243
+ if (err instanceof PluginSDKError) throw err;
1244
+ throw new PluginSDKError(
1245
+ `Failed to forward message: ${err instanceof Error ? err.message : String(err)}`,
1246
+ "OPERATION_FAILED"
1247
+ );
1248
+ }
1249
+ },
1250
+ async pinMessage(chatId, messageId, opts) {
1251
+ requireBridge();
1252
+ try {
1253
+ const gramJsClient = getClient();
1254
+ const { Api } = await import("telegram");
1255
+ await gramJsClient.invoke(
1256
+ new Api.messages.UpdatePinnedMessage({
1257
+ peer: chatId,
1258
+ id: messageId,
1259
+ silent: opts?.silent,
1260
+ unpin: opts?.unpin
1261
+ })
1262
+ );
1263
+ } catch (err) {
1264
+ if (err instanceof PluginSDKError) throw err;
1265
+ throw new PluginSDKError(
1266
+ `Failed to ${opts?.unpin ? "unpin" : "pin"} message: ${err instanceof Error ? err.message : String(err)}`,
1267
+ "OPERATION_FAILED"
1268
+ );
1269
+ }
1270
+ },
1271
+ async searchMessages(chatId, query, limit = 20) {
1272
+ requireBridge();
1273
+ try {
1274
+ const gramJsClient = getClient();
1275
+ const { Api } = await import("telegram");
1276
+ const entity = await gramJsClient.getEntity(chatId);
1277
+ const result = await gramJsClient.invoke(
1278
+ new Api.messages.Search({
1279
+ peer: entity,
1280
+ q: query,
1281
+ filter: new Api.InputMessagesFilterEmpty(),
1282
+ limit
1283
+ })
1284
+ );
1285
+ const resultData = result;
1286
+ return (resultData.messages ?? []).map(toSimpleMessage);
1287
+ } catch (err) {
1288
+ if (err instanceof PluginSDKError) throw err;
1289
+ log7.error("telegram.searchMessages() failed:", err);
1290
+ return [];
1291
+ }
1292
+ },
1293
+ async scheduleMessage(chatId, text, scheduleDate) {
1294
+ requireBridge();
1295
+ try {
1296
+ const gramJsClient = getClient();
1297
+ const result = await gramJsClient.sendMessage(chatId, {
1298
+ message: text,
1299
+ schedule: scheduleDate
1300
+ });
1301
+ return result.id ?? 0;
1302
+ } catch (err) {
1303
+ if (err instanceof PluginSDKError) throw err;
1304
+ throw new PluginSDKError(
1305
+ `Failed to schedule message: ${err instanceof Error ? err.message : String(err)}`,
1306
+ "OPERATION_FAILED"
1307
+ );
1308
+ }
1309
+ },
1310
+ async getReplies(chatId, messageId, limit = 50) {
1311
+ requireBridge();
1312
+ try {
1313
+ const gramJsClient = getClient();
1314
+ const { Api } = await import("telegram");
1315
+ const bigInt2 = (await import("./BigInteger-DQ33LTTE.js")).default;
1316
+ const peer = await gramJsClient.getInputEntity(chatId);
1317
+ const result = await gramJsClient.invoke(
1318
+ new Api.messages.GetReplies({
1319
+ peer,
1320
+ msgId: messageId,
1321
+ offsetId: 0,
1322
+ offsetDate: 0,
1323
+ addOffset: 0,
1324
+ limit,
1325
+ maxId: 0,
1326
+ minId: 0,
1327
+ hash: bigInt2(0)
1328
+ })
1329
+ );
1330
+ const messages = [];
1331
+ if ("messages" in result) {
1332
+ for (const msg of result.messages) {
1333
+ if (msg.className === "Message") {
1334
+ messages.push(toSimpleMessage(msg));
1335
+ }
1336
+ }
1337
+ }
1338
+ messages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
1339
+ return messages;
1340
+ } catch (err) {
1341
+ if (err instanceof PluginSDKError) throw err;
1342
+ throw new PluginSDKError(
1343
+ `Failed to get replies: ${err instanceof Error ? err.message : String(err)}`,
1344
+ "OPERATION_FAILED"
1345
+ );
1346
+ }
1347
+ },
1348
+ // ─── Media ─────────────────────────────────────────────────
1349
+ async sendPhoto(chatId, photo, opts) {
1350
+ requireBridge();
1351
+ try {
1352
+ const gramJsClient = getClient();
1353
+ const result = await gramJsClient.sendFile(chatId, {
1354
+ file: photo,
1355
+ caption: opts?.caption,
1356
+ replyTo: opts?.replyToId
1357
+ });
1358
+ return result.id;
1359
+ } catch (err) {
1360
+ if (err instanceof PluginSDKError) throw err;
1361
+ throw new PluginSDKError(
1362
+ `Failed to send photo: ${err instanceof Error ? err.message : String(err)}`,
1363
+ "OPERATION_FAILED"
1364
+ );
1365
+ }
1366
+ },
1367
+ async sendVideo(chatId, video, opts) {
1368
+ requireBridge();
1369
+ try {
1370
+ const gramJsClient = getClient();
1371
+ const { Api } = await import("telegram");
1372
+ const result = await gramJsClient.sendFile(chatId, {
1373
+ file: video,
1374
+ caption: opts?.caption,
1375
+ replyTo: opts?.replyToId,
1376
+ forceDocument: false,
1377
+ attributes: [
1378
+ new Api.DocumentAttributeVideo({
1379
+ roundMessage: false,
1380
+ supportsStreaming: true,
1381
+ duration: 0,
1382
+ w: 0,
1383
+ h: 0
1384
+ })
1385
+ ]
1386
+ });
1387
+ return result.id;
1388
+ } catch (err) {
1389
+ if (err instanceof PluginSDKError) throw err;
1390
+ throw new PluginSDKError(
1391
+ `Failed to send video: ${err instanceof Error ? err.message : String(err)}`,
1392
+ "OPERATION_FAILED"
1393
+ );
1394
+ }
1395
+ },
1396
+ async sendVoice(chatId, voice, opts) {
1397
+ requireBridge();
1398
+ try {
1399
+ const gramJsClient = getClient();
1400
+ const { Api } = await import("telegram");
1401
+ const result = await gramJsClient.sendFile(chatId, {
1402
+ file: voice,
1403
+ caption: opts?.caption,
1404
+ replyTo: opts?.replyToId,
1405
+ attributes: [new Api.DocumentAttributeAudio({ voice: true, duration: 0 })]
1406
+ });
1407
+ return result.id;
1408
+ } catch (err) {
1409
+ if (err instanceof PluginSDKError) throw err;
1410
+ throw new PluginSDKError(
1411
+ `Failed to send voice: ${err instanceof Error ? err.message : String(err)}`,
1412
+ "OPERATION_FAILED"
1413
+ );
1414
+ }
1415
+ },
1416
+ async sendFile(chatId, file, opts) {
1417
+ requireBridge();
1418
+ try {
1419
+ const gramJsClient = getClient();
1420
+ const { Api } = await import("telegram");
1421
+ const attributes = [];
1422
+ if (opts?.fileName) {
1423
+ attributes.push(new Api.DocumentAttributeFilename({ fileName: opts.fileName }));
1424
+ }
1425
+ const result = await gramJsClient.sendFile(chatId, {
1426
+ file,
1427
+ caption: opts?.caption,
1428
+ replyTo: opts?.replyToId,
1429
+ forceDocument: true,
1430
+ attributes: attributes.length > 0 ? attributes : void 0
1431
+ });
1432
+ return result.id;
1433
+ } catch (err) {
1434
+ if (err instanceof PluginSDKError) throw err;
1435
+ throw new PluginSDKError(
1436
+ `Failed to send file: ${err instanceof Error ? err.message : String(err)}`,
1437
+ "OPERATION_FAILED"
1438
+ );
1439
+ }
1440
+ },
1441
+ async sendGif(chatId, gif, opts) {
1442
+ requireBridge();
1443
+ try {
1444
+ const gramJsClient = getClient();
1445
+ const { Api } = await import("telegram");
1446
+ const result = await gramJsClient.sendFile(chatId, {
1447
+ file: gif,
1448
+ caption: opts?.caption,
1449
+ replyTo: opts?.replyToId,
1450
+ attributes: [new Api.DocumentAttributeAnimated()]
1451
+ });
1452
+ return result.id;
1453
+ } catch (err) {
1454
+ if (err instanceof PluginSDKError) throw err;
1455
+ throw new PluginSDKError(
1456
+ `Failed to send GIF: ${err instanceof Error ? err.message : String(err)}`,
1457
+ "OPERATION_FAILED"
1458
+ );
1459
+ }
1460
+ },
1461
+ async sendSticker(chatId, sticker) {
1462
+ requireBridge();
1463
+ try {
1464
+ const gramJsClient = getClient();
1465
+ const result = await gramJsClient.sendFile(chatId, {
1466
+ file: sticker
1467
+ });
1468
+ return result.id;
1469
+ } catch (err) {
1470
+ if (err instanceof PluginSDKError) throw err;
1471
+ throw new PluginSDKError(
1472
+ `Failed to send sticker: ${err instanceof Error ? err.message : String(err)}`,
1473
+ "OPERATION_FAILED"
1474
+ );
1475
+ }
1476
+ },
1477
+ async downloadMedia(chatId, messageId) {
1478
+ requireBridge();
1479
+ try {
1480
+ const gramJsClient = getClient();
1481
+ const messages = await gramJsClient.getMessages(chatId, {
1482
+ ids: [messageId]
1483
+ });
1484
+ if (!messages || messages.length === 0 || !messages[0].media) {
1485
+ return null;
1486
+ }
1487
+ const buffer = await gramJsClient.downloadMedia(messages[0], {});
1488
+ return buffer ? Buffer.from(buffer) : null;
1489
+ } catch (err) {
1490
+ if (err instanceof PluginSDKError) throw err;
1491
+ throw new PluginSDKError(
1492
+ `Failed to download media: ${err instanceof Error ? err.message : String(err)}`,
1493
+ "OPERATION_FAILED"
1494
+ );
1495
+ }
1496
+ },
1497
+ // ─── Advanced ──────────────────────────────────────────────
1498
+ async setTyping(chatId) {
1499
+ requireBridge();
1500
+ try {
1501
+ await bridge.setTyping(chatId);
1502
+ } catch (err) {
1503
+ if (err instanceof PluginSDKError) throw err;
1504
+ throw new PluginSDKError(
1505
+ `Failed to set typing: ${err instanceof Error ? err.message : String(err)}`,
1506
+ "OPERATION_FAILED"
1507
+ );
1508
+ }
1509
+ }
1510
+ };
1511
+ }
1512
+
1513
+ // src/sdk/telegram-social.ts
1514
+ function createTelegramSocialSDK(bridge, log7) {
1515
+ function requireBridge() {
1516
+ if (!bridge.isAvailable()) {
1517
+ throw new PluginSDKError(
1518
+ "Telegram bridge not connected. SDK telegram methods can only be called at runtime (inside tool executors or start()), not during plugin loading.",
1519
+ "BRIDGE_NOT_CONNECTED"
1520
+ );
1521
+ }
1522
+ }
1523
+ function getClient() {
1524
+ return bridge.getClient().getClient();
1525
+ }
1526
+ return {
1527
+ // ─── Chat & Users ─────────────────────────────────────────
1528
+ async getChatInfo(chatId) {
1529
+ requireBridge();
1530
+ try {
1531
+ const client = getClient();
1532
+ const { Api } = await import("telegram");
1533
+ let entity;
1534
+ try {
1535
+ entity = await client.getEntity(chatId);
1536
+ } catch {
1537
+ return null;
1538
+ }
1539
+ const isChannel = entity.className === "Channel" || entity.className === "ChannelForbidden";
1540
+ const isChat = entity.className === "Chat" || entity.className === "ChatForbidden";
1541
+ const isUser = entity.className === "User";
1542
+ if (isUser) {
1543
+ const user = entity;
1544
+ return {
1545
+ id: user.id?.toString() || chatId,
1546
+ title: [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown",
1547
+ type: "private",
1548
+ username: user.username || void 0
1549
+ };
1550
+ }
1551
+ if (isChannel) {
1552
+ const channel = entity;
1553
+ let description;
1554
+ let membersCount;
1555
+ try {
1556
+ const fullChannel = await client.invoke(new Api.channels.GetFullChannel({ channel }));
1557
+ const fullChat = fullChannel.fullChat;
1558
+ description = fullChat.about || void 0;
1559
+ membersCount = fullChat.participantsCount || void 0;
1560
+ } catch {
1561
+ }
1562
+ const type = channel.megagroup ? "supergroup" : channel.broadcast ? "channel" : "group";
1563
+ return {
1564
+ id: channel.id?.toString() || chatId,
1565
+ title: channel.title || "Unknown",
1566
+ type,
1567
+ username: channel.username || void 0,
1568
+ description,
1569
+ membersCount
1570
+ };
1571
+ }
1572
+ if (isChat) {
1573
+ const chat = entity;
1574
+ let description;
1575
+ try {
1576
+ const fullChatResult = await client.invoke(
1577
+ new Api.messages.GetFullChat({ chatId: chat.id })
1578
+ );
1579
+ const fullChat = fullChatResult.fullChat;
1580
+ description = fullChat.about || void 0;
1581
+ } catch {
1582
+ }
1583
+ return {
1584
+ id: chat.id?.toString() || chatId,
1585
+ title: chat.title || "Unknown",
1586
+ type: "group",
1587
+ description,
1588
+ membersCount: chat.participantsCount || void 0
1589
+ };
1590
+ }
1591
+ return null;
1592
+ } catch (err) {
1593
+ if (err instanceof PluginSDKError) throw err;
1594
+ log7.error("telegram.getChatInfo() failed:", err);
1595
+ return null;
1596
+ }
1597
+ },
1598
+ async getUserInfo(userId) {
1599
+ requireBridge();
1600
+ try {
1601
+ const client = getClient();
1602
+ const { Api } = await import("telegram");
1603
+ let entity;
1604
+ try {
1605
+ const id = typeof userId === "string" ? userId.replace("@", "") : userId.toString();
1606
+ entity = await client.getEntity(id);
1607
+ } catch {
1608
+ return null;
1609
+ }
1610
+ if (entity.className !== "User") return null;
1611
+ const user = entity;
1612
+ return {
1613
+ id: Number(user.id),
1614
+ firstName: user.firstName || "",
1615
+ lastName: user.lastName || void 0,
1616
+ username: user.username || void 0,
1617
+ isBot: user.bot || false
1618
+ };
1619
+ } catch (err) {
1620
+ if (err instanceof PluginSDKError) throw err;
1621
+ throw new PluginSDKError(
1622
+ `Failed to get user info: ${err instanceof Error ? err.message : String(err)}`,
1623
+ "OPERATION_FAILED"
1624
+ );
1625
+ }
1626
+ },
1627
+ async resolveUsername(username) {
1628
+ requireBridge();
1629
+ try {
1630
+ const client = getClient();
1631
+ const { Api } = await import("telegram");
1632
+ const cleanUsername = username.replace("@", "").toLowerCase();
1633
+ if (!cleanUsername) return null;
1634
+ let result;
1635
+ try {
1636
+ result = await client.invoke(
1637
+ new Api.contacts.ResolveUsername({ username: cleanUsername })
1638
+ );
1639
+ } catch (err) {
1640
+ if (err.message?.includes("USERNAME_NOT_OCCUPIED") || err.errorMessage === "USERNAME_NOT_OCCUPIED") {
1641
+ return null;
1642
+ }
1643
+ throw err;
1644
+ }
1645
+ if (result.users && result.users.length > 0) {
1646
+ const user = result.users[0];
1647
+ if (user instanceof Api.User) {
1648
+ return {
1649
+ id: Number(user.id),
1650
+ type: "user",
1651
+ username: user.username || void 0,
1652
+ title: user.firstName || void 0
1653
+ };
1654
+ }
1655
+ }
1656
+ if (result.chats && result.chats.length > 0) {
1657
+ const chat = result.chats[0];
1658
+ const type = chat.className === "Channel" ? "channel" : "chat";
1659
+ return {
1660
+ id: Number(chat.id),
1661
+ type,
1662
+ username: chat instanceof Api.Channel ? chat.username || void 0 : void 0,
1663
+ title: chat instanceof Api.Channel || chat instanceof Api.Chat ? chat.title || void 0 : void 0
1664
+ };
1665
+ }
1666
+ return null;
1667
+ } catch (err) {
1668
+ if (err instanceof PluginSDKError) throw err;
1669
+ throw new PluginSDKError(
1670
+ `Failed to resolve username: ${err instanceof Error ? err.message : String(err)}`,
1671
+ "OPERATION_FAILED"
1672
+ );
1673
+ }
1674
+ },
1675
+ async getParticipants(chatId, limit) {
1676
+ requireBridge();
1677
+ try {
1678
+ const client = getClient();
1679
+ const { Api } = await import("telegram");
1680
+ const entity = await client.getEntity(chatId);
1681
+ const result = await client.invoke(
1682
+ new Api.channels.GetParticipants({
1683
+ channel: entity,
1684
+ filter: new Api.ChannelParticipantsRecent(),
1685
+ offset: 0,
1686
+ limit: limit ?? 100,
1687
+ hash: toLong(0)
1688
+ })
1689
+ );
1690
+ const resultData = result;
1691
+ return (resultData.users || []).map((user) => {
1692
+ const u = user;
1693
+ return {
1694
+ id: Number(u.id),
1695
+ firstName: u.firstName || "",
1696
+ lastName: u.lastName || void 0,
1697
+ username: u.username || void 0,
1698
+ isBot: u.bot || false
1699
+ };
1700
+ });
1701
+ } catch (err) {
1702
+ if (err instanceof PluginSDKError) throw err;
1703
+ log7.error("telegram.getParticipants() failed:", err);
1704
+ return [];
1705
+ }
1706
+ },
1707
+ // ─── Interactive ──────────────────────────────────────────
1708
+ async createPoll(chatId, question, answers, opts) {
1709
+ requireBridge();
1710
+ if (!answers || answers.length < 2) {
1711
+ throw new PluginSDKError("Poll must have at least 2 answers", "OPERATION_FAILED");
1712
+ }
1713
+ if (answers.length > 10) {
1714
+ throw new PluginSDKError("Poll cannot have more than 10 answers", "OPERATION_FAILED");
1715
+ }
1716
+ try {
1717
+ const client = getClient();
1718
+ const { Api } = await import("telegram");
1719
+ const anonymous = opts?.isAnonymous ?? true;
1720
+ const multipleChoice = opts?.multipleChoice ?? false;
1721
+ const poll = new Api.Poll({
1722
+ id: randomLong(),
1723
+ question: new Api.TextWithEntities({ text: question, entities: [] }),
1724
+ answers: answers.map(
1725
+ (opt, idx) => new Api.PollAnswer({
1726
+ text: new Api.TextWithEntities({ text: opt, entities: [] }),
1727
+ option: Buffer.from([idx])
1728
+ })
1729
+ ),
1730
+ publicVoters: !anonymous,
1731
+ multipleChoice
1732
+ });
1733
+ const result = await client.invoke(
1734
+ new Api.messages.SendMedia({
1735
+ peer: chatId,
1736
+ media: new Api.InputMediaPoll({ poll }),
1737
+ message: "",
1738
+ randomId: randomLong()
1739
+ })
1740
+ );
1741
+ if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
1742
+ for (const update of result.updates) {
1743
+ if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
1744
+ return update.message?.id ?? 0;
1745
+ }
1746
+ }
1747
+ }
1748
+ return 0;
1749
+ } catch (err) {
1750
+ if (err instanceof PluginSDKError) throw err;
1751
+ throw new PluginSDKError(
1752
+ `Failed to create poll: ${err instanceof Error ? err.message : String(err)}`,
1753
+ "OPERATION_FAILED"
1754
+ );
1755
+ }
1756
+ },
1757
+ async createQuiz(chatId, question, answers, correctIndex, explanation) {
1758
+ requireBridge();
1759
+ if (!answers || answers.length < 2) {
1760
+ throw new PluginSDKError("Quiz must have at least 2 answers", "OPERATION_FAILED");
1761
+ }
1762
+ if (answers.length > 10) {
1763
+ throw new PluginSDKError("Quiz cannot have more than 10 answers", "OPERATION_FAILED");
1764
+ }
1765
+ if (correctIndex < 0 || correctIndex >= answers.length) {
1766
+ throw new PluginSDKError(
1767
+ `correctIndex ${correctIndex} is out of bounds (0-${answers.length - 1})`,
1768
+ "OPERATION_FAILED"
1769
+ );
1770
+ }
1771
+ try {
1772
+ const client = getClient();
1773
+ const { Api } = await import("telegram");
1774
+ const poll = new Api.Poll({
1775
+ id: randomLong(),
1776
+ question: new Api.TextWithEntities({ text: question, entities: [] }),
1777
+ answers: answers.map(
1778
+ (opt, idx) => new Api.PollAnswer({
1779
+ text: new Api.TextWithEntities({ text: opt, entities: [] }),
1780
+ option: Buffer.from([idx])
1781
+ })
1782
+ ),
1783
+ quiz: true,
1784
+ publicVoters: false,
1785
+ multipleChoice: false
1786
+ });
1787
+ const result = await client.invoke(
1788
+ new Api.messages.SendMedia({
1789
+ peer: chatId,
1790
+ media: new Api.InputMediaPoll({
1791
+ poll,
1792
+ correctAnswers: [Buffer.from([correctIndex])],
1793
+ solution: explanation,
1794
+ solutionEntities: []
1795
+ }),
1796
+ message: "",
1797
+ randomId: randomLong()
1798
+ })
1799
+ );
1800
+ if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
1801
+ for (const update of result.updates) {
1802
+ if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
1803
+ return update.message?.id ?? 0;
1804
+ }
1805
+ }
1806
+ }
1807
+ return 0;
1808
+ } catch (err) {
1809
+ if (err instanceof PluginSDKError) throw err;
1810
+ throw new PluginSDKError(
1811
+ `Failed to create quiz: ${err instanceof Error ? err.message : String(err)}`,
1812
+ "OPERATION_FAILED"
1813
+ );
1814
+ }
1815
+ },
1816
+ // ─── Moderation ───────────────────────────────────────────
1817
+ async banUser(chatId, userId) {
1818
+ requireBridge();
1819
+ try {
1820
+ const client = getClient();
1821
+ const { Api } = await import("telegram");
1822
+ await client.invoke(
1823
+ new Api.channels.EditBanned({
1824
+ channel: chatId,
1825
+ participant: userId.toString(),
1826
+ bannedRights: new Api.ChatBannedRights({
1827
+ untilDate: 0,
1828
+ viewMessages: true,
1829
+ sendMessages: true,
1830
+ sendMedia: true,
1831
+ sendStickers: true,
1832
+ sendGifs: true,
1833
+ sendGames: true,
1834
+ sendInline: true,
1835
+ embedLinks: true
1836
+ })
1837
+ })
1838
+ );
1839
+ } catch (err) {
1840
+ if (err instanceof PluginSDKError) throw err;
1841
+ throw new PluginSDKError(
1842
+ `Failed to ban user: ${err instanceof Error ? err.message : String(err)}`,
1843
+ "OPERATION_FAILED"
1844
+ );
1845
+ }
1846
+ },
1847
+ async unbanUser(chatId, userId) {
1848
+ requireBridge();
1849
+ try {
1850
+ const client = getClient();
1851
+ const { Api } = await import("telegram");
1852
+ await client.invoke(
1853
+ new Api.channels.EditBanned({
1854
+ channel: chatId,
1855
+ participant: userId.toString(),
1856
+ bannedRights: new Api.ChatBannedRights({
1857
+ untilDate: 0
1858
+ })
1859
+ })
1860
+ );
1861
+ } catch (err) {
1862
+ if (err instanceof PluginSDKError) throw err;
1863
+ throw new PluginSDKError(
1864
+ `Failed to unban user: ${err instanceof Error ? err.message : String(err)}`,
1865
+ "OPERATION_FAILED"
1866
+ );
1867
+ }
1868
+ },
1869
+ async muteUser(chatId, userId, untilDate) {
1870
+ requireBridge();
1871
+ try {
1872
+ const client = getClient();
1873
+ const { Api } = await import("telegram");
1874
+ await client.invoke(
1875
+ new Api.channels.EditBanned({
1876
+ channel: chatId,
1877
+ participant: userId.toString(),
1878
+ bannedRights: new Api.ChatBannedRights({
1879
+ untilDate: untilDate ?? 0,
1880
+ sendMessages: true
1881
+ })
1882
+ })
1883
+ );
1884
+ } catch (err) {
1885
+ if (err instanceof PluginSDKError) throw err;
1886
+ throw new PluginSDKError(
1887
+ `Failed to mute user: ${err instanceof Error ? err.message : String(err)}`,
1888
+ "OPERATION_FAILED"
1889
+ );
1890
+ }
1891
+ },
1892
+ // ─── Stars & Gifts ────────────────────────────────────────
1893
+ async getStarsBalance() {
1894
+ requireBridge();
1895
+ try {
1896
+ const client = getClient();
1897
+ const { Api } = await import("telegram");
1898
+ const result = await client.invoke(
1899
+ new Api.payments.GetStarsStatus({
1900
+ peer: new Api.InputPeerSelf()
1901
+ })
1902
+ );
1903
+ return Number(result.balance?.amount?.toString() || "0");
1904
+ } catch (err) {
1905
+ if (err instanceof PluginSDKError) throw err;
1906
+ throw new PluginSDKError(
1907
+ `Failed to get stars balance: ${err instanceof Error ? err.message : String(err)}`,
1908
+ "OPERATION_FAILED"
1909
+ );
1910
+ }
1911
+ },
1912
+ async sendGift(userId, giftId, opts) {
1913
+ requireBridge();
1914
+ try {
1915
+ const client = getClient();
1916
+ const { Api } = await import("telegram");
1917
+ const user = await client.getEntity(userId.toString());
1918
+ const invoiceData = {
1919
+ peer: user,
1920
+ giftId: BigInt(giftId),
1921
+ hideName: opts?.anonymous ?? false,
1922
+ message: opts?.message ? new Api.TextWithEntities({ text: opts.message, entities: [] }) : void 0
1923
+ };
1924
+ const form = await client.invoke(
1925
+ new Api.payments.GetPaymentForm({
1926
+ invoice: new Api.InputInvoiceStarGift(invoiceData)
1927
+ })
1928
+ );
1929
+ await client.invoke(
1930
+ new Api.payments.SendStarsForm({
1931
+ formId: form.formId,
1932
+ invoice: new Api.InputInvoiceStarGift(invoiceData)
1933
+ })
1934
+ );
1935
+ } catch (err) {
1936
+ if (err instanceof PluginSDKError) throw err;
1937
+ throw new PluginSDKError(
1938
+ `Failed to send gift: ${err instanceof Error ? err.message : String(err)}`,
1939
+ "OPERATION_FAILED"
1940
+ );
1941
+ }
1942
+ },
1943
+ async getAvailableGifts() {
1944
+ requireBridge();
1945
+ try {
1946
+ const client = getClient();
1947
+ const { Api } = await import("telegram");
1948
+ const result = await client.invoke(new Api.payments.GetStarGifts({ hash: 0 }));
1949
+ if (result.className === "payments.StarGiftsNotModified") {
1950
+ return [];
1951
+ }
1952
+ return (result.gifts || []).filter((gift) => !gift.soldOut).map((gift) => ({
1953
+ id: gift.id?.toString(),
1954
+ starsAmount: Number(gift.stars?.toString() || "0"),
1955
+ availableAmount: gift.limited ? Number(gift.availabilityRemains?.toString() || "0") : void 0,
1956
+ totalAmount: gift.limited ? Number(gift.availabilityTotal?.toString() || "0") : void 0
1957
+ }));
1958
+ } catch (err) {
1959
+ if (err instanceof PluginSDKError) throw err;
1960
+ throw new PluginSDKError(
1961
+ `Failed to get available gifts: ${err instanceof Error ? err.message : String(err)}`,
1962
+ "OPERATION_FAILED"
1963
+ );
1964
+ }
1965
+ },
1966
+ async getMyGifts(limit) {
1967
+ requireBridge();
1968
+ try {
1969
+ const client = getClient();
1970
+ const { Api } = await import("telegram");
1971
+ const result = await client.invoke(
1972
+ new Api.payments.GetSavedStarGifts({
1973
+ peer: new Api.InputPeerSelf(),
1974
+ offset: "",
1975
+ limit: limit ?? 50
1976
+ })
1977
+ );
1978
+ return (result.gifts || []).map((savedGift) => {
1979
+ const gift = savedGift.gift;
1980
+ return {
1981
+ id: gift?.id?.toString() || "",
1982
+ fromId: savedGift.fromId ? Number(savedGift.fromId) : void 0,
1983
+ date: savedGift.date || 0,
1984
+ starsAmount: Number(gift?.stars?.toString() || "0"),
1985
+ saved: savedGift.unsaved !== true,
1986
+ messageId: savedGift.msgId || void 0
1987
+ };
1988
+ });
1989
+ } catch (err) {
1990
+ if (err instanceof PluginSDKError) throw err;
1991
+ throw new PluginSDKError(
1992
+ `Failed to get my gifts: ${err instanceof Error ? err.message : String(err)}`,
1993
+ "OPERATION_FAILED"
1994
+ );
1995
+ }
1996
+ },
1997
+ async getResaleGifts(limit) {
1998
+ requireBridge();
1999
+ try {
2000
+ const client = getClient();
2001
+ const { Api } = await import("telegram");
2002
+ if (!Api.payments.GetResaleStarGifts) {
2003
+ throw new PluginSDKError(
2004
+ "Resale gift marketplace is not supported in the current Telegram API layer.",
2005
+ "OPERATION_FAILED"
2006
+ );
2007
+ }
2008
+ const result = await client.invoke(
2009
+ new Api.payments.GetResaleStarGifts({
2010
+ offset: "",
2011
+ limit: limit ?? 50
2012
+ })
2013
+ );
2014
+ return (result.gifts || []).map((listing) => ({
2015
+ id: listing.odayId?.toString() || "",
2016
+ starsAmount: Number(listing.resellStars?.toString() || "0")
2017
+ }));
2018
+ } catch (err) {
2019
+ if (err instanceof PluginSDKError) throw err;
2020
+ throw new PluginSDKError(
2021
+ `Failed to get resale gifts: ${err instanceof Error ? err.message : String(err)}`,
2022
+ "OPERATION_FAILED"
2023
+ );
2024
+ }
2025
+ },
2026
+ async buyResaleGift(giftId) {
2027
+ requireBridge();
2028
+ try {
2029
+ const client = getClient();
2030
+ const { Api } = await import("telegram");
2031
+ if (!Api.InputInvoiceStarGiftResale) {
2032
+ throw new PluginSDKError(
2033
+ "Resale gift purchasing is not supported in the current Telegram API layer.",
2034
+ "OPERATION_FAILED"
2035
+ );
2036
+ }
2037
+ const stargiftInput = new Api.InputSavedStarGiftUser({
2038
+ odayId: BigInt(giftId)
2039
+ });
2040
+ const invoiceData = { stargift: stargiftInput };
2041
+ const form = await client.invoke(
2042
+ new Api.payments.GetPaymentForm({
2043
+ invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
2044
+ })
2045
+ );
2046
+ await client.invoke(
2047
+ new Api.payments.SendStarsForm({
2048
+ formId: form.formId,
2049
+ invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
2050
+ })
2051
+ );
2052
+ } catch (err) {
2053
+ if (err instanceof PluginSDKError) throw err;
2054
+ throw new PluginSDKError(
2055
+ `Failed to buy resale gift: ${err instanceof Error ? err.message : String(err)}`,
2056
+ "OPERATION_FAILED"
2057
+ );
2058
+ }
2059
+ },
2060
+ async sendStory(mediaPath, opts) {
2061
+ requireBridge();
2062
+ try {
2063
+ const client = getClient();
2064
+ const { Api, helpers } = await import("telegram");
2065
+ const { CustomFile } = await import("telegram/client/uploads.js");
2066
+ const { readFileSync: readFileSync7, statSync: statSync2 } = await import("fs");
2067
+ const { basename: basename2 } = await import("path");
2068
+ const { resolve: resolve2, normalize: normalize2 } = await import("path");
2069
+ const { homedir: homedir3 } = await import("os");
2070
+ const { realpathSync } = await import("fs");
2071
+ const filePath = realpathSync(resolve2(normalize2(mediaPath)));
2072
+ const home = homedir3();
2073
+ const teletonWorkspace = `${home}/.teleton/workspace/`;
2074
+ const allowedPrefixes = [
2075
+ "/tmp/",
2076
+ `${home}/Downloads/`,
2077
+ `${home}/Pictures/`,
2078
+ `${home}/Videos/`,
2079
+ `${teletonWorkspace}uploads/`,
2080
+ `${teletonWorkspace}downloads/`,
2081
+ `${teletonWorkspace}memes/`
2082
+ ];
2083
+ if (!allowedPrefixes.some((p) => filePath.startsWith(p))) {
2084
+ throw new PluginSDKError(
2085
+ "sendStory: media path must be within /tmp, Downloads, Pictures, or Videos",
2086
+ "OPERATION_FAILED"
2087
+ );
2088
+ }
2089
+ const fileName = basename2(filePath);
2090
+ const fileSize = statSync2(filePath).size;
2091
+ const fileBuffer = readFileSync7(filePath);
2092
+ const isVideo = filePath.toLowerCase().match(/\.(mp4|mov|avi)$/);
2093
+ const customFile = new CustomFile(fileName, fileSize, filePath, fileBuffer);
2094
+ const uploadedFile = await client.uploadFile({
2095
+ file: customFile,
2096
+ workers: 1
2097
+ });
2098
+ let inputMedia;
2099
+ if (isVideo) {
2100
+ inputMedia = new Api.InputMediaUploadedDocument({
2101
+ file: uploadedFile,
2102
+ mimeType: "video/mp4",
2103
+ attributes: [
2104
+ new Api.DocumentAttributeVideo({
2105
+ duration: 0,
2106
+ w: 720,
2107
+ h: 1280,
2108
+ supportsStreaming: true
2109
+ }),
2110
+ new Api.DocumentAttributeFilename({ fileName })
2111
+ ]
2112
+ });
2113
+ } else {
2114
+ inputMedia = new Api.InputMediaUploadedPhoto({
2115
+ file: uploadedFile
2116
+ });
2117
+ }
2118
+ const privacyRules = [new Api.InputPrivacyValueAllowAll()];
2119
+ const result = await client.invoke(
2120
+ new Api.stories.SendStory({
2121
+ peer: "me",
2122
+ media: inputMedia,
2123
+ caption: opts?.caption || "",
2124
+ privacyRules,
2125
+ randomId: helpers.generateRandomBigInt()
2126
+ })
2127
+ );
2128
+ const storyUpdate = result instanceof Api.Updates ? result.updates.find((u) => u.className === "UpdateStory") : void 0;
2129
+ return storyUpdate?.story?.id ?? 0;
2130
+ } catch (err) {
2131
+ if (err instanceof PluginSDKError) throw err;
2132
+ throw new PluginSDKError(
2133
+ `Failed to send story: ${err instanceof Error ? err.message : String(err)}`,
2134
+ "OPERATION_FAILED"
2135
+ );
2136
+ }
2137
+ }
2138
+ };
2139
+ }
2140
+
2141
+ // src/sdk/telegram.ts
2142
+ function createTelegramSDK(bridge, log7) {
2143
+ function requireBridge() {
2144
+ if (!bridge.isAvailable()) {
2145
+ throw new PluginSDKError(
2146
+ "Telegram bridge not connected. SDK telegram methods can only be called at runtime (inside tool executors or start()), not during plugin loading.",
2147
+ "BRIDGE_NOT_CONNECTED"
2148
+ );
2149
+ }
2150
+ }
2151
+ return {
2152
+ async sendMessage(chatId, text, opts) {
2153
+ requireBridge();
2154
+ try {
2155
+ const msg = await bridge.sendMessage({
2156
+ chatId,
2157
+ text,
2158
+ replyToId: opts?.replyToId,
2159
+ inlineKeyboard: opts?.inlineKeyboard
2160
+ });
2161
+ return msg.id;
2162
+ } catch (err) {
2163
+ if (err instanceof PluginSDKError) throw err;
2164
+ throw new PluginSDKError(
2165
+ `Failed to send message: ${err instanceof Error ? err.message : String(err)}`,
2166
+ "OPERATION_FAILED"
2167
+ );
2168
+ }
2169
+ },
2170
+ async editMessage(chatId, messageId, text, opts) {
2171
+ requireBridge();
2172
+ try {
2173
+ const msg = await bridge.editMessage({
2174
+ chatId,
2175
+ messageId,
2176
+ text,
2177
+ inlineKeyboard: opts?.inlineKeyboard
2178
+ });
2179
+ return typeof msg?.id === "number" ? msg.id : messageId;
2180
+ } catch (err) {
2181
+ if (err instanceof PluginSDKError) throw err;
2182
+ throw new PluginSDKError(
2183
+ `Failed to edit message: ${err instanceof Error ? err.message : String(err)}`,
2184
+ "OPERATION_FAILED"
2185
+ );
2186
+ }
2187
+ },
2188
+ async sendDice(chatId, emoticon, replyToId) {
2189
+ requireBridge();
2190
+ try {
2191
+ const gramJsClient = bridge.getClient().getClient();
2192
+ const { Api } = await import("telegram");
2193
+ const result = await gramJsClient.invoke(
2194
+ new Api.messages.SendMedia({
2195
+ peer: chatId,
2196
+ media: new Api.InputMediaDice({ emoticon }),
2197
+ message: "",
2198
+ randomId: randomLong(),
2199
+ replyTo: replyToId ? new Api.InputReplyToMessage({ replyToMsgId: replyToId }) : void 0
2200
+ })
2201
+ );
2202
+ let value;
2203
+ let messageId;
2204
+ if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
2205
+ for (const update of result.updates) {
2206
+ if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
2207
+ const msg = update.message;
2208
+ if (msg instanceof Api.Message && msg.media?.className === "MessageMediaDice") {
2209
+ value = msg.media.value;
2210
+ messageId = msg.id;
2211
+ break;
2212
+ }
2213
+ }
2214
+ }
2215
+ }
2216
+ if (value === void 0 || messageId === void 0) {
2217
+ throw new Error("Could not extract dice value from Telegram response");
2218
+ }
2219
+ return { value, messageId };
2220
+ } catch (err) {
2221
+ if (err instanceof PluginSDKError) throw err;
2222
+ throw new PluginSDKError(
2223
+ `Failed to send dice: ${err instanceof Error ? err.message : String(err)}`,
2224
+ "OPERATION_FAILED"
2225
+ );
2226
+ }
2227
+ },
2228
+ async sendReaction(chatId, messageId, emoji) {
2229
+ requireBridge();
2230
+ try {
2231
+ await bridge.sendReaction(chatId, messageId, emoji);
2232
+ } catch (err) {
2233
+ if (err instanceof PluginSDKError) throw err;
2234
+ throw new PluginSDKError(
2235
+ `Failed to send reaction: ${err instanceof Error ? err.message : String(err)}`,
2236
+ "OPERATION_FAILED"
2237
+ );
2238
+ }
2239
+ },
2240
+ async getMessages(chatId, limit) {
2241
+ requireBridge();
2242
+ try {
2243
+ const messages = await bridge.getMessages(chatId, limit ?? 50);
2244
+ return messages.map((m) => ({
2245
+ id: m.id,
2246
+ text: m.text,
2247
+ senderId: m.senderId,
2248
+ senderUsername: m.senderUsername,
2249
+ timestamp: m.timestamp
2250
+ }));
2251
+ } catch (err) {
2252
+ log7.error("telegram.getMessages() failed:", err);
2253
+ return [];
2254
+ }
2255
+ },
2256
+ getMe() {
2257
+ try {
2258
+ const me = bridge.getClient()?.getMe?.();
2259
+ if (!me) return null;
2260
+ return {
2261
+ id: Number(me.id),
2262
+ username: me.username,
2263
+ firstName: me.firstName,
2264
+ isBot: me.isBot
2265
+ };
2266
+ } catch {
2267
+ return null;
2268
+ }
2269
+ },
2270
+ isAvailable() {
2271
+ return bridge.isAvailable();
2272
+ },
2273
+ getRawClient() {
2274
+ log7.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
2275
+ if (!bridge.isAvailable()) return null;
2276
+ try {
2277
+ return bridge.getClient().getClient();
2278
+ } catch {
2279
+ return null;
2280
+ }
2281
+ },
2282
+ // Spread extended methods from sub-modules
2283
+ ...createTelegramMessagesSDK(bridge, log7),
2284
+ ...createTelegramSocialSDK(bridge, log7)
2285
+ };
2286
+ }
2287
+
2288
+ // src/sdk/secrets.ts
2289
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
2290
+ import { join as join3 } from "path";
2291
+ var SECRETS_DIR = join3(TELETON_ROOT, "plugins", "data");
2292
+ function getSecretsPath(pluginName) {
2293
+ return join3(SECRETS_DIR, `${pluginName}.secrets.json`);
2294
+ }
2295
+ function readSecretsFile(pluginName) {
2296
+ const filePath = getSecretsPath(pluginName);
2297
+ try {
2298
+ if (!existsSync5(filePath)) return {};
2299
+ const raw = readFileSync4(filePath, "utf-8");
2300
+ const parsed = JSON.parse(raw);
2301
+ if (typeof parsed !== "object" || parsed === null) return {};
2302
+ return parsed;
2303
+ } catch {
2304
+ return {};
2305
+ }
2306
+ }
2307
+ function writePluginSecret(pluginName, key, value) {
2308
+ mkdirSync3(SECRETS_DIR, { recursive: true, mode: 448 });
2309
+ const filePath = getSecretsPath(pluginName);
2310
+ const existing = readSecretsFile(pluginName);
2311
+ existing[key] = value;
2312
+ writeFileSync2(filePath, JSON.stringify(existing, null, 2), { mode: 384 });
2313
+ }
2314
+ function deletePluginSecret(pluginName, key) {
2315
+ const existing = readSecretsFile(pluginName);
2316
+ if (!(key in existing)) return false;
2317
+ delete existing[key];
2318
+ const filePath = getSecretsPath(pluginName);
2319
+ writeFileSync2(filePath, JSON.stringify(existing, null, 2), { mode: 384 });
2320
+ return true;
2321
+ }
2322
+ function listPluginSecretKeys(pluginName) {
2323
+ return Object.keys(readSecretsFile(pluginName));
2324
+ }
2325
+ function createSecretsSDK(pluginName, pluginConfig, log7) {
2326
+ const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
2327
+ function get(key) {
2328
+ const envKey = `${envPrefix}_${key.toUpperCase()}`;
2329
+ const envValue = process.env[envKey];
2330
+ if (envValue) {
2331
+ log7.debug(`Secret "${key}" resolved from env var ${envKey}`);
2332
+ return envValue;
2333
+ }
2334
+ const stored = readSecretsFile(pluginName);
2335
+ if (key in stored && stored[key]) {
2336
+ log7.debug(`Secret "${key}" resolved from secrets store`);
2337
+ return stored[key];
2338
+ }
2339
+ const configValue = pluginConfig[key];
2340
+ if (configValue !== void 0 && configValue !== null) {
2341
+ log7.debug(`Secret "${key}" resolved from pluginConfig`);
2342
+ return String(configValue);
2343
+ }
2344
+ return void 0;
2345
+ }
2346
+ return {
2347
+ get,
2348
+ require(key) {
2349
+ const value = get(key);
2350
+ if (!value) {
2351
+ throw new PluginSDKError(
2352
+ `Missing required secret "${key}". Set it via: /plugin set ${pluginName} ${key} <value>`,
2353
+ "SECRET_NOT_FOUND"
2354
+ );
2355
+ }
2356
+ return value;
2357
+ },
2358
+ has(key) {
2359
+ return get(key) !== void 0;
2360
+ }
2361
+ };
2362
+ }
2363
+
2364
+ // src/sdk/storage.ts
2365
+ var KV_TABLE = "_kv";
2366
+ var CLEANUP_PROBABILITY2 = 0.05;
2367
+ function ensureTable(db) {
2368
+ db.exec(`
2369
+ CREATE TABLE IF NOT EXISTS ${KV_TABLE} (
2370
+ key TEXT PRIMARY KEY,
2371
+ value TEXT NOT NULL,
2372
+ expires_at INTEGER
2373
+ )
2374
+ `);
2375
+ }
2376
+ function createStorageSDK(db) {
2377
+ ensureTable(db);
2378
+ const stmtGet = db.prepare(`SELECT value, expires_at FROM ${KV_TABLE} WHERE key = ?`);
2379
+ const stmtSet = db.prepare(
2380
+ `INSERT OR REPLACE INTO ${KV_TABLE} (key, value, expires_at) VALUES (?, ?, ?)`
2381
+ );
2382
+ const stmtDel = db.prepare(`DELETE FROM ${KV_TABLE} WHERE key = ?`);
2383
+ const stmtHas = db.prepare(`SELECT 1 FROM ${KV_TABLE} WHERE key = ?`);
2384
+ const stmtClear = db.prepare(`DELETE FROM ${KV_TABLE}`);
2385
+ const stmtCleanup = db.prepare(
2386
+ `DELETE FROM ${KV_TABLE} WHERE expires_at IS NOT NULL AND expires_at < ?`
2387
+ );
2388
+ function maybeCleanup() {
2389
+ if (Math.random() < CLEANUP_PROBABILITY2) {
2390
+ stmtCleanup.run(Date.now());
2391
+ }
2392
+ }
2393
+ return {
2394
+ get(key) {
2395
+ maybeCleanup();
2396
+ const row = stmtGet.get(key);
2397
+ if (!row) return void 0;
2398
+ if (row.expires_at !== null && row.expires_at < Date.now()) {
2399
+ stmtDel.run(key);
2400
+ return void 0;
2401
+ }
2402
+ try {
2403
+ return JSON.parse(row.value);
2404
+ } catch {
2405
+ return row.value;
2406
+ }
2407
+ },
2408
+ set(key, value, opts) {
2409
+ const serialized = JSON.stringify(value);
2410
+ const expiresAt = opts?.ttl ? Date.now() + opts.ttl : null;
2411
+ stmtSet.run(key, serialized, expiresAt);
2412
+ },
2413
+ delete(key) {
2414
+ const result = stmtDel.run(key);
2415
+ return result.changes > 0;
2416
+ },
2417
+ has(key) {
2418
+ const row = stmtGet.get(key);
2419
+ if (!row) return false;
2420
+ if (row.expires_at !== null && row.expires_at < Date.now()) {
2421
+ stmtDel.run(key);
2422
+ return false;
2423
+ }
2424
+ return true;
2425
+ },
2426
+ clear() {
2427
+ stmtClear.run();
2428
+ }
2429
+ };
2430
+ }
2431
+
2432
+ // src/sdk/index.ts
2433
+ var sdkLog = createLogger("SDK");
2434
+ var BLOCKED_SQL_RE = /\b(ATTACH|DETACH)\s+DATABASE\b/i;
2435
+ function createSafeDb(db) {
2436
+ return new Proxy(db, {
2437
+ get(target, prop, receiver) {
2438
+ const value = Reflect.get(target, prop, receiver);
2439
+ if (prop === "exec") {
2440
+ return (sql) => {
2441
+ if (BLOCKED_SQL_RE.test(sql)) {
2442
+ throw new Error("ATTACH/DETACH DATABASE is not allowed in plugin context");
2443
+ }
2444
+ return target.exec(sql);
2445
+ };
2446
+ }
2447
+ if (prop === "prepare") {
2448
+ return (sql) => {
2449
+ if (BLOCKED_SQL_RE.test(sql)) {
2450
+ throw new Error("ATTACH/DETACH DATABASE is not allowed in plugin context");
2451
+ }
2452
+ return target.prepare(sql);
2453
+ };
2454
+ }
2455
+ return typeof value === "function" ? value.bind(target) : value;
2456
+ }
2457
+ });
2458
+ }
2459
+ function createPluginSDK(deps, opts) {
2460
+ const log7 = createLogger2(opts.pluginName);
2461
+ const safeDb = opts.db ? createSafeDb(opts.db) : null;
2462
+ const ton = Object.freeze(createTonSDK(log7, safeDb));
2463
+ const telegram = Object.freeze(createTelegramSDK(deps.bridge, log7));
2464
+ const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log7));
2465
+ const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
2466
+ const frozenLog = Object.freeze(log7);
2467
+ const frozenConfig = Object.freeze(opts.sanitizedConfig);
2468
+ const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
2469
+ return Object.freeze({
2470
+ version: SDK_VERSION,
2471
+ ton,
2472
+ telegram,
2473
+ secrets,
2474
+ storage,
2475
+ db: safeDb,
2476
+ config: frozenConfig,
2477
+ pluginConfig: frozenPluginConfig,
2478
+ log: frozenLog
2479
+ });
2480
+ }
2481
+ function createLogger2(pluginName) {
2482
+ const pinoChild = createLogger(`plugin:${pluginName}`);
2483
+ return {
2484
+ info: (...args) => pinoChild.info(args.map(String).join(" ")),
2485
+ warn: (...args) => pinoChild.warn(args.map(String).join(" ")),
2486
+ error: (...args) => pinoChild.error(args.map(String).join(" ")),
2487
+ debug: (...args) => pinoChild.debug(args.map(String).join(" "))
2488
+ };
2489
+ }
2490
+ function parseSemver(v) {
2491
+ const match = v.match(/(\d+)\.(\d+)\.(\d+)/);
2492
+ if (!match) return null;
2493
+ return {
2494
+ major: parseInt(match[1]),
2495
+ minor: parseInt(match[2]),
2496
+ patch: parseInt(match[3])
2497
+ };
2498
+ }
2499
+ function semverGte(a, b) {
2500
+ if (a.major !== b.major) return a.major > b.major;
2501
+ if (a.minor !== b.minor) return a.minor > b.minor;
2502
+ return a.patch >= b.patch;
2503
+ }
2504
+ function semverSatisfies(current, range) {
2505
+ const cur = parseSemver(current);
2506
+ if (!cur) {
2507
+ sdkLog.warn(`[SDK] Could not parse current version "${current}", rejecting`);
2508
+ return false;
2509
+ }
2510
+ if (range.startsWith(">=")) {
2511
+ const req2 = parseSemver(range.slice(2));
2512
+ if (!req2) {
2513
+ sdkLog.warn(`[SDK] Malformed sdkVersion range "${range}", rejecting`);
2514
+ return false;
2515
+ }
2516
+ return semverGte(cur, req2);
2517
+ }
2518
+ if (range.startsWith("^")) {
2519
+ const req2 = parseSemver(range.slice(1));
2520
+ if (!req2) {
2521
+ sdkLog.warn(`[SDK] Malformed sdkVersion range "${range}", rejecting`);
2522
+ return false;
2523
+ }
2524
+ if (req2.major === 0) {
2525
+ return cur.major === 0 && cur.minor === req2.minor && semverGte(cur, req2);
2526
+ }
2527
+ return cur.major === req2.major && semverGte(cur, req2);
2528
+ }
2529
+ const req = parseSemver(range);
2530
+ if (!req) {
2531
+ sdkLog.warn(`[SDK] Malformed sdkVersion "${range}", rejecting`);
2532
+ return false;
2533
+ }
2534
+ return cur.major === req.major && cur.minor === req.minor && cur.patch === req.patch;
2535
+ }
2536
+
2537
+ // src/agent/tools/plugin-loader.ts
2538
+ var execFileAsync = promisify(execFile);
2539
+ var log6 = createLogger("PluginLoader");
2540
+ var PLUGIN_DATA_DIR = join4(TELETON_ROOT, "plugins", "data");
2541
+ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2542
+ let manifest = null;
2543
+ if (raw.manifest) {
2544
+ try {
2545
+ manifest = validateManifest(raw.manifest);
2546
+ } catch (err) {
2547
+ log6.warn(
2548
+ `[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
2549
+ );
2550
+ }
2551
+ }
2552
+ if (!manifest) {
2553
+ const manifestPath = join4(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
2554
+ try {
2555
+ if (existsSync6(manifestPath)) {
2556
+ const diskManifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
2557
+ if (diskManifest && typeof diskManifest.version === "string") {
2558
+ manifest = {
2559
+ name: entryName,
2560
+ version: diskManifest.version,
2561
+ description: typeof diskManifest.description === "string" ? diskManifest.description : void 0,
2562
+ author: typeof diskManifest.author === "string" ? diskManifest.author : diskManifest.author?.name ?? void 0
2563
+ };
2564
+ }
2565
+ }
2566
+ } catch {
2567
+ }
2568
+ }
2569
+ const pluginName = manifest?.name ?? entryName.replace(/\.js$/, "");
2570
+ const pluginVersion = manifest?.version ?? "0.0.0";
2571
+ if (manifest?.dependencies) {
2572
+ for (const dep of manifest.dependencies) {
2573
+ if (!loadedModuleNames.includes(dep)) {
2574
+ throw new Error(`Plugin "${pluginName}" requires module "${dep}" which is not loaded`);
2575
+ }
2576
+ }
2577
+ }
2578
+ if (manifest?.sdkVersion) {
2579
+ if (!semverSatisfies(SDK_VERSION, manifest.sdkVersion)) {
2580
+ throw new Error(
2581
+ `Plugin "${pluginName}" requires SDK ${manifest.sdkVersion} but current SDK is ${SDK_VERSION}`
2582
+ );
2583
+ }
2584
+ }
2585
+ const pluginConfigKey = pluginName.replace(/-/g, "_");
2586
+ const rawPluginConfig = config.plugins?.[pluginConfigKey] ?? {};
2587
+ const pluginConfig = { ...manifest?.defaultConfig, ...rawPluginConfig };
2588
+ const pluginLog = createLogger(`Plugin:${pluginName}`);
2589
+ const logFn = (...args) => pluginLog.info(args.map(String).join(" "));
2590
+ if (manifest?.secrets) {
2591
+ const dummyLogger = {
2592
+ info: (...a) => pluginLog.info(a.map(String).join(" ")),
2593
+ warn: (...a) => pluginLog.warn(a.map(String).join(" ")),
2594
+ error: (...a) => pluginLog.error(a.map(String).join(" ")),
2595
+ debug: () => {
2596
+ }
2597
+ };
2598
+ const secretsCheck = createSecretsSDK(pluginName, pluginConfig, dummyLogger);
2599
+ const missing = [];
2600
+ for (const [key, decl] of Object.entries(
2601
+ manifest.secrets
2602
+ )) {
2603
+ if (decl.required && !secretsCheck.has(key)) {
2604
+ missing.push(`${key} \u2014 ${decl.description}`);
2605
+ }
2606
+ }
2607
+ if (missing.length > 0) {
2608
+ pluginLog.warn(
2609
+ `Missing required secrets:
2610
+ ` + missing.map((m) => ` \u2022 ${m}`).join("\n") + `
2611
+ Set via: /plugin set ${pluginName} <key> <value>`
2612
+ );
2613
+ }
2614
+ }
2615
+ const hasMigrate = typeof raw.migrate === "function";
2616
+ let pluginDb = null;
2617
+ const getDb = () => pluginDb;
2618
+ const withPluginDb = createDbWrapper(getDb, pluginName);
2619
+ const sanitizedConfig = sanitizeConfigForPlugins(config);
2620
+ const module = {
2621
+ name: pluginName,
2622
+ version: pluginVersion,
2623
+ // Store event hooks from plugin exports
2624
+ onMessage: typeof raw.onMessage === "function" ? raw.onMessage : void 0,
2625
+ onCallbackQuery: typeof raw.onCallbackQuery === "function" ? raw.onCallbackQuery : void 0,
2626
+ configure() {
2627
+ },
2628
+ migrate() {
2629
+ try {
2630
+ const dbPath = join4(PLUGIN_DATA_DIR, `${pluginName}.db`);
2631
+ pluginDb = openModuleDb(dbPath);
2632
+ if (hasMigrate) {
2633
+ raw.migrate(pluginDb);
2634
+ const pluginTables = pluginDb.prepare(
2635
+ `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`
2636
+ ).all().map((t) => t.name).filter((n) => n !== "_kv");
2637
+ if (pluginTables.length > 0) {
2638
+ migrateFromMainDb(pluginDb, pluginTables);
2639
+ }
2640
+ }
2641
+ } catch (err) {
2642
+ pluginLog.error(`migrate() failed: ${err instanceof Error ? err.message : err}`);
2643
+ if (pluginDb) {
2644
+ try {
2645
+ pluginDb.close();
2646
+ } catch {
2647
+ }
2648
+ pluginDb = null;
2649
+ }
2650
+ }
2651
+ },
2652
+ tools() {
2653
+ try {
2654
+ let toolDefs;
2655
+ if (typeof raw.tools === "function") {
2656
+ const sdk = createPluginSDK(sdkDeps, {
2657
+ pluginName,
2658
+ db: pluginDb,
2659
+ sanitizedConfig,
2660
+ pluginConfig
2661
+ });
2662
+ toolDefs = raw.tools(sdk);
2663
+ } else if (Array.isArray(raw.tools)) {
2664
+ toolDefs = raw.tools;
2665
+ } else {
2666
+ return [];
2667
+ }
2668
+ const validDefs = validateToolDefs(toolDefs, pluginName);
2669
+ return validDefs.map((def) => {
2670
+ const rawExecutor = def.execute;
2671
+ const sandboxedExecutor = (params, context) => {
2672
+ const sanitizedContext = {
2673
+ ...context,
2674
+ config: context.config ? sanitizeConfigForPlugins(context.config) : void 0
2675
+ };
2676
+ return rawExecutor(params, sanitizedContext);
2677
+ };
2678
+ return {
2679
+ tool: {
2680
+ name: def.name,
2681
+ description: def.description,
2682
+ parameters: def.parameters || {
2683
+ type: "object",
2684
+ properties: {}
2685
+ },
2686
+ ...def.category ? { category: def.category } : {}
2687
+ },
2688
+ executor: pluginDb ? withPluginDb(sandboxedExecutor) : sandboxedExecutor,
2689
+ scope: def.scope
2690
+ };
2691
+ });
2692
+ } catch (err) {
2693
+ pluginLog.error(`tools() failed: ${err instanceof Error ? err.message : err}`);
2694
+ return [];
2695
+ }
2696
+ },
2697
+ async start(context) {
2698
+ if (!raw.start) return;
2699
+ try {
2700
+ const enhancedContext = {
2701
+ bridge: context.bridge,
2702
+ db: pluginDb ?? null,
2703
+ config: sanitizedConfig,
2704
+ pluginConfig,
2705
+ log: logFn
2706
+ };
2707
+ await raw.start(enhancedContext);
2708
+ } catch (err) {
2709
+ pluginLog.error(`start() failed: ${err instanceof Error ? err.message : err}`);
2710
+ }
2711
+ },
2712
+ async stop() {
2713
+ try {
2714
+ await raw.stop?.();
2715
+ } catch (err) {
2716
+ pluginLog.error(`stop() failed: ${err instanceof Error ? err.message : err}`);
2717
+ } finally {
2718
+ if (pluginDb) {
2719
+ try {
2720
+ pluginDb.close();
2721
+ } catch {
2722
+ }
2723
+ pluginDb = null;
2724
+ }
2725
+ }
2726
+ }
2727
+ };
2728
+ return module;
2729
+ }
2730
+ async function ensurePluginDeps(pluginDir, pluginEntry) {
2731
+ const pkgJson = join4(pluginDir, "package.json");
2732
+ const lockfile = join4(pluginDir, "package-lock.json");
2733
+ const nodeModules = join4(pluginDir, "node_modules");
2734
+ if (!existsSync6(pkgJson)) return;
2735
+ if (!existsSync6(lockfile)) {
2736
+ log6.warn(
2737
+ `[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
2738
+ );
2739
+ return;
2740
+ }
2741
+ if (existsSync6(nodeModules)) {
2742
+ const marker = join4(nodeModules, ".package-lock.json");
2743
+ if (existsSync6(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
2744
+ }
2745
+ log6.info(`[${pluginEntry}] Installing dependencies...`);
2746
+ try {
2747
+ await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
2748
+ cwd: pluginDir,
2749
+ timeout: 6e4,
2750
+ env: { ...process.env, NODE_ENV: "production" }
2751
+ });
2752
+ log6.info(`[${pluginEntry}] Dependencies installed`);
2753
+ } catch (err) {
2754
+ log6.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
2755
+ }
2756
+ }
2757
+ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2758
+ const pluginsDir = WORKSPACE_PATHS.PLUGINS_DIR;
2759
+ if (!existsSync6(pluginsDir)) {
2760
+ return [];
2761
+ }
2762
+ const entries = readdirSync2(pluginsDir);
2763
+ const modules = [];
2764
+ const loadedNames = /* @__PURE__ */ new Set();
2765
+ const pluginPaths = [];
2766
+ for (const entry of entries) {
2767
+ if (entry === "data") continue;
2768
+ const entryPath = join4(pluginsDir, entry);
2769
+ let modulePath = null;
2770
+ try {
2771
+ const stat = statSync(entryPath);
2772
+ if (stat.isFile() && entry.endsWith(".js")) {
2773
+ modulePath = entryPath;
2774
+ } else if (stat.isDirectory()) {
2775
+ const indexPath = join4(entryPath, "index.js");
2776
+ if (existsSync6(indexPath)) {
2777
+ modulePath = indexPath;
2778
+ }
2779
+ }
2780
+ } catch {
2781
+ continue;
2782
+ }
2783
+ if (modulePath) {
2784
+ pluginPaths.push({ entry, path: modulePath });
2785
+ }
2786
+ }
2787
+ await Promise.allSettled(
2788
+ pluginPaths.filter(({ path }) => path.endsWith("index.js")).map(({ entry }) => ensurePluginDeps(join4(pluginsDir, entry), entry))
2789
+ );
2790
+ const loadResults = await Promise.allSettled(
2791
+ pluginPaths.map(async ({ entry, path }) => {
2792
+ const moduleUrl = pathToFileURL(path).href;
2793
+ const mod = await import(moduleUrl);
2794
+ return { entry, mod };
2795
+ })
2796
+ );
2797
+ for (const result of loadResults) {
2798
+ if (result.status === "rejected") {
2799
+ log6.error(
2800
+ `Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
2801
+ );
2802
+ continue;
2803
+ }
2804
+ const { entry, mod } = result.value;
2805
+ try {
2806
+ if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
2807
+ log6.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
2808
+ continue;
2809
+ }
2810
+ const adapted = adaptPlugin(mod, entry, config, loadedModuleNames, sdkDeps);
2811
+ if (loadedNames.has(adapted.name)) {
2812
+ log6.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
2813
+ continue;
2814
+ }
2815
+ loadedNames.add(adapted.name);
2816
+ modules.push(adapted);
2817
+ } catch (err) {
2818
+ log6.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
2819
+ }
2820
+ }
2821
+ return modules;
2822
+ }
2823
+
2824
+ // src/config/configurable-keys.ts
2825
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "fs";
2826
+ import { parse as parse2, stringify as stringify2 } from "yaml";
2827
+ var noValidation = () => void 0;
2828
+ var identity = (v) => v;
2829
+ var nonEmpty = (v) => v.length > 0 ? void 0 : "Must not be empty";
2830
+ function numberInRange(min, max) {
2831
+ return (v) => {
2832
+ const n = Number(v);
2833
+ if (isNaN(n)) return "Must be a number";
2834
+ if (n < min || n > max) return `Must be between ${min} and ${max}`;
2835
+ return void 0;
2836
+ };
2837
+ }
2838
+ function enumValidator(options) {
2839
+ return (v) => options.includes(v) ? void 0 : `Must be one of: ${options.join(", ")}`;
2840
+ }
2841
+ var CONFIGURABLE_KEYS = {
2842
+ // ─── API Keys ──────────────────────────────────────────────────────
2843
+ "agent.api_key": {
2844
+ type: "string",
2845
+ category: "API Keys",
2846
+ description: "LLM provider API key",
2847
+ sensitive: true,
2848
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
2849
+ mask: (v) => v.slice(0, 8) + "****",
2850
+ parse: identity
2851
+ },
2852
+ tavily_api_key: {
2853
+ type: "string",
2854
+ category: "API Keys",
2855
+ description: "Tavily API key for web search",
2856
+ sensitive: true,
2857
+ validate: (v) => v.startsWith("tvly-") ? void 0 : "Must start with 'tvly-'",
2858
+ mask: (v) => v.slice(0, 9) + "****",
2859
+ parse: identity
2860
+ },
2861
+ tonapi_key: {
2862
+ type: "string",
2863
+ category: "API Keys",
2864
+ description: "TonAPI key for higher rate limits",
2865
+ sensitive: true,
2866
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
2867
+ mask: (v) => v.slice(0, 10) + "****",
2868
+ parse: identity
2869
+ },
2870
+ "telegram.bot_token": {
2871
+ type: "string",
2872
+ category: "API Keys",
2873
+ description: "Bot token from @BotFather",
2874
+ sensitive: true,
2875
+ validate: (v) => v.includes(":") ? void 0 : "Must contain ':' (e.g., 123456:ABC...)",
2876
+ mask: (v) => v.split(":")[0] + ":****",
2877
+ parse: identity
2878
+ },
2879
+ // ─── Agent ─────────────────────────────────────────────────────────
2880
+ "agent.provider": {
2881
+ type: "enum",
2882
+ category: "Agent",
2883
+ description: "LLM provider",
2884
+ sensitive: false,
2885
+ options: [
2886
+ "anthropic",
2887
+ "openai",
2888
+ "google",
2889
+ "xai",
2890
+ "groq",
2891
+ "openrouter",
2892
+ "moonshot",
2893
+ "mistral",
2894
+ "cocoon",
2895
+ "local"
2896
+ ],
2897
+ validate: enumValidator([
2898
+ "anthropic",
2899
+ "openai",
2900
+ "google",
2901
+ "xai",
2902
+ "groq",
2903
+ "openrouter",
2904
+ "moonshot",
2905
+ "mistral",
2906
+ "cocoon",
2907
+ "local"
2908
+ ]),
2909
+ mask: identity,
2910
+ parse: identity
2911
+ },
2912
+ "agent.model": {
2913
+ type: "string",
2914
+ category: "Agent",
2915
+ description: "Main LLM model ID",
2916
+ sensitive: false,
2917
+ validate: nonEmpty,
2918
+ mask: identity,
2919
+ parse: identity
2920
+ },
2921
+ "agent.utility_model": {
2922
+ type: "string",
2923
+ category: "Agent",
2924
+ description: "Cheap model for summarization (auto-detected if empty)",
2925
+ sensitive: false,
2926
+ validate: noValidation,
2927
+ mask: identity,
2928
+ parse: identity
2929
+ },
2930
+ "agent.temperature": {
2931
+ type: "number",
2932
+ category: "Agent",
2933
+ description: "Response creativity (0.0 = deterministic, 2.0 = max)",
2934
+ sensitive: false,
2935
+ validate: numberInRange(0, 2),
2936
+ mask: identity,
2937
+ parse: (v) => Number(v)
2938
+ },
2939
+ "agent.max_tokens": {
2940
+ type: "number",
2941
+ category: "Agent",
2942
+ description: "Maximum response length in tokens",
2943
+ sensitive: false,
2944
+ validate: numberInRange(256, 128e3),
2945
+ mask: identity,
2946
+ parse: (v) => Number(v)
2947
+ },
2948
+ "agent.max_agentic_iterations": {
2949
+ type: "number",
2950
+ category: "Agent",
2951
+ description: "Max tool-call loop iterations per message",
2952
+ sensitive: false,
2953
+ validate: numberInRange(1, 20),
2954
+ mask: identity,
2955
+ parse: (v) => Number(v)
2956
+ },
2957
+ // ─── Session ───────────────────────────────────────────────────
2958
+ "agent.session_reset_policy.daily_reset_enabled": {
2959
+ type: "boolean",
2960
+ category: "Session",
2961
+ description: "Enable daily session reset at specified hour",
2962
+ sensitive: false,
2963
+ validate: enumValidator(["true", "false"]),
2964
+ mask: identity,
2965
+ parse: (v) => v === "true"
2966
+ },
2967
+ "agent.session_reset_policy.daily_reset_hour": {
2968
+ type: "number",
2969
+ category: "Session",
2970
+ description: "Hour (0-23 UTC) for daily session reset",
2971
+ sensitive: false,
2972
+ validate: numberInRange(0, 23),
2973
+ mask: identity,
2974
+ parse: (v) => Number(v)
2975
+ },
2976
+ "agent.session_reset_policy.idle_expiry_enabled": {
2977
+ type: "boolean",
2978
+ category: "Session",
2979
+ description: "Enable automatic session expiry after idle period",
2980
+ sensitive: false,
2981
+ validate: enumValidator(["true", "false"]),
2982
+ mask: identity,
2983
+ parse: (v) => v === "true"
2984
+ },
2985
+ "agent.session_reset_policy.idle_expiry_minutes": {
2986
+ type: "number",
2987
+ category: "Session",
2988
+ description: "Idle minutes before session expires (minimum 1)",
2989
+ sensitive: false,
2990
+ validate: numberInRange(1, Number.MAX_SAFE_INTEGER),
2991
+ mask: identity,
2992
+ parse: (v) => Number(v)
2993
+ },
2994
+ // ─── Telegram ──────────────────────────────────────────────────────
2995
+ "telegram.bot_username": {
2996
+ type: "string",
2997
+ category: "Telegram",
2998
+ description: "Bot username without @",
2999
+ sensitive: false,
3000
+ validate: (v) => v.length >= 3 ? void 0 : "Must be at least 3 characters",
3001
+ mask: identity,
3002
+ parse: identity
3003
+ },
3004
+ "telegram.dm_policy": {
3005
+ type: "enum",
3006
+ category: "Telegram",
3007
+ description: "DM access policy",
3008
+ sensitive: false,
3009
+ options: ["pairing", "allowlist", "open", "disabled"],
3010
+ validate: enumValidator(["pairing", "allowlist", "open", "disabled"]),
3011
+ mask: identity,
3012
+ parse: identity
3013
+ },
3014
+ "telegram.group_policy": {
3015
+ type: "enum",
3016
+ category: "Telegram",
3017
+ description: "Group access policy",
3018
+ sensitive: false,
3019
+ options: ["open", "allowlist", "disabled"],
3020
+ validate: enumValidator(["open", "allowlist", "disabled"]),
3021
+ mask: identity,
3022
+ parse: identity
3023
+ },
3024
+ "telegram.require_mention": {
3025
+ type: "boolean",
3026
+ category: "Telegram",
3027
+ description: "Require @mention in groups to respond",
3028
+ sensitive: false,
3029
+ validate: enumValidator(["true", "false"]),
3030
+ mask: identity,
3031
+ parse: (v) => v === "true"
3032
+ },
3033
+ "telegram.owner_name": {
3034
+ type: "string",
3035
+ category: "Telegram",
3036
+ description: "Owner's first name (used in system prompt)",
3037
+ sensitive: false,
3038
+ validate: noValidation,
3039
+ mask: identity,
3040
+ parse: identity
3041
+ },
3042
+ "telegram.owner_username": {
3043
+ type: "string",
3044
+ category: "Telegram",
3045
+ description: "Owner's Telegram username (without @)",
3046
+ sensitive: false,
3047
+ validate: noValidation,
3048
+ mask: identity,
3049
+ parse: identity
3050
+ },
3051
+ "telegram.debounce_ms": {
3052
+ type: "number",
3053
+ category: "Telegram",
3054
+ description: "Group message debounce delay in ms (0 = disabled)",
3055
+ sensitive: false,
3056
+ validate: numberInRange(0, 1e4),
3057
+ mask: identity,
3058
+ parse: (v) => Number(v)
3059
+ },
3060
+ "telegram.agent_channel": {
3061
+ type: "string",
3062
+ category: "Telegram",
3063
+ description: "Channel username for auto-publishing",
3064
+ sensitive: false,
3065
+ validate: noValidation,
3066
+ mask: identity,
3067
+ parse: identity
3068
+ },
3069
+ "telegram.typing_simulation": {
3070
+ type: "boolean",
3071
+ category: "Telegram",
3072
+ description: "Simulate typing indicator before sending replies",
3073
+ sensitive: false,
3074
+ validate: enumValidator(["true", "false"]),
3075
+ mask: identity,
3076
+ parse: (v) => v === "true"
3077
+ },
3078
+ // ─── Embedding ─────────────────────────────────────────────────────
3079
+ "embedding.provider": {
3080
+ type: "enum",
3081
+ category: "Embedding",
3082
+ description: "Embedding provider for RAG",
3083
+ sensitive: false,
3084
+ options: ["local", "anthropic", "none"],
3085
+ validate: enumValidator(["local", "anthropic", "none"]),
3086
+ mask: identity,
3087
+ parse: identity
3088
+ },
3089
+ // ─── WebUI ─────────────────────────────────────────────────────────
3090
+ "webui.port": {
3091
+ type: "number",
3092
+ category: "WebUI",
3093
+ description: "HTTP server port (requires restart)",
3094
+ sensitive: false,
3095
+ validate: numberInRange(1024, 65535),
3096
+ mask: identity,
3097
+ parse: (v) => Number(v)
3098
+ },
3099
+ "webui.log_requests": {
3100
+ type: "boolean",
3101
+ category: "WebUI",
3102
+ description: "Log all HTTP requests to console",
3103
+ sensitive: false,
3104
+ validate: enumValidator(["true", "false"]),
3105
+ mask: identity,
3106
+ parse: (v) => v === "true"
3107
+ },
3108
+ // ─── Deals ─────────────────────────────────────────────────────────
3109
+ "deals.enabled": {
3110
+ type: "boolean",
3111
+ category: "Deals",
3112
+ description: "Enable the deals/escrow module",
3113
+ sensitive: false,
3114
+ validate: enumValidator(["true", "false"]),
3115
+ mask: identity,
3116
+ parse: (v) => v === "true"
3117
+ },
3118
+ // ─── Developer ─────────────────────────────────────────────────────
3119
+ "dev.hot_reload": {
3120
+ type: "boolean",
3121
+ category: "Developer",
3122
+ description: "Watch ~/.teleton/plugins/ for live changes",
3123
+ sensitive: false,
3124
+ validate: enumValidator(["true", "false"]),
3125
+ mask: identity,
3126
+ parse: (v) => v === "true"
3127
+ }
3128
+ };
3129
+ var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3130
+ function assertSafePath(parts) {
3131
+ if (parts.some((p) => FORBIDDEN_SEGMENTS.has(p))) {
3132
+ throw new Error("Invalid config path: forbidden segment");
3133
+ }
3134
+ }
3135
+ function getNestedValue(obj, path) {
3136
+ const parts = path.split(".");
3137
+ assertSafePath(parts);
3138
+ let current = obj;
3139
+ for (const part of parts) {
3140
+ if (current == null || typeof current !== "object") return void 0;
3141
+ current = current[part];
3142
+ }
3143
+ return current;
3144
+ }
3145
+ function setNestedValue(obj, path, value) {
3146
+ const parts = path.split(".");
3147
+ assertSafePath(parts);
3148
+ let current = obj;
3149
+ for (let i = 0; i < parts.length - 1; i++) {
3150
+ if (current[parts[i]] == null || typeof current[parts[i]] !== "object") {
3151
+ current[parts[i]] = {};
3152
+ }
3153
+ current = current[parts[i]];
3154
+ }
3155
+ current[parts[parts.length - 1]] = value;
3156
+ }
3157
+ function deleteNestedValue(obj, path) {
3158
+ const parts = path.split(".");
3159
+ assertSafePath(parts);
3160
+ let current = obj;
3161
+ for (let i = 0; i < parts.length - 1; i++) {
3162
+ if (current == null || typeof current !== "object") return;
3163
+ current = current[parts[i]];
3164
+ }
3165
+ if (current != null && typeof current === "object") {
3166
+ delete current[parts[parts.length - 1]];
3167
+ }
3168
+ }
3169
+ function readRawConfig(configPath) {
3170
+ const fullPath = expandPath(configPath);
3171
+ if (!existsSync7(fullPath)) {
3172
+ throw new Error(`Config file not found: ${fullPath}
3173
+ Run 'teleton setup' to create one.`);
3174
+ }
3175
+ const raw = parse2(readFileSync6(fullPath, "utf-8"));
3176
+ if (!raw || typeof raw !== "object") {
3177
+ throw new Error(`Invalid config file: ${fullPath}`);
3178
+ }
3179
+ return raw;
3180
+ }
3181
+ function writeRawConfig(raw, configPath) {
3182
+ const clone = { ...raw };
3183
+ delete clone.market;
3184
+ const result = ConfigSchema.safeParse(clone);
3185
+ if (!result.success) {
3186
+ throw new Error(`Refusing to save invalid config: ${result.error.message}`);
3187
+ }
3188
+ raw.meta = raw.meta ?? {};
3189
+ raw.meta.last_modified_at = (/* @__PURE__ */ new Date()).toISOString();
3190
+ const fullPath = expandPath(configPath);
3191
+ writeFileSync3(fullPath, stringify2(raw), { encoding: "utf-8", mode: 384 });
3192
+ }
3193
+
3194
+ export {
3195
+ loadConfig,
3196
+ configExists,
3197
+ getDefaultConfigPath,
3198
+ WorkspaceSecurityError,
3199
+ validatePath,
3200
+ validateReadPath,
3201
+ validateWritePath,
3202
+ validateDirectory,
3203
+ appendToDailyLog,
3204
+ writeSummaryToDailyLog,
3205
+ sanitizeForPrompt,
3206
+ sanitizeForContext,
3207
+ clearPromptCache,
3208
+ loadSoul,
3209
+ buildSystemPrompt,
3210
+ writePluginSecret,
3211
+ deletePluginSecret,
3212
+ listPluginSecretKeys,
3213
+ toLong,
3214
+ randomLong,
3215
+ withBlockchainRetry,
3216
+ sendTon,
3217
+ adaptPlugin,
3218
+ ensurePluginDeps,
3219
+ loadEnhancedPlugins,
3220
+ CONFIGURABLE_KEYS,
3221
+ getNestedValue,
3222
+ setNestedValue,
3223
+ deleteNestedValue,
3224
+ readRawConfig,
3225
+ writeRawConfig
3226
+ };