@emqo/claudebridge 0.2.3 → 0.3.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.
@@ -12,12 +12,16 @@ export declare class TelegramAdapter implements Adapter {
12
12
  private reminderTimer?;
13
13
  private autoTimer?;
14
14
  private autoRunning;
15
+ private pages;
16
+ private static PAGE_TTL;
15
17
  constructor(engine: AgentEngine, store: Store, config: TelegramConfig, locale?: string);
16
18
  private get api();
17
19
  private call;
18
20
  private reply;
19
21
  private editMsg;
20
22
  private handleUpdate;
23
+ private pageKeyboard;
24
+ private handlePageCallback;
21
25
  private handlePrompt;
22
26
  start(): Promise<void>;
23
27
  stop(): void;
@@ -13,6 +13,8 @@ export class TelegramAdapter {
13
13
  reminderTimer;
14
14
  autoTimer;
15
15
  autoRunning = false;
16
+ pages = new Map();
17
+ static PAGE_TTL = 30 * 60 * 1000; // 30 minutes
16
18
  constructor(engine, store, config, locale = "en") {
17
19
  this.engine = engine;
18
20
  this.store = store;
@@ -35,10 +37,16 @@ export class TelegramAdapter {
35
37
  });
36
38
  clearTimeout(timer);
37
39
  const json = await res.json();
40
+ if (!json.ok) {
41
+ console.error(`[telegram] API error ${method}:`, json.description || json);
42
+ const err = new Error(json.description || `Telegram API error: ${method}`);
43
+ err.apiError = true;
44
+ throw err;
45
+ }
38
46
  return json.result;
39
47
  }
40
48
  catch (err) {
41
- if (i === 2)
49
+ if (err.apiError || i === 2)
42
50
  throw err;
43
51
  await new Promise(r => setTimeout(r, 1000 * (i + 1)));
44
52
  }
@@ -63,6 +71,10 @@ export class TelegramAdapter {
63
71
  catch { }
64
72
  }
65
73
  async handleUpdate(update) {
74
+ if (update.callback_query) {
75
+ await this.handlePageCallback(update.callback_query);
76
+ return;
77
+ }
66
78
  const msg = update.message;
67
79
  if (!msg)
68
80
  return;
@@ -162,6 +174,71 @@ export class TelegramAdapter {
162
174
  if (text)
163
175
  await this.handlePrompt(chatId, String(uid), text);
164
176
  }
177
+ pageKeyboard(chatId, msgId, cur, total) {
178
+ const btns = [];
179
+ if (cur > 0)
180
+ btns.push({ text: "◀", callback_data: `p:${chatId}:${msgId}:${cur - 1}` });
181
+ btns.push({ text: `${cur + 1} / ${total}`, callback_data: "noop" });
182
+ if (cur < total - 1)
183
+ btns.push({ text: "▶", callback_data: `p:${chatId}:${msgId}:${cur + 1}` });
184
+ return { inline_keyboard: [btns] };
185
+ }
186
+ async handlePageCallback(cb) {
187
+ const data = cb.data || "";
188
+ const cbId = cb.id;
189
+ // Always answer callback to remove loading spinner
190
+ const answer = (text) => this.call("answerCallbackQuery", { callback_query_id: cbId, ...(text ? { text, show_alert: false } : {}) });
191
+ if (data === "noop") {
192
+ await answer();
193
+ return;
194
+ }
195
+ if (!data.startsWith("p:")) {
196
+ await answer();
197
+ return;
198
+ }
199
+ const parts = data.split(":");
200
+ if (parts.length !== 4) {
201
+ await answer();
202
+ return;
203
+ }
204
+ const chatId = Number(parts[1]);
205
+ const msgId = Number(parts[2]);
206
+ const page = Number(parts[3]);
207
+ const key = `${chatId}:${msgId}`;
208
+ const entry = this.pages.get(key);
209
+ if (!entry || Date.now() - entry.ts > TelegramAdapter.PAGE_TTL) {
210
+ this.pages.delete(key);
211
+ await answer(t(this.locale, "page_expired"));
212
+ return;
213
+ }
214
+ if (page < 0 || page >= entry.chunks.length) {
215
+ await answer();
216
+ return;
217
+ }
218
+ const keyboard = this.pageKeyboard(chatId, msgId, page, entry.chunks.length);
219
+ try {
220
+ await this.call("editMessageText", {
221
+ chat_id: chatId,
222
+ message_id: msgId,
223
+ text: entry.chunks[page],
224
+ parse_mode: "MarkdownV2",
225
+ reply_markup: keyboard,
226
+ });
227
+ }
228
+ catch {
229
+ // MarkdownV2 failed, fallback to raw text
230
+ try {
231
+ await this.call("editMessageText", {
232
+ chat_id: chatId,
233
+ message_id: msgId,
234
+ text: entry.raw[page],
235
+ reply_markup: keyboard,
236
+ });
237
+ }
238
+ catch { }
239
+ }
240
+ await answer();
241
+ }
165
242
  async handlePrompt(chatId, uid, text) {
166
243
  if (this.engine.isLocked(uid)) {
167
244
  await this.reply(chatId, t(this.locale, "still_processing"));
@@ -183,19 +260,43 @@ export class TelegramAdapter {
183
260
  console.log(`[telegram] claude done for ${uid}, cost=$${res.cost?.toFixed(4)}`);
184
261
  const maxLen = this.config.chunk_size || 4000;
185
262
  const md = toTelegramMarkdown(res.text);
186
- const chunks = chunkText(md, maxLen);
187
- try {
188
- await this.editMsg(chatId, msgId, chunks[0], "MarkdownV2");
189
- }
190
- catch {
191
- await this.editMsg(chatId, msgId, res.text);
263
+ const mdChunks = chunkText(md, maxLen);
264
+ const rawChunks = chunkText(res.text, maxLen);
265
+ if (mdChunks.length <= 1) {
266
+ // Single page — no pagination needed
267
+ try {
268
+ await this.editMsg(chatId, msgId, mdChunks[0], "MarkdownV2");
269
+ }
270
+ catch {
271
+ await this.editMsg(chatId, msgId, res.text);
272
+ }
192
273
  }
193
- for (let i = 1; i < chunks.length; i++) {
274
+ else {
275
+ // Multi-page — store pages and show inline keyboard
276
+ const key = `${chatId}:${msgId}`;
277
+ this.pages.set(key, { chunks: mdChunks, raw: rawChunks, ts: Date.now() });
278
+ setTimeout(() => this.pages.delete(key), TelegramAdapter.PAGE_TTL);
279
+ const keyboard = this.pageKeyboard(chatId, msgId, 0, mdChunks.length);
194
280
  try {
195
- await this.reply(chatId, chunks[i], "MarkdownV2");
281
+ await this.call("editMessageText", {
282
+ chat_id: chatId,
283
+ message_id: msgId,
284
+ text: mdChunks[0],
285
+ parse_mode: "MarkdownV2",
286
+ reply_markup: keyboard,
287
+ });
196
288
  }
197
289
  catch {
198
- await this.reply(chatId, chunks[i]);
290
+ // MarkdownV2 failed, fallback to raw text
291
+ try {
292
+ await this.call("editMessageText", {
293
+ chat_id: chatId,
294
+ message_id: msgId,
295
+ text: rawChunks[0],
296
+ reply_markup: keyboard,
297
+ });
298
+ }
299
+ catch { }
199
300
  }
200
301
  }
201
302
  }
package/dist/cli.js CHANGED
File without changes
package/dist/core/i18n.js CHANGED
@@ -13,6 +13,7 @@ const messages = {
13
13
  auto_starting: "🤖 Auto task #{id} starting:\n{desc}",
14
14
  auto_done: "✅ Auto task #{id} done (cost: ${cost}):",
15
15
  auto_failed: "❌ Auto task #{id} failed: {err}",
16
+ page_expired: "Page expired. Please resend your question.",
16
17
  },
17
18
  zh: {
18
19
  help: "ClaudeBridge 就绪。\n\n管理命令:\n/new - 清除会话\n/usage - 你的用量\n/allusage - 所有用量\n/history - 最近对话\n/model - 端点信息\n/reload - 重载配置\n/help - 显示帮助\n\n直接对话即可管理记忆、任务、提醒等 — Claude 会自动处理。",
@@ -28,6 +29,7 @@ const messages = {
28
29
  auto_starting: "🤖 自动任务 #{id} 开始执行:\n{desc}",
29
30
  auto_done: "✅ 自动任务 #{id} 完成(花费:${cost}):",
30
31
  auto_failed: "❌ 自动任务 #{id} 失败:{err}",
32
+ page_expired: "页面已过期,请重新发送问题。",
31
33
  },
32
34
  };
33
35
  const commandDescriptions = {
package/dist/ctl.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emqo/claudebridge",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Bridge Claude Code Agent SDK to chat platforms (Telegram, Discord, etc.)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {