@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
|
|
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 =
|
|
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="
|
|
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
|
-
|
|
25376
|
-
.replace(
|
|
25377
|
-
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
25415
|
+
.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>')
|
|
25383
25416
|
.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
|
|
25384
|
-
.replace(
|
|
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
|
|
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: "
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
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
|
|
26021
|
-
- Show
|
|
26022
|
-
- For read-only queries (balance, rates, portfolio), respond immediately
|
|
26023
|
-
-
|
|
26024
|
-
|
|
26025
|
-
|
|
26026
|
-
-
|
|
26027
|
-
-
|
|
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
|
-
-
|
|
26031
|
-
- Brief \u2014
|
|
26032
|
-
-
|
|
26033
|
-
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
26822
|
-
|
|
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
|
-
|
|
26837
|
-
|
|
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
|
-
|
|
26842
|
-
if (!(err instanceof Error)) return
|
|
27041
|
+
friendlyError(err) {
|
|
27042
|
+
if (!(err instanceof Error)) return "Something went wrong. Try again?";
|
|
26843
27043
|
const msg = err.message.toLowerCase();
|
|
26844
|
-
|
|
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-
|
|
27110
|
+
//# sourceMappingURL=dist-QPASVL6D.js.map
|