@t2000/cli 0.18.5 → 0.18.7

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.
@@ -12650,7 +12650,7 @@ var require_keyboard = __commonJS({
12650
12650
  }
12651
12651
  };
12652
12652
  exports.Keyboard = Keyboard;
12653
- var InlineKeyboard = class _InlineKeyboard {
12653
+ var InlineKeyboard2 = class _InlineKeyboard {
12654
12654
  /**
12655
12655
  * Initialize a new `InlineKeyboard` with an optional two-dimensional array
12656
12656
  * of `InlineKeyboardButton` objects. This is the nested array that holds
@@ -13162,7 +13162,7 @@ var require_keyboard = __commonJS({
13162
13162
  return new _InlineKeyboard(source.map((row) => row.slice()));
13163
13163
  }
13164
13164
  };
13165
- exports.InlineKeyboard = InlineKeyboard;
13165
+ exports.InlineKeyboard = InlineKeyboard2;
13166
13166
  function transpose(grid) {
13167
13167
  var _a3;
13168
13168
  const transposed = [];
@@ -14989,6 +14989,7 @@ var require_node_cron = __commonJS({
14989
14989
  import { createRequire } from "module";
14990
14990
  import { fileURLToPath } from "url";
14991
14991
  import { dirname, resolve, join } from "path";
14992
+ import { T2000Error, INVESTMENT_ASSETS } from "@t2000/sdk";
14992
14993
 
14993
14994
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.39.0/node_modules/@anthropic-ai/sdk/version.mjs
14994
14995
  var VERSION = "0.39.0";
@@ -24863,7 +24864,6 @@ var import_grammy = __toESM(require_mod2(), 1);
24863
24864
  import { Hono } from "hono";
24864
24865
  import { serve } from "@hono/node-server";
24865
24866
  var import_node_cron = __toESM(require_node_cron(), 1);
24866
- import { INVESTMENT_ASSETS } from "@t2000/sdk";
24867
24867
  import { readFile, writeFile } from "fs/promises";
24868
24868
  import { homedir } from "os";
24869
24869
  import { existsSync, mkdirSync, appendFileSync, statSync, readdirSync, unlinkSync } from "fs";
@@ -25294,6 +25294,17 @@ function getInlineHTML() {
25294
25294
  .confirm-bar .accept { background: var(--green); color: white; }
25295
25295
  .confirm-bar .cancel { background: var(--surface); color: var(--text-muted); border: 1px solid var(--border); }
25296
25296
  .typing { align-self: flex-start; color: var(--text-muted); font-size: 13px; padding: 8px 16px; }
25297
+ #welcome { padding: 40px 20px; text-align: center; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 16px; }
25298
+ #welcome h2 { font-size: 18px; font-weight: 600; color: var(--text); }
25299
+ #welcome p { font-size: 14px; color: var(--text-muted); max-width: 320px; }
25300
+ .quick-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; max-width: 380px; }
25301
+ .quick-btn { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 10px 16px; color: var(--text); font-size: 13px; cursor: pointer; transition: border-color 0.15s; }
25302
+ .quick-btn:hover { border-color: var(--accent); }
25303
+ .msg a { color: var(--accent); text-decoration: none; }
25304
+ .msg a:hover { text-decoration: underline; }
25305
+ .msg code { background: rgba(255,255,255,0.08); padding: 1px 5px; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 0.9em; }
25306
+ .msg ul, .msg ol { margin: 4px 0; padding-left: 20px; }
25307
+ .msg li { margin: 2px 0; }
25297
25308
  footer { padding: 16px 20px; border-top: 1px solid var(--border); }
25298
25309
  #input-form { display: flex; gap: 8px; }
25299
25310
  #input { flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; color: var(--text); font-size: 14px; outline: none; font-family: inherit; }
@@ -25308,7 +25319,17 @@ function getInlineHTML() {
25308
25319
  <div class="status" id="status"></div>
25309
25320
  <h1>t2000</h1>
25310
25321
  </header>
25311
- <div id="messages"></div>
25322
+ <div id="welcome">
25323
+ <h2>t2000</h2>
25324
+ <p>Your AI financial advisor. Ask me anything about your accounts.</p>
25325
+ <div class="quick-actions">
25326
+ <button class="quick-btn" onclick="quickAction('What\\'s my balance?')">\u{1F4B0} Balance</button>
25327
+ <button class="quick-btn" onclick="quickAction('Show my portfolio')">\u{1F4CA} Portfolio</button>
25328
+ <button class="quick-btn" onclick="quickAction('What are the best rates?')">\u{1F4C8} Rates</button>
25329
+ <button class="quick-btn" onclick="quickAction('Show recent transactions')">\u{1F4B8} Transactions</button>
25330
+ </div>
25331
+ </div>
25332
+ <div id="messages" style="display:none"></div>
25312
25333
  <footer>
25313
25334
  <form id="input-form">
25314
25335
  <input id="input" placeholder="Message your AI financial advisor..." autocomplete="off" />
@@ -25317,11 +25338,25 @@ function getInlineHTML() {
25317
25338
  </footer>
25318
25339
  <script>
25319
25340
  const messages = document.getElementById('messages');
25341
+ const welcome = document.getElementById('welcome');
25320
25342
  const input = document.getElementById('input');
25321
25343
  const form = document.getElementById('input-form');
25322
25344
  const statusDot = document.getElementById('status');
25323
25345
  let currentAssistantMsg = null;
25324
25346
  let connected = false;
25347
+ let welcomeVisible = true;
25348
+
25349
+ function hideWelcome() {
25350
+ if (!welcomeVisible) return;
25351
+ welcomeVisible = false;
25352
+ welcome.style.display = 'none';
25353
+ messages.style.display = 'flex';
25354
+ }
25355
+
25356
+ function quickAction(text) {
25357
+ hideWelcome();
25358
+ sendMsg(text);
25359
+ }
25325
25360
 
25326
25361
  function connect() {
25327
25362
  const es = new EventSource('/api/events');
@@ -25330,12 +25365,14 @@ function connect() {
25330
25365
  es.onmessage = (e) => {
25331
25366
  const data = JSON.parse(e.data);
25332
25367
  if (data.type === 'token') {
25368
+ hideWelcome();
25333
25369
  if (!currentAssistantMsg) {
25334
25370
  currentAssistantMsg = addMessage('', 'assistant');
25335
25371
  }
25336
25372
  currentAssistantMsg.textContent += data.text;
25337
25373
  scrollToBottom();
25338
25374
  } else if (data.type === 'message') {
25375
+ hideWelcome();
25339
25376
  if (currentAssistantMsg) {
25340
25377
  currentAssistantMsg.innerHTML = renderMarkdown(data.text);
25341
25378
  } else {
@@ -25344,6 +25381,7 @@ function connect() {
25344
25381
  }
25345
25382
  currentAssistantMsg = null;
25346
25383
  } else if (data.type === 'tool_call') {
25384
+ hideWelcome();
25347
25385
  if (!currentAssistantMsg) currentAssistantMsg = addMessage('', 'assistant');
25348
25386
  const badge = document.createElement('span');
25349
25387
  badge.className = 'tool-badge';
@@ -25372,20 +25410,30 @@ function addMessage(text, role) {
25372
25410
  function scrollToBottom() { messages.scrollTop = messages.scrollHeight; }
25373
25411
 
25374
25412
  function renderMarkdown(text) {
25375
- return text
25376
- .replace(/\\|(.+?)\\|/g, (match) => {
25377
- const rows = match.trim().split('\\n').filter(r => r.trim());
25378
- if (rows.length < 2) return match;
25379
- const headers = rows[0].split('|').filter(c => c.trim()).map(c => '<th>' + c.trim() + '</th>');
25380
- const body = rows.slice(2).map(r => '<tr>' + r.split('|').filter(c => c.trim()).map(c => '<td>' + c.trim() + '</td>').join('') + '</tr>');
25381
- return '<table><tr>' + headers.join('') + '</tr>' + body.join('') + '</table>';
25382
- })
25413
+ let html = text
25414
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
25415
+ .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>')
25383
25416
  .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
25384
- .replace(/\\n/g, '<br>');
25417
+ .replace(/\`([^\`]+)\`/g, '<code>$1</code>');
25418
+ const lines = html.split('\\n');
25419
+ let result = [];
25420
+ let inList = false;
25421
+ for (const line of lines) {
25422
+ if (line.match(/^\\s*[-*]\\s+/)) {
25423
+ if (!inList) { result.push('<ul>'); inList = true; }
25424
+ result.push('<li>' + line.replace(/^\\s*[-*]\\s+/, '') + '</li>');
25425
+ } else {
25426
+ if (inList) { result.push('</ul>'); inList = false; }
25427
+ result.push(line);
25428
+ }
25429
+ }
25430
+ if (inList) result.push('</ul>');
25431
+ return result.join('<br>').replace(/<br><ul>/g, '<ul>').replace(/<\\/ul><br>/g, '</ul>');
25385
25432
  }
25386
25433
 
25387
25434
  function sendMsg(text) {
25388
25435
  if (!text.trim()) return;
25436
+ hideWelcome();
25389
25437
  addMessage(text, 'user');
25390
25438
  input.value = '';
25391
25439
  currentAssistantMsg = null;
@@ -25402,6 +25450,7 @@ connect();
25402
25450
  </html>`;
25403
25451
  }
25404
25452
  var TELEGRAM_MAX_LENGTH = 4096;
25453
+ var TYPING_INTERVAL_MS = 4e3;
25405
25454
  var TelegramChannel = class {
25406
25455
  id = "telegram";
25407
25456
  name = "Telegram";
@@ -25409,7 +25458,9 @@ var TelegramChannel = class {
25409
25458
  allowedUsers;
25410
25459
  messageHandler = null;
25411
25460
  pinUnlockHandler = null;
25461
+ startHandler = null;
25412
25462
  awaitingPin = /* @__PURE__ */ new Set();
25463
+ typingIntervals = /* @__PURE__ */ new Map();
25413
25464
  constructor(config) {
25414
25465
  this.bot = new import_grammy.Bot(config.botToken);
25415
25466
  this.allowedUsers = new Set(config.allowedUsers);
@@ -25427,25 +25478,101 @@ var TelegramChannel = class {
25427
25478
  }
25428
25479
  async send(userId, message) {
25429
25480
  const chatId = parseInt(userId, 10);
25430
- const chunks = splitMessage(message);
25481
+ const html = markdownToTelegramHTML(message);
25482
+ const chunks = splitMessage(html);
25431
25483
  for (const chunk of chunks) {
25432
25484
  try {
25433
- await this.bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
25485
+ await this.bot.api.sendMessage(chatId, chunk, { parse_mode: "HTML" });
25434
25486
  } catch {
25435
- await this.bot.api.sendMessage(chatId, chunk);
25487
+ await this.bot.api.sendMessage(chatId, stripHtml(chunk));
25436
25488
  }
25437
25489
  }
25438
25490
  }
25491
+ async sendWithConfirmation(userId, message) {
25492
+ const chatId = parseInt(userId, 10);
25493
+ const html = markdownToTelegramHTML(message);
25494
+ const keyboard = new import_grammy.InlineKeyboard().text("\u2705 Confirm", "confirm:yes").text("\u274C Cancel", "confirm:no");
25495
+ try {
25496
+ await this.bot.api.sendMessage(chatId, html, { parse_mode: "HTML", reply_markup: keyboard });
25497
+ } catch {
25498
+ await this.bot.api.sendMessage(chatId, stripHtml(html), { reply_markup: keyboard });
25499
+ }
25500
+ }
25439
25501
  onMessage(handler) {
25440
25502
  this.messageHandler = handler;
25441
25503
  }
25442
25504
  onPinUnlock(handler) {
25443
25505
  this.pinUnlockHandler = handler;
25444
25506
  }
25507
+ onStart(handler) {
25508
+ this.startHandler = handler;
25509
+ }
25445
25510
  requestPin(userId) {
25446
25511
  this.awaitingPin.add(userId);
25447
25512
  }
25513
+ startTyping(userId) {
25514
+ const chatId = parseInt(userId, 10);
25515
+ this.bot.api.sendChatAction(chatId, "typing").catch(() => {
25516
+ });
25517
+ const interval = setInterval(() => {
25518
+ this.bot.api.sendChatAction(chatId, "typing").catch(() => {
25519
+ });
25520
+ }, TYPING_INTERVAL_MS);
25521
+ this.typingIntervals.set(userId, interval);
25522
+ }
25523
+ stopTyping(userId) {
25524
+ const interval = this.typingIntervals.get(userId);
25525
+ if (interval) {
25526
+ clearInterval(interval);
25527
+ this.typingIntervals.delete(userId);
25528
+ }
25529
+ }
25448
25530
  setupHandlers() {
25531
+ this.bot.command("start", async (ctx) => {
25532
+ if (!this.isAllowed(ctx)) {
25533
+ await ctx.reply("This is a private financial agent. Access is restricted to the account owner.");
25534
+ return;
25535
+ }
25536
+ const userId = ctx.from.id.toString();
25537
+ let welcomeText = "Welcome to t2000 \u2014 your AI financial advisor.\n\nAsk me anything about your accounts.";
25538
+ if (this.startHandler) {
25539
+ try {
25540
+ welcomeText = await this.startHandler(userId);
25541
+ } catch {
25542
+ }
25543
+ }
25544
+ const keyboard = new import_grammy.InlineKeyboard().text("\u{1F4B0} Balance", "quick:What's my balance?").text("\u{1F4CA} Portfolio", "quick:Show my portfolio").row().text("\u{1F4C8} Rates", "quick:What are the best rates?").text("\u2753 Help", "quick:What can you do?");
25545
+ await ctx.reply(welcomeText, { reply_markup: keyboard });
25546
+ });
25547
+ this.bot.on("callback_query:data", async (ctx) => {
25548
+ if (!this.isAllowed(ctx)) {
25549
+ await ctx.answerCallbackQuery({ text: "Unauthorized" });
25550
+ return;
25551
+ }
25552
+ const data = ctx.callbackQuery.data;
25553
+ const userId = ctx.from.id.toString();
25554
+ if (data.startsWith("confirm:")) {
25555
+ const answer = data.slice(8);
25556
+ await ctx.answerCallbackQuery();
25557
+ try {
25558
+ await ctx.editMessageReplyMarkup({ reply_markup: void 0 });
25559
+ } catch {
25560
+ }
25561
+ if (this.messageHandler) {
25562
+ await this.messageHandler({ channelId: this.id, userId, text: answer });
25563
+ }
25564
+ return;
25565
+ }
25566
+ if (data.startsWith("quick:")) {
25567
+ await ctx.answerCallbackQuery();
25568
+ if (this.messageHandler) {
25569
+ const text = data.slice(6);
25570
+ await this.messageHandler({ channelId: this.id, userId, text });
25571
+ }
25572
+ return;
25573
+ }
25574
+ await ctx.answerCallbackQuery({ text: "Unknown action" });
25575
+ });
25449
25576
  this.bot.on(["message:photo", "message:video", "message:voice", "message:sticker", "message:document"], async (ctx) => {
25450
25577
  if (!this.isAllowed(ctx)) return;
25451
25578
  await ctx.reply("I can only process text messages. How can I help with your finances?");
@@ -25512,6 +25639,19 @@ function splitMessage(text) {
25512
25639
  }
25513
25640
  return chunks;
25514
25641
  }
25642
+ function escapeHtml(text) {
25643
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25644
+ }
25645
+ function markdownToTelegramHTML(text) {
25646
+ let result = escapeHtml(text);
25647
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
25648
+ result = result.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
25649
+ result = result.replace(/`([^`]+)`/g, "<code>$1</code>");
25650
+ return result;
25651
+ }
25652
+ function stripHtml(text) {
25653
+ return text.replace(/<[^>]+>/g, "");
25654
+ }
25515
25655
  var investAssets = Object.keys(INVESTMENT_ASSETS);
25516
25656
  function createToolRegistry() {
25517
25657
  return [
@@ -26014,23 +26154,66 @@ You manage their bank accounts on the Sui blockchain:
26014
26154
  - Exchange (swap tokens via Cetus DEX)
26015
26155
  - Investment (crypto & commodities \u2014 BTC, ETH, SUI, GOLD)
26016
26156
 
26017
- You have access to ${toolCount} tools. Use them to check balances, execute transactions, manage investments, and optimize yield.
26157
+ You have ${toolCount} tools. Use them to check balances, execute transactions, manage investments, and optimize yield.
26018
26158
 
26019
26159
  RULES:
26020
- - Always confirm before executing state-changing actions (send, save, invest, etc.)
26021
- - Show a clear summary of what you're about to do and ask "should I proceed?"
26022
- - For read-only queries (balance, rates, portfolio), respond immediately
26023
- - Be concise. Numbers matter. Skip fluff.
26024
- - Format currency with proper decimals. Show percentages for APY.
26025
- - If the user asks something outside finance, politely redirect.
26026
- - When presenting tables, use markdown format.
26027
- - Always include transaction links (suiscan.xyz) after successful transactions.
26160
+ - Always confirm before state-changing actions (send, save, invest, borrow, etc.)
26161
+ - Show what you'll do and ask "should I proceed?"
26162
+ - For read-only queries (balance, rates, portfolio), respond immediately \u2014 no confirmation needed
26163
+ - If the user asks something outside finance, briefly redirect
26164
+
26165
+ FORMATTING \u2014 follow these exactly:
26166
+ - Use standard markdown only: **bold**, \`code\`, [links](url), - lists
26167
+ - NEVER use markdown tables \u2014 they render poorly on mobile
26168
+ - One data point per line with emoji prefix for scannability
26169
+ - Currency: always 2 decimal places ($52.67, not $52.6723)
26170
+ - APY: always show % with 1-2 decimals (4.2%, not 0.0423)
26171
+ - Addresses: wrap in backticks \`0xabc...def\`
26172
+ - Transaction links: [View on explorer](https://suiscan.xyz/testnet/tx/DIGEST)
26173
+ - No Unicode box-drawing characters (\u2500, \u2502, \u250C, etc.) \u2014 they render inconsistently
26174
+ - No column-aligned text with spaces \u2014 breaks on proportional fonts
26028
26175
 
26029
26176
  PERSONALITY:
26030
- - Professional but approachable
26031
- - Brief \u2014 this is a chat, not an essay
26032
- - Proactive \u2014 suggest optimizations when you see them
26033
- - When you notice a better yield rate, mention it`;
26177
+ - You are a knowledgeable financial advisor, not a chatbot
26178
+ - Brief \u2014 3-5 lines for simple queries, never an essay
26179
+ - Lead with the number \u2014 users scan for amounts
26180
+ - Be opinionated \u2014 if you notice idle funds, bad rates, or risky positions, say so
26181
+ - Suggest next actions only when something is genuinely actionable (idle funds, rate changes, better yield, risky health factor). Do NOT add a suggestion to every response
26182
+ - Never start with "Sure!" or "Of course!" \u2014 just answer
26183
+
26184
+ RESPONSE EXAMPLES \u2014 match this style:
26185
+
26186
+ When showing balances:
26187
+ \u{1F4B3} Checking: **$52.67**
26188
+ \u{1F3E6} Savings: **$19.24** (earning 4.2%)
26189
+ \u{1F4B8} Debt: **-$2.01**
26190
+ \u{1F4C8} Investment: **$0.05**
26191
+
26192
+ Net: **$70.95**
26193
+
26194
+ Your debt ($2.01) costs more than it earns. Pay it off from checking? Just say "repay all."
26195
+
26196
+ When showing a transaction receipt:
26197
+ \u2705 Saved **$80.00**
26198
+
26199
+ Protocol: NAVI
26200
+ APY: 5.57%
26201
+ Monthly yield: ~$3.71
26202
+ [View on explorer](https://suiscan.xyz/testnet/tx/abc123)
26203
+
26204
+ Savings balance: **$99.24** (+$80.00)
26205
+
26206
+ When showing portfolio:
26207
+ Your portfolio: **$152.30** (+2.3%)
26208
+
26209
+ \u{1F4C8} **SUI** \u2014 45.2 tokens ($48.00, +3.1%) \u2014 earning 2.6% on Suilend
26210
+ \u{1F4C8} **BTC** \u2014 0.0012 ($89.30, +1.8%)
26211
+ \u{1F4C9} **ETH** \u2014 0.025 ($15.00, -0.5%)
26212
+
26213
+ \u{1F4A1} ETH is the only position losing. Rebalance into SUI?
26214
+
26215
+ When showing an error:
26216
+ \u274C Not enough funds. You have **$12.50** available but need $50.00. Try a smaller amount?`;
26034
26217
  }
26035
26218
  async function buildContextInjection(agent) {
26036
26219
  try {
@@ -26566,7 +26749,7 @@ var Logger = class _Logger {
26566
26749
  if (!existsSync(this.logDir)) mkdirSync(this.logDir, { recursive: true });
26567
26750
  this.logPath = join(this.logDir, LOG_FILE);
26568
26751
  this.level = opts?.level ?? "info";
26569
- this.toConsole = opts?.verbose ?? false;
26752
+ this.toConsole = true;
26570
26753
  }
26571
26754
  debug(msg, data) {
26572
26755
  this.write("debug", msg, data);
@@ -26738,11 +26921,6 @@ var Gateway = class _Gateway {
26738
26921
  };
26739
26922
  process.on("SIGINT", shutdown);
26740
26923
  process.on("SIGTERM", shutdown);
26741
- this.logger.info("Gateway ready", {
26742
- webchat: results.webchatUrl,
26743
- telegram: results.telegramConnected,
26744
- heartbeat: results.heartbeatTasks
26745
- });
26746
26924
  return results;
26747
26925
  }
26748
26926
  async startTelegram(tools, toolDefs, results) {
@@ -26752,13 +26930,32 @@ var Gateway = class _Gateway {
26752
26930
  allowedUsers: telegramConfig.allowedUsers ?? []
26753
26931
  });
26754
26932
  const loop = new AgentLoop({ agent: this.agent, llm: this.llm, tools, toolDefinitions: toolDefs });
26933
+ telegram.onStart(async (_userId) => {
26934
+ try {
26935
+ const balance = await this.agent.balance();
26936
+ return [
26937
+ "Welcome to t2000 \u2014 your AI financial advisor.\n",
26938
+ `\u{1F4B3} Checking: $${balance.available.toFixed(2)}`,
26939
+ `\u{1F3E6} Savings: $${balance.savings.toFixed(2)}`,
26940
+ `Net: $${(balance.available + balance.savings - balance.debt).toFixed(2)}`,
26941
+ "\nAsk me anything, or tap a button below."
26942
+ ].join("\n");
26943
+ } catch {
26944
+ return "Welcome to t2000 \u2014 your AI financial advisor.\n\nAsk me anything about your accounts.";
26945
+ }
26946
+ });
26755
26947
  telegram.onMessage(async (msg) => {
26756
26948
  if (this.agent.enforcer.getConfig().locked) {
26757
26949
  telegram.requestPin(msg.userId);
26758
26950
  await telegram.send(msg.userId, "Agent is locked. Enter your PIN to unlock.");
26759
26951
  return;
26760
26952
  }
26761
- await this.handleMessage(msg, loop, telegram);
26953
+ telegram.startTyping(msg.userId);
26954
+ try {
26955
+ await this.handleMessage(msg, loop, telegram);
26956
+ } finally {
26957
+ telegram.stopTyping(msg.userId);
26958
+ }
26762
26959
  });
26763
26960
  telegram.onPinUnlock(async (_pin) => {
26764
26961
  try {
@@ -26805,6 +27002,9 @@ var Gateway = class _Gateway {
26805
27002
  }
26806
27003
  async handleMessage(msg, loop, channel) {
26807
27004
  const isWebChat = channel instanceof WebChatChannel;
27005
+ const isTelegram = channel instanceof TelegramChannel;
27006
+ const startTime = Date.now();
27007
+ const queryPreview = msg.text.length > 40 ? msg.text.slice(0, 40) + "..." : msg.text;
26808
27008
  try {
26809
27009
  const response = await loop.processMessage(msg.text, {
26810
27010
  stream: isWebChat,
@@ -26818,30 +27018,58 @@ var Gateway = class _Gateway {
26818
27018
  channel.sendConfirmation(response.needsConfirmation.preview);
26819
27019
  }
26820
27020
  }
26821
- await channel.send(msg.userId, response.text);
26822
- const cost = this.estimateCost(response.usage);
26823
- this.logger.debug(`Message handled`, {
26824
- channel: channel.name,
26825
- tools: response.toolCalls.length,
26826
- inputTokens: response.usage.inputTokens,
26827
- outputTokens: response.usage.outputTokens,
26828
- cost: `$${cost.toFixed(4)}`
26829
- });
26830
- } catch (err) {
26831
- const errorMsg = err instanceof Error ? err.message : "Internal error";
26832
- if (this.isLLMError(err)) {
26833
- this.logger.error("LLM call failed", { error: errorMsg });
26834
- await channel.send(msg.userId, "AI is temporarily unavailable. Please try again in a moment.");
27021
+ if (isTelegram && response.needsConfirmation) {
27022
+ await channel.sendWithConfirmation(msg.userId, response.text);
26835
27023
  } else {
26836
- this.logger.error(`Message error: ${errorMsg}`);
26837
- await channel.send(msg.userId, `Sorry, something went wrong: ${errorMsg}`);
27024
+ await channel.send(msg.userId, response.text);
27025
+ }
27026
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
27027
+ const toolCount = response.toolCalls.length;
27028
+ const suffix = response.needsConfirmation ? "confirmation pending" : `${toolCount} tool${toolCount !== 1 ? "s" : ""}, ${elapsed}s`;
27029
+ this.logger.info(`${channel.id} \xB7 "${queryPreview}" \u2192 ${suffix}`);
27030
+ if (this.options.verbose) {
27031
+ const cost = this.estimateCost(response.usage);
27032
+ this.logger.debug(` tokens: ${response.usage.inputTokens}in/${response.usage.outputTokens}out, ~$${cost.toFixed(4)}`);
26838
27033
  }
27034
+ } catch (err) {
27035
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
27036
+ const friendlyMsg = this.friendlyError(err);
27037
+ this.logger.error(`${channel.id} \xB7 "${queryPreview}" \u2192 error (${elapsed}s): ${err instanceof Error ? err.message : String(err)}`);
27038
+ await channel.send(msg.userId, friendlyMsg);
26839
27039
  }
26840
27040
  }
26841
- isLLMError(err) {
26842
- if (!(err instanceof Error)) return false;
27041
+ friendlyError(err) {
27042
+ if (!(err instanceof Error)) return "Something went wrong. Try again?";
26843
27043
  const msg = err.message.toLowerCase();
26844
- return msg.includes("api") || msg.includes("rate limit") || msg.includes("429") || msg.includes("500") || msg.includes("503") || msg.includes("overloaded") || msg.includes("timeout");
27044
+ if (msg.includes("rate limit") || msg.includes("429") || msg.includes("overloaded")) {
27045
+ return "AI is busy. Try again in a moment.";
27046
+ }
27047
+ if (msg.includes("api") || msg.includes("500") || msg.includes("503") || msg.includes("timeout")) {
27048
+ return "AI is temporarily unavailable. Please try again in a moment.";
27049
+ }
27050
+ if (err instanceof T2000Error) {
27051
+ switch (err.code) {
27052
+ case "INSUFFICIENT_BALANCE":
27053
+ case "INSUFFICIENT_GAS":
27054
+ return `Not enough funds. ${err.message}`;
27055
+ case "SAFEGUARD_BLOCKED":
27056
+ return `${err.message}`;
27057
+ case "HEALTH_FACTOR_TOO_LOW":
27058
+ case "WITHDRAW_WOULD_LIQUIDATE":
27059
+ return "That would put your health factor below safe levels. Try a smaller amount.";
27060
+ case "SLIPPAGE_EXCEEDED":
27061
+ return "Price moved too much during the swap. Try again or increase slippage.";
27062
+ case "PROTOCOL_PAUSED":
27063
+ return "The protocol is temporarily paused. Try again later.";
27064
+ case "INVALID_ADDRESS":
27065
+ return "That address doesn't look right. Check it and try again.";
27066
+ case "INVALID_AMOUNT":
27067
+ return "Invalid amount. Please enter a positive number.";
27068
+ default:
27069
+ return `${err.message}`;
27070
+ }
27071
+ }
27072
+ return `Something went wrong: ${err.message}`;
26845
27073
  }
26846
27074
  estimateCost(usage) {
26847
27075
  if (this.llm.id === "anthropic") {
@@ -26879,4 +27107,4 @@ humanize-ms/index.js:
26879
27107
  * MIT Licensed
26880
27108
  *)
26881
27109
  */
26882
- //# sourceMappingURL=dist-E4QWKWVY.js.map
27110
+ //# sourceMappingURL=dist-QPASVL6D.js.map