@openacp/cli 0.4.6 → 0.4.8

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 (35) hide show
  1. package/README.md +8 -8
  2. package/dist/{chunk-IUPHAXGA.js → chunk-6MJLVZXV.js} +3 -3
  3. package/dist/{chunk-2M4O7AFI.js → chunk-BBPWAWE3.js} +392 -37
  4. package/dist/chunk-BBPWAWE3.js.map +1 -0
  5. package/dist/{chunk-3QACY5E3.js → chunk-C6YIUTGR.js} +2 -2
  6. package/dist/{chunk-2SY7Y2VB.js → chunk-HZD3CGPK.js} +2 -2
  7. package/dist/{chunk-ARWC4S35.js → chunk-UAUTLC4E.js} +13 -7
  8. package/dist/{chunk-ARWC4S35.js.map → chunk-UAUTLC4E.js.map} +1 -1
  9. package/dist/{chunk-WF5XDN4D.js → chunk-ZRFBLD3W.js} +6 -2
  10. package/dist/chunk-ZRFBLD3W.js.map +1 -0
  11. package/dist/cli.js +37 -31
  12. package/dist/cli.js.map +1 -1
  13. package/dist/{config-J5YQOMDU.js → config-H2DSEHNW.js} +2 -2
  14. package/dist/config-editor-SKS4LJLT.js +11 -0
  15. package/dist/{daemon-SLGQGRKO.js → daemon-VF6HJQXD.js} +3 -3
  16. package/dist/index.d.ts +18 -0
  17. package/dist/index.js +6 -6
  18. package/dist/integrate-WUPLRJD3.js +145 -0
  19. package/dist/integrate-WUPLRJD3.js.map +1 -0
  20. package/dist/{main-3POGUQPY.js → main-NV7YN3VY.js} +11 -11
  21. package/dist/{setup-CEDO6VWV.js → setup-FCVL75K6.js} +3 -3
  22. package/package.json +1 -1
  23. package/dist/chunk-2M4O7AFI.js.map +0 -1
  24. package/dist/chunk-WF5XDN4D.js.map +0 -1
  25. package/dist/config-editor-EPOKAEP6.js +0 -11
  26. package/dist/integrate-HYDSHAF3.js +0 -123
  27. package/dist/integrate-HYDSHAF3.js.map +0 -1
  28. /package/dist/{chunk-IUPHAXGA.js.map → chunk-6MJLVZXV.js.map} +0 -0
  29. /package/dist/{chunk-3QACY5E3.js.map → chunk-C6YIUTGR.js.map} +0 -0
  30. /package/dist/{chunk-2SY7Y2VB.js.map → chunk-HZD3CGPK.js.map} +0 -0
  31. /package/dist/{config-J5YQOMDU.js.map → config-H2DSEHNW.js.map} +0 -0
  32. /package/dist/{config-editor-EPOKAEP6.js.map → config-editor-SKS4LJLT.js.map} +0 -0
  33. /package/dist/{daemon-SLGQGRKO.js.map → daemon-VF6HJQXD.js.map} +0 -0
  34. /package/dist/{main-3POGUQPY.js.map → main-NV7YN3VY.js.map} +0 -0
  35. /package/dist/{setup-CEDO6VWV.js.map → setup-FCVL75K6.js.map} +0 -0
package/README.md CHANGED
@@ -109,24 +109,24 @@ Once OpenACP is running, control it from Telegram:
109
109
 
110
110
  Each session gets its own forum topic. The agent streams responses in real time, shows tool calls, and asks for permission when needed.
111
111
 
112
- ### Session Handoff
112
+ ### Session Transfer
113
113
 
114
- Transfer sessions between Claude CLI and Telegram:
114
+ Move sessions between your terminal and Telegram:
115
115
 
116
- **CLI → Telegram:**
116
+ **Terminal → Telegram:**
117
117
  ```bash
118
- # Install Claude CLI integration (one-time)
118
+ # Install integration (one-time)
119
119
  openacp integrate claude
120
120
 
121
- # In Claude CLI, type /openacp:handoff to transfer current session
121
+ # In Claude CLI, type /openacp:handoff to transfer the current session
122
122
  # Or manually:
123
123
  openacp adopt claude <session_id> --cwd /path/to/project
124
124
  ```
