@openacp/cli 0.2.24 → 0.2.25

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.
@@ -54,33 +54,35 @@ import { randomUUID } from "crypto";
54
54
  import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
55
55
  var log = createChildLogger({ module: "agent-instance" });
56
56
  function resolveAgentCommand(cmd) {
57
- const packageDirs = [
58
- path.resolve(
59
- process.cwd(),
60
- "node_modules",
61
- "@zed-industries",
62
- cmd,
63
- "dist",
64
- "index.js"
65
- ),
66
- path.resolve(process.cwd(), "node_modules", cmd, "dist", "index.js")
67
- ];
68
- for (const jsPath of packageDirs) {
69
- if (fs.existsSync(jsPath)) {
70
- return { command: process.execPath, args: [jsPath] };
71
- }
72
- }
73
- const localBin = path.resolve(process.cwd(), "node_modules", ".bin", cmd);
74
- if (fs.existsSync(localBin)) {
75
- const content = fs.readFileSync(localBin, "utf-8");
76
- if (content.startsWith("#!/usr/bin/env node")) {
77
- return { command: process.execPath, args: [localBin] };
78
- }
79
- const match = content.match(/"([^"]+\.js)"/);
80
- if (match) {
81
- const target = path.resolve(path.dirname(localBin), match[1]);
82
- if (fs.existsSync(target)) {
83
- return { command: process.execPath, args: [target] };
57
+ const searchRoots = [process.cwd()];
58
+ const ownDir = path.resolve(import.meta.dirname, "..", "..");
59
+ if (ownDir !== process.cwd()) {
60
+ searchRoots.push(ownDir);
61
+ }
62
+ for (const root of searchRoots) {
63
+ const packageDirs = [
64
+ path.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
65
+ path.resolve(root, "node_modules", cmd, "dist", "index.js")
66
+ ];
67
+ for (const jsPath of packageDirs) {
68
+ if (fs.existsSync(jsPath)) {
69
+ return { command: process.execPath, args: [jsPath] };
70
+ }
71
+ }
72
+ }
73
+ for (const root of searchRoots) {
74
+ const localBin = path.resolve(root, "node_modules", ".bin", cmd);
75
+ if (fs.existsSync(localBin)) {
76
+ const content = fs.readFileSync(localBin, "utf-8");
77
+ if (content.startsWith("#!/usr/bin/env node")) {
78
+ return { command: process.execPath, args: [localBin] };
79
+ }
80
+ const match = content.match(/"([^"]+\.js)"/);
81
+ if (match) {
82
+ const target = path.resolve(path.dirname(localBin), match[1]);
83
+ if (fs.existsSync(target)) {
84
+ return { command: process.execPath, args: [target] };
85
+ }
84
86
  }
85
87
  }
86
88
  }
@@ -1588,20 +1590,25 @@ function splitMessage(text, maxLength = 4096) {
1588
1590
  }
1589
1591
 
1590
1592
  // src/adapters/telegram/streaming.ts
1593
+ var nextDraftId = 1;
1591
1594
  var MessageDraft = class {
1592
- // 1 second throttle
1593
- constructor(bot, chatId, threadId) {
1595
+ // Only set in fallback mode (sendMessageDraft returns true, not Message)
1596
+ constructor(bot, chatId, threadId, throttleMs = 200, sendQueue) {
1594
1597
  this.bot = bot;
1595
1598
  this.chatId = chatId;
1596
1599
  this.threadId = threadId;
1600
+ this.sendQueue = sendQueue;
1601
+ this.draftId = nextDraftId++;
1602
+ this.minInterval = throttleMs;
1597
1603
  }
1598
- messageId;
1604
+ draftId;
1599
1605
  buffer = "";
1600
1606
  lastFlush = 0;
1601
1607
  flushTimer;
1602
1608
  flushPromise = Promise.resolve();
1603
- // serialize flushes
1604
- minInterval = 1e3;
1609
+ minInterval;
1610
+ useFallback = false;
1611
+ messageId;
1605
1612
  append(text) {
1606
1613
  this.buffer += text;
1607
1614
  this.scheduleFlush();
@@ -1626,34 +1633,48 @@ var MessageDraft = class {
1626
1633
  const html = markdownToTelegramHtml(this.buffer);
1627
1634
  const truncated = html.length > 4096 ? html.slice(0, 4090) + "\n..." : html;
1628
1635
  if (!truncated) return;
1636
+ if (this.useFallback) {
1637
+ await this.flushFallback(truncated);
1638
+ return;
1639
+ }
1640
+ try {
1641
+ await this.bot.api.sendMessageDraft(this.chatId, this.draftId, truncated, {
1642
+ message_thread_id: this.threadId,
1643
+ parse_mode: "HTML"
1644
+ });
1645
+ } catch {
1646
+ this.useFallback = true;
1647
+ this.minInterval = 1e3;
1648
+ await this.flushFallback(truncated);
1649
+ }
1650
+ }
1651
+ async flushFallback(html) {
1652
+ const exec = this.sendQueue ? (fn) => this.sendQueue.enqueue(fn) : (fn) => fn();
1629
1653
  try {
1630
1654
  if (!this.messageId) {
1631
- const msg = await this.bot.api.sendMessage(this.chatId, truncated, {
1632
- message_thread_id: this.threadId,
1633
- parse_mode: "HTML",
1634
- disable_notification: true
1635
- });
1655
+ const msg = await exec(
1656
+ () => this.bot.api.sendMessage(this.chatId, html, {
1657
+ message_thread_id: this.threadId,
1658
+ parse_mode: "HTML",
1659
+ disable_notification: true
1660
+ })
1661
+ );
1636
1662
  this.messageId = msg.message_id;
1637
1663
  } else {
1638
- await this.bot.api.editMessageText(
1639
- this.chatId,
1640
- this.messageId,
1641
- truncated,
1642
- {
1664
+ await exec(
1665
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
1643
1666
  parse_mode: "HTML"
1644
- }
1667
+ })
1645
1668
  );
1646
1669
  }
1647
1670
  } catch {
1648
1671
  try {
1649
1672
  if (!this.messageId) {
1650
- const msg = await this.bot.api.sendMessage(
1651
- this.chatId,
1652
- this.buffer.slice(0, 4096),
1653
- {
1673
+ const msg = await exec(
1674
+ () => this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1654
1675
  message_thread_id: this.threadId,
1655
1676
  disable_notification: true
1656
- }
1677
+ })
1657
1678
  );
1658
1679
  this.messageId = msg.message_id;
1659
1680
  }
@@ -1661,7 +1682,7 @@ var MessageDraft = class {
1661
1682
  }
1662
1683
  }
1663
1684
  }
1664
- async finalize(replyMarkup) {
1685
+ async finalize() {
1665
1686
  if (this.flushTimer) {
1666
1687
  clearTimeout(this.flushTimer);
1667
1688
  this.flushTimer = void 0;
@@ -1673,38 +1694,25 @@ var MessageDraft = class {
1673
1694
  try {
1674
1695
  for (let i = 0; i < chunks.length; i++) {
1675
1696
  const chunk = chunks[i];
1676
- const isLast = i === chunks.length - 1;
1677
- const markup = isLast && replyMarkup ? { reply_markup: replyMarkup } : {};
1678
1697
  if (i === 0 && this.messageId) {
1679
- await this.bot.api.editMessageText(
1680
- this.chatId,
1681
- this.messageId,
1682
- chunk,
1683
- {
1684
- parse_mode: "HTML",
1685
- ...markup
1686
- }
1687
- );
1698
+ await this.bot.api.editMessageText(this.chatId, this.messageId, chunk, {
1699
+ parse_mode: "HTML"
1700
+ });
1688
1701
  } else {
1689
1702
  const msg = await this.bot.api.sendMessage(this.chatId, chunk, {
1690
1703
  message_thread_id: this.threadId,
1691
1704
  parse_mode: "HTML",
1692
- disable_notification: true,
1693
- ...markup
1705
+ disable_notification: true
1694
1706
  });
1695
1707
  this.messageId = msg.message_id;
1696
1708
  }
1697
1709
  }
1698
1710
  } catch {
1699
1711
  try {
1700
- await this.bot.api.sendMessage(
1701
- this.chatId,
1702
- this.buffer.slice(0, 4096),
1703
- {
1704
- message_thread_id: this.threadId,
1705
- disable_notification: true
1706
- }
1707
- );
1712
+ await this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1713
+ message_thread_id: this.threadId,
1714
+ disable_notification: true
1715
+ });
1708
1716
  } catch {
1709
1717
  }
1710
1718
  }
@@ -1713,9 +1721,6 @@ var MessageDraft = class {
1713
1721
  getMessageId() {
1714
1722
  return this.messageId;
1715
1723
  }
1716
- getBuffer() {
1717
- return this.buffer;
1718
- }
1719
1724
  };
1720
1725
 
1721
1726
  // src/adapters/telegram/topics.ts
@@ -2190,6 +2195,39 @@ function redirectToAssistant(chatId, assistantTopicId) {
2190
2195
  return `\u{1F4AC} Please use the <a href="${link}">\u{1F916} Assistant</a> topic to chat with OpenACP.`;
2191
2196
  }
2192
2197
 
2198
+ // src/adapters/telegram/send-queue.ts
2199
+ var TelegramSendQueue = class {
2200
+ queue = Promise.resolve();
2201
+ lastExec = 0;
2202
+ minInterval;
2203
+ constructor(minInterval = 100) {
2204
+ this.minInterval = minInterval;
2205
+ }
2206
+ enqueue(fn) {
2207
+ let resolve;
2208
+ let reject;
2209
+ const resultPromise = new Promise((res, rej) => {
2210
+ resolve = res;
2211
+ reject = rej;
2212
+ });
2213
+ this.queue = this.queue.then(async () => {
2214
+ const elapsed = Date.now() - this.lastExec;
2215
+ if (elapsed < this.minInterval) {
2216
+ await new Promise((r) => setTimeout(r, this.minInterval - elapsed));
2217
+ }
2218
+ try {
2219
+ const result = await fn();
2220
+ resolve(result);
2221
+ } catch (err) {
2222
+ reject(err);
2223
+ } finally {
2224
+ this.lastExec = Date.now();
2225
+ }
2226
+ });
2227
+ return resultPromise;
2228
+ }
2229
+ };
2230
+
2193
2231
  // src/adapters/telegram/action-detect.ts
2194
2232
  import { nanoid as nanoid4 } from "nanoid";
2195
2233
  import { InlineKeyboard as InlineKeyboard3 } from "grammy";
@@ -2354,6 +2392,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2354
2392
  bot;
2355
2393
  telegramConfig;
2356
2394
  sessionDrafts = /* @__PURE__ */ new Map();
2395
+ sessionTextBuffers = /* @__PURE__ */ new Map();
2357
2396
  toolCallMessages = /* @__PURE__ */ new Map();
2358
2397
  // sessionId → (toolCallId → state)
2359
2398
  permissionHandler;
@@ -2362,6 +2401,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2362
2401
  assistantTopicId;
2363
2402
  skillMessages = /* @__PURE__ */ new Map();
2364
2403
  // sessionId → pinned messageId
2404
+ sendQueue = new TelegramSendQueue();
2365
2405
  constructor(core, config) {
2366
2406
  super(core, config);
2367
2407
  this.telegramConfig = config;
@@ -2372,6 +2412,22 @@ var TelegramAdapter = class extends ChannelAdapter {
2372
2412
  const rootCause = err.error instanceof Error ? err.error : err;
2373
2413
  log8.error({ err: rootCause }, "Telegram bot error");
2374
2414
  });
2415
+ this.bot.api.config.use(async (prev, method, payload, signal) => {
2416
+ const maxRetries = 3;
2417
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2418
+ const result = await prev(method, payload, signal);
2419
+ if (result.ok || result.error_code !== 429 || attempt === maxRetries) {
2420
+ return result;
2421
+ }
2422
+ const retryAfter = (result.parameters?.retry_after ?? 5) + 1;
2423
+ log8.warn(
2424
+ { method, retryAfter, attempt: attempt + 1 },
2425
+ "Rate limited by Telegram, retrying"
2426
+ );
2427
+ await new Promise((r) => setTimeout(r, retryAfter * 1e3));
2428
+ }
2429
+ return prev(method, payload, signal);
2430
+ });
2375
2431
  this.bot.api.config.use((prev, method, payload, signal) => {
2376
2432
  if (method === "getUpdates") {
2377
2433
  const p = payload;
@@ -2519,24 +2575,32 @@ Workspace: <code>${workspace}</code>
2519
2575
  draft = new MessageDraft(
2520
2576
  this.bot,
2521
2577
  this.telegramConfig.chatId,
2522
- threadId
2578
+ threadId,
2579
+ this.telegramConfig.streamThrottleMs,
2580
+ this.sendQueue
2523
2581
  );
2524
2582
  this.sessionDrafts.set(sessionId, draft);
2525
2583
  }
2526
2584
  draft.append(content.text);
2585
+ this.sessionTextBuffers.set(
2586
+ sessionId,
2587
+ (this.sessionTextBuffers.get(sessionId) ?? "") + content.text
2588
+ );
2527
2589
  break;
2528
2590
  }
2529
2591
  case "tool_call": {
2530
2592
  await this.finalizeDraft(sessionId);
2531
2593
  const meta = content.metadata;
2532
- const msg = await this.bot.api.sendMessage(
2533
- this.telegramConfig.chatId,
2534
- formatToolCall(meta),
2535
- {
2536
- message_thread_id: threadId,
2537
- parse_mode: "HTML",
2538
- disable_notification: true
2539
- }
2594
+ const msg = await this.sendQueue.enqueue(
2595
+ () => this.bot.api.sendMessage(
2596
+ this.telegramConfig.chatId,
2597
+ formatToolCall(meta),
2598
+ {
2599
+ message_thread_id: threadId,
2600
+ parse_mode: "HTML",
2601
+ disable_notification: true
2602
+ }
2603
+ )
2540
2604
  );
2541
2605
  if (!this.toolCallMessages.has(sessionId)) {
2542
2606
  this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
@@ -2562,11 +2626,13 @@ Workspace: <code>${workspace}</code>
2562
2626
  viewerLinks
2563
2627
  };
2564
2628
  try {
2565
- await this.bot.api.editMessageText(
2566
- this.telegramConfig.chatId,
2567
- toolState.msgId,
2568
- formatToolUpdate(merged),
2569
- { parse_mode: "HTML" }
2629
+ await this.sendQueue.enqueue(
2630
+ () => this.bot.api.editMessageText(
2631
+ this.telegramConfig.chatId,
2632
+ toolState.msgId,
2633
+ formatToolUpdate(merged),
2634
+ { parse_mode: "HTML" }
2635
+ )
2570
2636
  );
2571
2637
  } catch {
2572
2638
  }
@@ -2575,31 +2641,35 @@ Workspace: <code>${workspace}</code>
2575
2641
  }
2576
2642
  case "plan": {
2577
2643
  await this.finalizeDraft(sessionId);
2578
- await this.bot.api.sendMessage(
2579
- this.telegramConfig.chatId,
2580
- formatPlan(
2581
- content.metadata
2582
- ),
2583
- {
2584
- message_thread_id: threadId,
2585
- parse_mode: "HTML",
2586
- disable_notification: true
2587
- }
2644
+ await this.sendQueue.enqueue(
2645
+ () => this.bot.api.sendMessage(
2646
+ this.telegramConfig.chatId,
2647
+ formatPlan(
2648
+ content.metadata
2649
+ ),
2650
+ {
2651
+ message_thread_id: threadId,
2652
+ parse_mode: "HTML",
2653
+ disable_notification: true
2654
+ }
2655
+ )
2588
2656
  );
2589
2657
  break;
2590
2658
  }
2591
2659
  case "usage": {
2592
2660
  await this.finalizeDraft(sessionId);
2593
- await this.bot.api.sendMessage(
2594
- this.telegramConfig.chatId,
2595
- formatUsage(
2596
- content.metadata
2597
- ),
2598
- {
2599
- message_thread_id: threadId,
2600
- parse_mode: "HTML",
2601
- disable_notification: true
2602
- }
2661
+ await this.sendQueue.enqueue(
2662
+ () => this.bot.api.sendMessage(
2663
+ this.telegramConfig.chatId,
2664
+ formatUsage(
2665
+ content.metadata
2666
+ ),
2667
+ {
2668
+ message_thread_id: threadId,
2669
+ parse_mode: "HTML",
2670
+ disable_notification: true
2671
+ }
2672
+ )
2603
2673
  );
2604
2674
  break;
2605
2675
  }
@@ -2608,27 +2678,31 @@ Workspace: <code>${workspace}</code>
2608
2678
  this.sessionDrafts.delete(sessionId);
2609
2679
  this.toolCallMessages.delete(sessionId);
2610
2680
  await this.cleanupSkillCommands(sessionId);
2611
- await this.bot.api.sendMessage(
2612
- this.telegramConfig.chatId,
2613
- `\u2705 <b>Done</b>`,
2614
- {
2615
- message_thread_id: threadId,
2616
- parse_mode: "HTML",
2617
- disable_notification: true
2618
- }
2681
+ await this.sendQueue.enqueue(
2682
+ () => this.bot.api.sendMessage(
2683
+ this.telegramConfig.chatId,
2684
+ `\u2705 <b>Done</b>`,
2685
+ {
2686
+ message_thread_id: threadId,
2687
+ parse_mode: "HTML",
2688
+ disable_notification: true
2689
+ }
2690
+ )
2619
2691
  );
2620
2692
  break;
2621
2693
  }
2622
2694
  case "error": {
2623
2695
  await this.finalizeDraft(sessionId);
2624
- await this.bot.api.sendMessage(
2625
- this.telegramConfig.chatId,
2626
- `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
2627
- {
2628
- message_thread_id: threadId,
2629
- parse_mode: "HTML",
2630
- disable_notification: true
2631
- }
2696
+ await this.sendQueue.enqueue(
2697
+ () => this.bot.api.sendMessage(
2698
+ this.telegramConfig.chatId,
2699
+ `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
2700
+ {
2701
+ message_thread_id: threadId,
2702
+ parse_mode: "HTML",
2703
+ disable_notification: true
2704
+ }
2705
+ )
2632
2706
  );
2633
2707
  break;
2634
2708
  }
@@ -2640,7 +2714,9 @@ Workspace: <code>${workspace}</code>
2640
2714
  sessionId
2641
2715
  );
2642
2716
  if (!session) return;
2643
- await this.permissionHandler.sendPermissionRequest(session, request);
2717
+ await this.sendQueue.enqueue(
2718
+ () => this.permissionHandler.sendPermissionRequest(session, request)
2719
+ );
2644
2720
  }
2645
2721
  async sendNotification(notification) {
2646
2722
  log8.info(
@@ -2662,11 +2738,13 @@ Workspace: <code>${workspace}</code>
2662
2738
 
2663
2739
  <a href="${notification.deepLink}">\u2192 Go to message</a>`;
2664
2740
  }
2665
- await this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
2666
- message_thread_id: this.notificationTopicId,
2667
- parse_mode: "HTML",
2668
- disable_notification: false
2669
- });
2741
+ await this.sendQueue.enqueue(
2742
+ () => this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
2743
+ message_thread_id: this.notificationTopicId,
2744
+ parse_mode: "HTML",
2745
+ disable_notification: false
2746
+ })
2747
+ );
2670
2748
  }
2671
2749
  async createSessionThread(sessionId, name) {
2672
2750
  log8.info({ sessionId, name }, "Session topic created");
@@ -2714,15 +2792,17 @@ Workspace: <code>${workspace}</code>
2714
2792
  }
2715
2793
  }
2716
2794
  try {
2717
- const msg = await this.bot.api.sendMessage(
2718
- this.telegramConfig.chatId,
2719
- text,
2720
- {
2721
- message_thread_id: threadId,
2722
- parse_mode: "HTML",
2723
- reply_markup: keyboard,
2724
- disable_notification: true
2725
- }
2795
+ const msg = await this.sendQueue.enqueue(
2796
+ () => this.bot.api.sendMessage(
2797
+ this.telegramConfig.chatId,
2798
+ text,
2799
+ {
2800
+ message_thread_id: threadId,
2801
+ parse_mode: "HTML",
2802
+ reply_markup: keyboard,
2803
+ disable_notification: true
2804
+ }
2805
+ )
2726
2806
  );
2727
2807
  this.skillMessages.set(sessionId, msg.message_id);
2728
2808
  await this.bot.api.pinChatMessage(
@@ -2778,19 +2858,29 @@ Workspace: <code>${workspace}</code>
2778
2858
  async finalizeDraft(sessionId) {
2779
2859
  const draft = this.sessionDrafts.get(sessionId);
2780
2860
  if (!draft) return;
2781
- let keyboard;
2861
+ const finalMsgId = await draft.finalize();
2862
+ this.sessionDrafts.delete(sessionId);
2782
2863
  if (sessionId === this.assistantSession?.id) {
2783
- const fullText = draft.getBuffer();
2784
- if (fullText) {
2864
+ const fullText = this.sessionTextBuffers.get(sessionId);
2865
+ this.sessionTextBuffers.delete(sessionId);
2866
+ if (fullText && finalMsgId) {
2785
2867
  const detected = detectAction(fullText);
2786
2868
  if (detected) {
2787
2869
  const actionId = storeAction(detected);
2788
- keyboard = buildActionKeyboard(actionId, detected);
2870
+ const keyboard = buildActionKeyboard(actionId, detected);
2871
+ try {
2872
+ await this.bot.api.editMessageReplyMarkup(
2873
+ this.telegramConfig.chatId,
2874
+ finalMsgId,
2875
+ { reply_markup: keyboard }
2876
+ );
2877
+ } catch {
2878
+ }
2789
2879
  }
2790
2880
  }
2881
+ } else {
2882
+ this.sessionTextBuffers.delete(sessionId);
2791
2883
  }
2792
- await draft.finalize(keyboard);
2793
- this.sessionDrafts.delete(sessionId);
2794
2884
  }
2795
2885
  };
2796
2886
 
@@ -2808,4 +2898,4 @@ export {
2808
2898
  ApiServer,
2809
2899
  TelegramAdapter
2810
2900
  };
2811
- //# sourceMappingURL=chunk-IX63F4JG.js.map
2901
+ //# sourceMappingURL=chunk-S5MPFOR3.js.map