125
125
 
126
- **Telegram → CLI:**
127
- Type `/handoff` in any session topic. The bot will reply with the `claude --resume` command to paste in your terminal.
126
+ **Telegram → Terminal:**
127
+ Type `/handoff` in any session topic. The bot replies with a command you can paste in your terminal to continue.
128
128
 
129
- Sessions are not locked after handoff — you can continue from either side.
129
+ Sessions are not locked after transfer — you can continue from either side.
130
130
 
131
131
  ## Roadmap
132
132
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  validateBotToken,
3
3
  validateChatId
4
- } from "./chunk-ARWC4S35.js";
4
+ } from "./chunk-UAUTLC4E.js";
5
5
  import {
6
6
  expandHome
7
- } from "./chunk-WF5XDN4D.js";
7
+ } from "./chunk-ZRFBLD3W.js";
8
8
  import {
9
9
  installAutoStart,
10
10
  isAutoStartInstalled,
@@ -524,4 +524,4 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
524
524
  export {
525
525
  runConfigEditor
526
526
  };
527
- //# sourceMappingURL=chunk-IUPHAXGA.js.map
527
+ //# sourceMappingURL=chunk-6MJLVZXV.js.map
@@ -830,6 +830,7 @@ var SessionManager = class {
830
830
  createdAt: session.createdAt.toISOString(),
831
831
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
832
832
  name: session.name,
833
+ dangerousMode: false,
833
834
  platform: {}
834
835
  });
835
836
  }
@@ -2016,7 +2017,7 @@ var ApiServer = class {
2016
2017
  return;
2017
2018
  }
2018
2019
  target[lastKey] = value;
2019
- const { ConfigSchema } = await import("./config-J5YQOMDU.js");
2020
+ const { ConfigSchema } = await import("./config-H2DSEHNW.js");
2020
2021
  const result = ConfigSchema.safeParse(cloned);
2021
2022
  if (!result.success) {
2022
2023
  this.sendJson(res, 400, {
@@ -2418,7 +2419,7 @@ function formatUsage(usage) {
2418
2419
  return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
2419
2420
  ${bar} ${pct}%`;
2420
2421
  }
2421
- function splitMessage(text, maxLength = 4096) {
2422
+ function splitMessage(text, maxLength = 3800) {
2422
2423
  if (text.length <= maxLength) return [text];
2423
2424
  const chunks = [];
2424
2425
  let remaining = text;
@@ -2428,14 +2429,23 @@ function splitMessage(text, maxLength = 4096) {
2428
2429
  break;
2429
2430
  }
2430
2431
  let splitAt = remaining.lastIndexOf("\n\n", maxLength);
2431
- if (splitAt === -1 || splitAt < maxLength * 0.5) {
2432
+ if (splitAt === -1 || splitAt < maxLength * 0.2) {
2432
2433
  splitAt = remaining.lastIndexOf("\n", maxLength);
2433
2434
  }
2434
- if (splitAt === -1 || splitAt < maxLength * 0.5) {
2435
+ if (splitAt === -1 || splitAt < maxLength * 0.2) {
2435
2436
  splitAt = maxLength;
2436
2437
  }
2438
+ const candidate = remaining.slice(0, splitAt);
2439
+ const fences = candidate.match(/```/g);
2440
+ if (fences && fences.length % 2 !== 0) {
2441
+ const closingFence = remaining.indexOf("```", splitAt);
2442
+ if (closingFence !== -1) {
2443
+ const afterFence = remaining.indexOf("\n", closingFence + 3);
2444
+ splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
2445
+ }
2446
+ }
2437
2447
  chunks.push(remaining.slice(0, splitAt));
2438
- remaining = remaining.slice(splitAt).trimStart();
2448
+ remaining = remaining.slice(splitAt).replace(/^\n+/, "");
2439
2449
  }
2440
2450
  return chunks;
2441
2451
  }
@@ -2472,14 +2482,22 @@ var MessageDraft = class {
2472
2482
  async flush() {
2473
2483
  if (!this.buffer) return;
2474
2484
  if (this.firstFlushPending) return;
2475
- const html = markdownToTelegramHtml(this.buffer);
2476
- const truncated = html.length > 4096 ? html.slice(0, 4090) + "\n..." : html;
2477
- if (!truncated) return;
2485
+ let displayBuffer = this.buffer;
2486
+ if (displayBuffer.length > 3800) {
2487
+ let cutAt = displayBuffer.lastIndexOf("\n", 3800);
2488
+ if (cutAt < 800) cutAt = 3800;
2489
+ displayBuffer = displayBuffer.slice(0, cutAt) + "\n\u2026";
2490
+ }
2491
+ let html = markdownToTelegramHtml(displayBuffer);
2492
+ if (!html) return;
2493
+ if (html.length > 4096) {
2494
+ html = html.slice(0, 4090) + "\n\u2026";
2495
+ }
2478
2496
  if (!this.messageId) {
2479
2497
  this.firstFlushPending = true;
2480
2498
  try {
2481
2499
  const result = await this.sendQueue.enqueue(
2482
- () => this.bot.api.sendMessage(this.chatId, truncated, {
2500
+ () => this.bot.api.sendMessage(this.chatId, html, {
2483
2501
  message_thread_id: this.threadId,
2484
2502
  parse_mode: "HTML",
2485
2503
  disable_notification: true
@@ -2497,7 +2515,7 @@ var MessageDraft = class {
2497
2515
  } else {
2498
2516
  try {
2499
2517
  await this.sendQueue.enqueue(
2500
- () => this.bot.api.editMessageText(this.chatId, this.messageId, truncated, {
2518
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
2501
2519
  parse_mode: "HTML"
2502
2520
  }),
2503
2521
  { type: "text", key: this.sessionId }
@@ -2517,21 +2535,20 @@ var MessageDraft = class {
2517
2535
  if (this.messageId && this.buffer === this.lastSentBuffer) {
2518
2536
  return this.messageId;
2519
2537
  }
2520
- const html = markdownToTelegramHtml(this.buffer);
2521
- const chunks = splitMessage(html);
2522
- try {
2523
- for (let i = 0; i < chunks.length; i++) {
2524
- const chunk = chunks[i];
2538
+ const mdChunks = splitMessage(this.buffer);
2539
+ for (let i = 0; i < mdChunks.length; i++) {
2540
+ const html = markdownToTelegramHtml(mdChunks[i]);
2541
+ try {
2525
2542
  if (i === 0 && this.messageId) {
2526
2543
  await this.sendQueue.enqueue(
2527
- () => this.bot.api.editMessageText(this.chatId, this.messageId, chunk, {
2544
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
2528
2545
  parse_mode: "HTML"
2529
2546
  }),
2530
2547
  { type: "other" }
2531
2548
  );
2532
2549
  } else {
2533
2550
  const msg = await this.sendQueue.enqueue(
2534
- () => this.bot.api.sendMessage(this.chatId, chunk, {
2551
+ () => this.bot.api.sendMessage(this.chatId, html, {
2535
2552
  message_thread_id: this.threadId,
2536
2553
  parse_mode: "HTML",
2537
2554
  disable_notification: true
@@ -2542,17 +2559,25 @@ var MessageDraft = class {
2542
2559
  this.messageId = msg.message_id;
2543
2560
  }
2544
2561
  }
2545
- }
2546
- } catch {
2547
- if (this.buffer !== this.lastSentBuffer) {
2562
+ } catch {
2548
2563
  try {
2549
- await this.sendQueue.enqueue(
2550
- () => this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
2551
- message_thread_id: this.threadId,
2552
- disable_notification: true
2553
- }),
2554
- { type: "other" }
2555
- );
2564
+ if (i === 0 && this.messageId) {
2565
+ await this.sendQueue.enqueue(
2566
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
2567
+ { type: "other" }
2568
+ );
2569
+ } else {
2570
+ const msg = await this.sendQueue.enqueue(
2571
+ () => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
2572
+ message_thread_id: this.threadId,
2573
+ disable_notification: true
2574
+ }),
2575
+ { type: "other" }
2576
+ );
2577
+ if (msg) {
2578
+ this.messageId = msg.message_id;
2579
+ }
2580
+ }
2556
2581
  } catch {
2557
2582
  }
2558
2583
  }
@@ -2603,6 +2628,7 @@ function setupCommands(bot, core, chatId, assistant) {
2603
2628
  bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
2604
2629
  bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
2605
2630
  bot.command("status", (ctx) => handleStatus(ctx, core));
2631
+ bot.command("sessions", (ctx) => handleTopics(ctx, core));
2606
2632
  bot.command("agents", (ctx) => handleAgents(ctx, core));
2607
2633
  bot.command("help", (ctx) => handleHelp(ctx));
2608
2634
  bot.command("menu", (ctx) => handleMenu(ctx));
@@ -2610,11 +2636,12 @@ function setupCommands(bot, core, chatId, assistant) {
2610
2636
  bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
2611
2637
  bot.command("restart", (ctx) => handleRestart(ctx, core));
2612
2638
  bot.command("update", (ctx) => handleUpdate(ctx, core));
2639
+ bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
2613
2640
  }
2614
2641
  function buildMenuKeyboard() {
2615
- return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
2642
+ return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F4CB} Sessions", "m:topics").text("\u{1F916} Agents", "m:agents").row().text("\u{1F517} Integrate", "m:integrate").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
2616
2643
  }
2617
- function setupMenuCallbacks(bot, core, chatId) {
2644
+ function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
2618
2645
  bot.callbackQuery(/^m:/, async (ctx) => {
2619
2646
  const data = ctx.callbackQuery.data;
2620
2647
  try {
@@ -2646,6 +2673,27 @@ function setupMenuCallbacks(bot, core, chatId) {
2646
2673
  case "m:update":
2647
2674
  await handleUpdate(ctx, core);
2648
2675
  break;
2676
+ case "m:integrate":
2677
+ await handleIntegrate(ctx, core);
2678
+ break;
2679
+ case "m:topics":
2680
+ await handleTopics(ctx, core);
2681
+ break;
2682
+ case "m:cleanup:finished":
2683
+ await handleCleanup(ctx, core, chatId, ["finished"]);
2684
+ break;
2685
+ case "m:cleanup:errors":
2686
+ await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
2687
+ break;
2688
+ case "m:cleanup:all":
2689
+ await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
2690
+ break;
2691
+ case "m:cleanup:everything":
2692
+ await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
2693
+ break;
2694
+ case "m:cleanup:everything:confirm":
2695
+ await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
2696
+ break;
2649
2697
  }
2650
2698
  });
2651
2699
  }
@@ -2870,6 +2918,188 @@ Total sessions: ${sessions.length}`,
2870
2918
  );
2871
2919
  }
2872
2920
  }
2921
+ async function handleTopics(ctx, core) {
2922
+ try {
2923
+ const allRecords = core.sessionManager.listRecords();
2924
+ const records = allRecords.filter((r) => {
2925
+ const platform = r.platform;
2926
+ return !!platform?.topicId;
2927
+ });
2928
+ const headlessCount = allRecords.length - records.length;
2929
+ if (records.length === 0) {
2930
+ const extra = headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "";
2931
+ await ctx.reply(`No sessions with topics found.${extra}`, { parse_mode: "HTML" });
2932
+ return;
2933
+ }
2934
+ const statusEmoji = {
2935
+ active: "\u{1F7E2}",
2936
+ initializing: "\u{1F7E1}",
2937
+ finished: "\u2705",
2938
+ error: "\u274C",
2939
+ cancelled: "\u26D4"
2940
+ };
2941
+ const statusOrder = { active: 0, initializing: 1, error: 2, finished: 3, cancelled: 4 };
2942
+ records.sort((a, b) => (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5));
2943
+ const MAX_DISPLAY = 30;
2944
+ const displayed = records.slice(0, MAX_DISPLAY);
2945
+ const lines = displayed.map((r) => {
2946
+ const emoji = statusEmoji[r.status] || "\u26AA";
2947
+ const name = r.name?.trim();
2948
+ const label = name ? escapeHtml(name) : `<i>${escapeHtml(r.agentName)} session</i>`;
2949
+ return `${emoji} ${label} <code>[${r.status}]</code>`;
2950
+ });
2951
+ const header = `<b>Sessions: ${records.length}</b>` + (headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "");
2952
+ const truncated = records.length > MAX_DISPLAY ? `
2953
+
2954
+ <i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
2955
+ const finishedCount = records.filter((r) => r.status === "finished").length;
2956
+ const errorCount = records.filter((r) => r.status === "error" || r.status === "cancelled").length;
2957
+ const activeCount = records.filter((r) => r.status === "active" || r.status === "initializing").length;
2958
+ const keyboard = new InlineKeyboard();
2959
+ if (finishedCount > 0) {
2960
+ keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
2961
+ }
2962
+ if (errorCount > 0) {
2963
+ keyboard.text(`Cleanup errors (${errorCount})`, "m:cleanup:errors").row();
2964
+ }
2965
+ if (finishedCount + errorCount > 0) {
2966
+ keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
2967
+ }
2968
+ keyboard.text(`\u26A0\uFE0F Cleanup ALL (${records.length})`, "m:cleanup:everything").row();
2969
+ keyboard.text("Refresh", "m:topics");
2970
+ await ctx.reply(
2971
+ `${header}
2972
+
2973
+ ${lines.join("\n")}${truncated}`,
2974
+ { parse_mode: "HTML", reply_markup: keyboard }
2975
+ );
2976
+ } catch (err) {
2977
+ log8.error({ err }, "handleTopics error");
2978
+ await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
2979
+ });
2980
+ }
2981
+ }
2982
+ async function handleCleanup(ctx, core, chatId, statuses) {
2983
+ const allRecords = core.sessionManager.listRecords();
2984
+ const cleanable = allRecords.filter((r) => {
2985
+ const platform = r.platform;
2986
+ return !!platform?.topicId && statuses.includes(r.status);
2987
+ });
2988
+ if (cleanable.length === 0) {
2989
+ await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
2990
+ return;
2991
+ }
2992
+ let deleted = 0;
2993
+ let failed = 0;
2994
+ for (const record of cleanable) {
2995
+ try {
2996
+ const topicId = record.platform?.topicId;
2997
+ if (topicId) {
2998
+ try {
2999
+ await ctx.api.deleteForumTopic(chatId, topicId);
3000
+ } catch (err) {
3001
+ log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3002
+ }
3003
+ }
3004
+ await core.sessionManager.removeRecord(record.sessionId);
3005
+ deleted++;
3006
+ } catch (err) {
3007
+ log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3008
+ failed++;
3009
+ }
3010
+ }
3011
+ await ctx.reply(
3012
+ `\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
3013
+ { parse_mode: "HTML" }
3014
+ );
3015
+ }
3016
+ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
3017
+ const allRecords = core.sessionManager.listRecords();
3018
+ const cleanable = allRecords.filter((r) => {
3019
+ const platform = r.platform;
3020
+ if (!platform?.topicId) return false;
3021
+ if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
3022
+ return true;
3023
+ });
3024
+ if (cleanable.length === 0) {
3025
+ await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
3026
+ return;
3027
+ }
3028
+ const statusCounts = /* @__PURE__ */ new Map();
3029
+ for (const r of cleanable) {
3030
+ statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
3031
+ }
3032
+ const statusEmoji = {
3033
+ active: "\u{1F7E2}",
3034
+ initializing: "\u{1F7E1}",
3035
+ finished: "\u2705",
3036
+ error: "\u274C",
3037
+ cancelled: "\u26D4"
3038
+ };
3039
+ const breakdown = Array.from(statusCounts.entries()).map(([status, count]) => `${statusEmoji[status] ?? "\u26AA"} ${status}: ${count}`).join("\n");
3040
+ const activeCount = (statusCounts.get("active") ?? 0) + (statusCounts.get("initializing") ?? 0);
3041
+ const activeWarning = activeCount > 0 ? `
3042
+
3043
+ \u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
3044
+ const keyboard = new InlineKeyboard().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
3045
+ await ctx.reply(
3046
+ `<b>Delete ${cleanable.length} topics?</b>
3047
+
3048
+ This will:
3049
+ \u2022 Delete all session topics from this group
3050
+ \u2022 Cancel any running agent sessions
3051
+ \u2022 Remove all session records
3052
+
3053
+ <b>Breakdown:</b>
3054
+ ${breakdown}${activeWarning}
3055
+
3056
+ <i>Notifications and Assistant topics will NOT be deleted.</i>`,
3057
+ { parse_mode: "HTML", reply_markup: keyboard }
3058
+ );
3059
+ }
3060
+ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds) {
3061
+ const allRecords = core.sessionManager.listRecords();
3062
+ const cleanable = allRecords.filter((r) => {
3063
+ const platform = r.platform;
3064
+ if (!platform?.topicId) return false;
3065
+ if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
3066
+ return true;
3067
+ });
3068
+ if (cleanable.length === 0) {
3069
+ await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
3070
+ return;
3071
+ }
3072
+ let deleted = 0;
3073
+ let failed = 0;
3074
+ for (const record of cleanable) {
3075
+ try {
3076
+ if (record.status === "active" || record.status === "initializing") {
3077
+ try {
3078
+ await core.sessionManager.cancelSession(record.sessionId);
3079
+ } catch (err) {
3080
+ log8.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
3081
+ }
3082
+ }
3083
+ const topicId = record.platform?.topicId;
3084
+ if (topicId) {
3085
+ try {
3086
+ await ctx.api.deleteForumTopic(chatId, topicId);
3087
+ } catch (err) {
3088
+ log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3089
+ }
3090
+ }
3091
+ await core.sessionManager.removeRecord(record.sessionId);
3092
+ deleted++;
3093
+ } catch (err) {
3094
+ log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3095
+ failed++;
3096
+ }
3097
+ }
3098
+ await ctx.reply(
3099
+ `\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
3100
+ { parse_mode: "HTML" }
3101
+ );
3102
+ }
2873
3103
  async function handleAgents(ctx, core) {
2874
3104
  const agents = core.agentManager.getAvailableAgents();
2875
3105
  const defaultAgent = core.configManager.get().defaultAgent;
@@ -3126,16 +3356,134 @@ async function executeCancelSession(core, excludeSessionId) {
3126
3356
  await session.cancel();
3127
3357
  return session;
3128
3358
  }
3359
+ async function handleIntegrate(ctx, _core) {
3360
+ const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
3361
+ const agents = listIntegrations();
3362
+ const keyboard = new InlineKeyboard();
3363
+ for (const agent of agents) {
3364
+ keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
3365
+ }
3366
+ await ctx.reply(
3367
+ `<b>\u{1F517} Integrations</b>
3368
+
3369
+ Select an agent to manage its integrations.`,
3370
+ { parse_mode: "HTML", reply_markup: keyboard }
3371
+ );
3372
+ }
3373
+ function buildAgentItemsKeyboard(agentName, items) {
3374
+ const keyboard = new InlineKeyboard();
3375
+ for (const item of items) {
3376
+ const installed = item.isInstalled();
3377
+ keyboard.text(
3378
+ installed ? `\u2705 ${item.name} \u2014 Uninstall` : `\u{1F4E6} ${item.name} \u2014 Install`,
3379
+ installed ? `i:uninstall:${agentName}:${item.id}` : `i:install:${agentName}:${item.id}`
3380
+ ).row();
3381
+ }
3382
+ keyboard.text("\u2190 Back", "i:back").row();
3383
+ return keyboard;
3384
+ }
3385
+ function setupIntegrateCallbacks(bot, core) {
3386
+ bot.callbackQuery(/^i:/, async (ctx) => {
3387
+ const data = ctx.callbackQuery.data;
3388
+ try {
3389
+ await ctx.answerCallbackQuery();
3390
+ } catch {
3391
+ }
3392
+ if (data === "i:back") {
3393
+ const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
3394
+ const agents = listIntegrations();
3395
+ const keyboard2 = new InlineKeyboard();
3396
+ for (const agent of agents) {
3397
+ keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
3398
+ }
3399
+ try {
3400
+ await ctx.editMessageText(
3401
+ `<b>\u{1F517} Integrations</b>
3402
+
3403
+ Select an agent to manage its integrations.`,
3404
+ { parse_mode: "HTML", reply_markup: keyboard2 }
3405
+ );
3406
+ } catch {
3407
+ }
3408
+ return;
3409
+ }
3410
+ const agentMatch = data.match(/^i:agent:(.+)$/);
3411
+ if (agentMatch) {
3412
+ const agentName2 = agentMatch[1];
3413
+ const { getIntegration: getIntegration2 } = await import("./integrate-WUPLRJD3.js");
3414
+ const integration2 = getIntegration2(agentName2);
3415
+ if (!integration2) {
3416
+ await ctx.reply(`\u274C No integration available for '${escapeHtml(agentName2)}'.`, { parse_mode: "HTML" });
3417
+ return;
3418
+ }
3419
+ const keyboard2 = buildAgentItemsKeyboard(agentName2, integration2.items);
3420
+ try {
3421
+ await ctx.editMessageText(
3422
+ `<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>
3423
+
3424
+ ${integration2.items.map((i) => `\u2022 <b>${escapeHtml(i.name)}</b> \u2014 ${escapeHtml(i.description)}`).join("\n")}`,
3425
+ { parse_mode: "HTML", reply_markup: keyboard2 }
3426
+ );
3427
+ } catch {
3428
+ await ctx.reply(
3429
+ `<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>`,
3430
+ { parse_mode: "HTML", reply_markup: keyboard2 }
3431
+ );
3432
+ }
3433
+ return;
3434
+ }
3435
+ const actionMatch = data.match(/^i:(install|uninstall):([^:]+):(.+)$/);
3436
+ if (!actionMatch) return;
3437
+ const action = actionMatch[1];
3438
+ const agentName = actionMatch[2];
3439
+ const itemId = actionMatch[3];
3440
+ const { getIntegration } = await import("./integrate-WUPLRJD3.js");
3441
+ const integration = getIntegration(agentName);
3442
+ if (!integration) return;
3443
+ const item = integration.items.find((i) => i.id === itemId);
3444
+ if (!item) return;
3445
+ const result = action === "install" ? await item.install() : await item.uninstall();
3446
+ const installed = action === "install" && result.success;
3447
+ await core.configManager.save({
3448
+ integrations: {
3449
+ [agentName]: {
3450
+ installed,
3451
+ installedAt: installed ? (/* @__PURE__ */ new Date()).toISOString() : void 0
3452
+ }
3453
+ }
3454
+ });
3455
+ const statusEmoji = result.success ? "\u2705" : "\u274C";
3456
+ const actionLabel = action === "install" ? "installed" : "uninstalled";
3457
+ const logsText = result.logs.map((l) => `<code>${escapeHtml(l)}</code>`).join("\n");
3458
+ const resultText = `${statusEmoji} <b>${escapeHtml(item.name)}</b> ${actionLabel}.
3459
+
3460
+ ${logsText}`;
3461
+ const keyboard = buildAgentItemsKeyboard(agentName, integration.items);
3462
+ try {
3463
+ await ctx.editMessageText(
3464
+ `<b>\u{1F517} ${escapeHtml(agentName)} Integrations</b>
3465
+
3466
+ ${resultText}`,
3467
+ { parse_mode: "HTML", reply_markup: keyboard }
3468
+ );
3469
+ } catch {
3470
+ await ctx.reply(resultText, { parse_mode: "HTML" });
3471
+ }
3472
+ });
3473
+ }
3129
3474
  var STATIC_COMMANDS = [
3130
3475
  { command: "new", description: "Create new session" },
3131
3476
  { command: "newchat", description: "New chat, same agent & workspace" },
3132
3477
  { command: "cancel", description: "Cancel current session" },
3133
3478
  { command: "status", description: "Show status" },
3479
+ { command: "sessions", description: "List all sessions" },
3134
3480
  { command: "agents", description: "List available agents" },
3135
3481
  { command: "help", description: "Help" },
3136
3482
  { command: "menu", description: "Show menu" },
3137
3483
  { command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
3138
3484
  { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
3485
+ { command: "integrate", description: "Manage agent integrations" },
3486
+ { command: "handoff", description: "Continue this session in your terminal" },
3139
3487
  { command: "restart", description: "Restart OpenACP" },
3140
3488
  { command: "update", description: "Update to latest version and restart" }
3141
3489
  ];
@@ -3942,10 +4290,12 @@ var TelegramAdapter = class extends ChannelAdapter {
3942
4290
  this.telegramConfig.chatId,
3943
4291
  () => this.assistantSession?.id
3944
4292
  );
4293
+ setupIntegrateCallbacks(this.bot, this.core);
3945
4294
  setupMenuCallbacks(
3946
4295
  this.bot,
3947
4296
  this.core,
3948
- this.telegramConfig.chatId
4297
+ this.telegramConfig.chatId,
4298
+ { notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId }
3949
4299
  );
3950
4300
  setupCommands(
3951
4301
  this.bot,
@@ -3967,24 +4317,26 @@ var TelegramAdapter = class extends ChannelAdapter {
3967
4317
  return;
3968
4318
  }
3969
4319
  const session = this.core.sessionManager.getSessionByThread("telegram", String(threadId));
3970
- if (!session) {
3971
- await ctx.reply("No active session in this topic.", {
4320
+ const record = session ? void 0 : this.core.sessionManager.getRecordByThread("telegram", String(threadId));
4321
+ const agentName = session?.agentName ?? record?.agentName;
4322
+ const agentSessionId = session?.agentSessionId ?? record?.agentSessionId;
4323
+ if (!agentName || !agentSessionId) {
4324
+ await ctx.reply("No session found for this topic.", {
3972
4325
  message_thread_id: threadId
3973
4326
  });
3974
4327
  return;
3975
4328
  }
3976
4329
  const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-7HC6D4CH.js");
3977
- const caps = getAgentCapabilities2(session.agentName);
4330
+ const caps = getAgentCapabilities2(agentName);
3978
4331
  if (!caps.supportsResume || !caps.resumeCommand) {
3979
- await ctx.reply("This agent does not support CLI handoff.", {
4332
+ await ctx.reply("This agent does not support session transfer.", {
3980
4333
  message_thread_id: threadId
3981
4334
  });
3982
4335
  return;
3983
4336
  }
3984
- const agentSessionId = session.agentSessionId;
3985
4337
  const command = caps.resumeCommand(agentSessionId);
3986
4338
  await ctx.reply(
3987
- `Resume this session on CLI:
4339
+ `Run this in your terminal to continue the session:
3988
4340
 
3989
4341
  <code>${command}</code>`,
3990
4342
  {
@@ -4006,10 +4358,13 @@ var TelegramAdapter = class extends ChannelAdapter {
4006
4358
  const agents = this.core.agentManager.getAvailableAgents();
4007
4359
  const agentList = agents.map((a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`).join(", ");
4008
4360
  const workspace = escapeHtml(config.workspace.baseDir);
4361
+ const allRecords = this.core.sessionManager.listRecords();
4362
+ const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
4009
4363
  const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
4010
4364
 
4011
4365
  Available agents: ${agentList}
4012
4366
  Workspace: <code>${workspace}</code>
4367
+ Sessions: ${activeCount} active / ${allRecords.length} total
4013
4368
 
4014
4369
  <b>Select an action:</b>`;
4015
4370
  await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
@@ -4533,4 +4888,4 @@ export {
4533
4888
  TopicManager,
4534
4889
  TelegramAdapter
4535
4890
  };
4536
- //# sourceMappingURL=chunk-2M4O7AFI.js.map
4891
+ //# sourceMappingURL=chunk-BBPWAWE3.js.map