@openacp/cli 0.4.9 → 0.4.10

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.
@@ -2428,12 +2428,14 @@ function splitMessage(text, maxLength = 3800) {
2428
2428
  chunks.push(remaining);
2429
2429
  break;
2430
2430
  }
2431
- let splitAt = remaining.lastIndexOf("\n\n", maxLength);
2432
- if (splitAt === -1 || splitAt < maxLength * 0.2) {
2433
- splitAt = remaining.lastIndexOf("\n", maxLength);
2431
+ const wouldLeaveSmall = remaining.length < maxLength * 1.3;
2432
+ const searchLimit = wouldLeaveSmall ? Math.floor(remaining.length / 2) + 300 : maxLength;
2433
+ let splitAt = remaining.lastIndexOf("\n\n", searchLimit);
2434
+ if (splitAt === -1 || splitAt < searchLimit * 0.2) {
2435
+ splitAt = remaining.lastIndexOf("\n", searchLimit);
2434
2436
  }
2435
- if (splitAt === -1 || splitAt < maxLength * 0.2) {
2436
- splitAt = maxLength;
2437
+ if (splitAt === -1 || splitAt < searchLimit * 0.2) {
2438
+ splitAt = searchLimit;
2437
2439
  }
2438
2440
  const candidate = remaining.slice(0, splitAt);
2439
2441
  const fences = candidate.match(/```/g);
@@ -2466,6 +2468,7 @@ var MessageDraft = class {
2466
2468
  flushTimer;
2467
2469
  flushPromise = Promise.resolve();
2468
2470
  lastSentBuffer = "";
2471
+ displayTruncated = false;
2469
2472
  append(text) {
2470
2473
  if (!text) return;
2471
2474
  this.buffer += text;
@@ -2482,16 +2485,20 @@ var MessageDraft = class {
2482
2485
  async flush() {
2483
2486
  if (!this.buffer) return;
2484
2487
  if (this.firstFlushPending) 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);
2488
+ const snapshot = this.buffer;
2489
+ let html = markdownToTelegramHtml(snapshot);
2492
2490
  if (!html) return;
2491
+ let truncated = false;
2493
2492
  if (html.length > 4096) {
2494
- html = html.slice(0, 4090) + "\n\u2026";
2493
+ const ratio = 4e3 / html.length;
2494
+ const targetLen = Math.floor(snapshot.length * ratio);
2495
+ let cutAt = snapshot.lastIndexOf("\n", targetLen);
2496
+ if (cutAt < targetLen * 0.5) cutAt = targetLen;
2497
+ html = markdownToTelegramHtml(snapshot.slice(0, cutAt) + "\n\u2026");
2498
+ truncated = true;
2499
+ if (html.length > 4096) {
2500
+ html = html.slice(0, 4090) + "\n\u2026";
2501
+ }
2495
2502
  }
2496
2503
  if (!this.messageId) {
2497
2504
  this.firstFlushPending = true;
@@ -2506,7 +2513,12 @@ var MessageDraft = class {
2506
2513
  );
2507
2514
  if (result) {
2508
2515
  this.messageId = result.message_id;
2509
- this.lastSentBuffer = this.buffer;
2516
+ if (!truncated) {
2517
+ this.lastSentBuffer = snapshot;
2518
+ this.displayTruncated = false;
2519
+ } else {
2520
+ this.displayTruncated = true;
2521
+ }
2510
2522
  }
2511
2523
  } catch {
2512
2524
  } finally {
@@ -2514,13 +2526,20 @@ var MessageDraft = class {
2514
2526
  }
2515
2527
  } else {
2516
2528
  try {
2517
- await this.sendQueue.enqueue(
2529
+ const result = await this.sendQueue.enqueue(
2518
2530
  () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
2519
2531
  parse_mode: "HTML"
2520
2532
  }),
2521
2533
  { type: "text", key: this.sessionId }
2522
2534
  );
2523
- this.lastSentBuffer = this.buffer;
2535
+ if (result !== void 0) {
2536
+ if (!truncated) {
2537
+ this.lastSentBuffer = snapshot;
2538
+ this.displayTruncated = false;
2539
+ } else {
2540
+ this.displayTruncated = true;
2541
+ }
2542
+ }
2524
2543
  } catch {
2525
2544
  }
2526
2545
  }
@@ -2532,9 +2551,34 @@ var MessageDraft = class {
2532
2551
  }
2533
2552
  await this.flushPromise;
2534
2553
  if (!this.buffer) return this.messageId;
2535
- if (this.messageId && this.buffer === this.lastSentBuffer) {
2554
+ if (this.messageId && this.buffer === this.lastSentBuffer && !this.displayTruncated) {
2536
2555
  return this.messageId;
2537
2556
  }
2557
+ const fullHtml = markdownToTelegramHtml(this.buffer);
2558
+ if (fullHtml.length <= 4096) {
2559
+ try {
2560
+ if (this.messageId) {
2561
+ await this.sendQueue.enqueue(
2562
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, fullHtml, {
2563
+ parse_mode: "HTML"
2564
+ }),
2565
+ { type: "other" }
2566
+ );
2567
+ } else {
2568
+ const msg = await this.sendQueue.enqueue(
2569
+ () => this.bot.api.sendMessage(this.chatId, fullHtml, {
2570
+ message_thread_id: this.threadId,
2571
+ parse_mode: "HTML",
2572
+ disable_notification: true
2573
+ }),
2574
+ { type: "other" }
2575
+ );
2576
+ if (msg) this.messageId = msg.message_id;
2577
+ }
2578
+ return this.messageId;
2579
+ } catch {
2580
+ }
2581
+ }
2538
2582
  const mdChunks = splitMessage(this.buffer);
2539
2583
  for (let i = 0; i < mdChunks.length; i++) {
2540
2584
  const html = markdownToTelegramHtml(mdChunks[i]);
@@ -2620,202 +2664,229 @@ function buildDeepLink(chatId, messageId) {
2620
2664
  return `https://t.me/c/${cleanId}/${messageId}`;
2621
2665
  }
2622
2666
 
2623
- // src/adapters/telegram/commands.ts
2667
+ // src/adapters/telegram/commands/new-session.ts
2668
+ import { InlineKeyboard as InlineKeyboard2 } from "grammy";
2669
+
2670
+ // src/adapters/telegram/commands/admin.ts
2624
2671
  import { InlineKeyboard } from "grammy";
2625
- var log8 = createChildLogger({ module: "telegram-commands" });
2626
- var pendingNewSessions = /* @__PURE__ */ new Map();
2627
- var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
2628
- function cleanupPending(userId) {
2629
- const pending = pendingNewSessions.get(userId);
2630
- if (pending) {
2631
- clearTimeout(pending.timer);
2632
- pendingNewSessions.delete(userId);
2633
- }
2634
- }
2635
- function setupCommands(bot, core, chatId, assistant) {
2636
- bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
2637
- bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
2638
- bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
2639
- bot.command("status", (ctx) => handleStatus(ctx, core));
2640
- bot.command("sessions", (ctx) => handleTopics(ctx, core));
2641
- bot.command("agents", (ctx) => handleAgents(ctx, core));
2642
- bot.command("help", (ctx) => handleHelp(ctx));
2643
- bot.command("menu", (ctx) => handleMenu(ctx));
2644
- bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
2645
- bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
2646
- bot.command("restart", (ctx) => handleRestart(ctx, core));
2647
- bot.command("update", (ctx) => handleUpdate(ctx, core));
2648
- bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
2649
- bot.command("clear", (ctx) => handleClear(ctx, assistant));
2650
- }
2651
- function buildMenuKeyboard() {
2652
- return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4CB} Sessions", "m:topics").row().text("\u{1F4CA} Status", "m:status").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");
2672
+ var log8 = createChildLogger({ module: "telegram-cmd-admin" });
2673
+ function buildDangerousModeKeyboard(sessionId, enabled) {
2674
+ return new InlineKeyboard().text(
2675
+ enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
2676
+ `d:${sessionId}`
2677
+ );
2653
2678
  }
2654
- function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
2655
- bot.callbackQuery(/^m:/, async (ctx) => {
2656
- const data = ctx.callbackQuery.data;
2657
- try {
2658
- await ctx.answerCallbackQuery();
2659
- } catch {
2660
- }
2661
- if (data.startsWith("m:new:agent:")) {
2662
- const agentName = data.replace("m:new:agent:", "");
2663
- const userId = ctx.from?.id;
2664
- if (userId) await startWorkspaceStep(ctx, core, chatId, userId, agentName);
2665
- return;
2666
- }
2667
- if (data === "m:new:ws:default") {
2668
- const userId = ctx.from?.id;
2669
- if (!userId) return;
2670
- const pending = pendingNewSessions.get(userId);
2671
- if (!pending?.agentName) return;
2672
- const workspace = core.configManager.get().workspace.baseDir;
2673
- await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
2674
- return;
2675
- }
2676
- if (data === "m:new:ws:custom") {
2677
- const userId = ctx.from?.id;
2678
- if (!userId) return;
2679
- const pending = pendingNewSessions.get(userId);
2680
- if (!pending?.agentName) return;
2681
- try {
2682
- await ctx.api.editMessageText(
2683
- chatId,
2684
- pending.messageId,
2685
- `\u270F\uFE0F <b>Enter your project path:</b>
2686
-
2687
- Full path like <code>~/code/my-project</code>
2688
- Or just the folder name like <code>my-project</code> (will use ${core.configManager.get().workspace.baseDir}/)`,
2689
- { parse_mode: "HTML" }
2690
- );
2691
- } catch {
2692
- await ctx.reply(
2693
- `\u270F\uFE0F <b>Enter your project path:</b>`,
2694
- { parse_mode: "HTML" }
2695
- );
2696
- }
2697
- clearTimeout(pending.timer);
2698
- pending.step = "workspace_input";
2699
- pending.timer = setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS);
2700
- return;
2701
- }
2702
- if (data === "m:new:confirm") {
2703
- const userId = ctx.from?.id;
2704
- if (!userId) return;
2705
- const pending = pendingNewSessions.get(userId);
2706
- if (!pending?.agentName || !pending?.workspace) return;
2707
- cleanupPending(userId);
2708
- const confirmMsgId = pending.messageId;
2679
+ function setupDangerousModeCallbacks(bot, core) {
2680
+ bot.callbackQuery(/^d:/, async (ctx) => {
2681
+ const sessionId = ctx.callbackQuery.data.slice(2);
2682
+ const session = core.sessionManager.getSession(sessionId);
2683
+ if (session) {
2684
+ session.dangerousMode = !session.dangerousMode;
2685
+ log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
2686
+ core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
2687
+ });
2688
+ const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
2709
2689
  try {
2710
- await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
2690
+ await ctx.answerCallbackQuery({ text: toastText2 });
2711
2691
  } catch {
2712
2692
  }
2713
- const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
2714
2693
  try {
2715
- if (resultThreadId) {
2716
- const link = buildDeepLink(chatId, resultThreadId);
2717
- await ctx.api.editMessageText(chatId, confirmMsgId, `\u2705 Session created \u2192 <a href="${link}">Open topic</a>`, { parse_mode: "HTML" });
2718
- } else {
2719
- await ctx.api.editMessageText(chatId, confirmMsgId, `\u274C Session creation failed.`, { parse_mode: "HTML" });
2720
- }
2694
+ await ctx.editMessageReplyMarkup({
2695
+ reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
2696
+ });
2721
2697
  } catch {
2722
2698
  }
2723
2699
  return;
2724
2700
  }
2725
- if (data === "m:new:cancel") {
2726
- const userId = ctx.from?.id;
2727
- if (userId) cleanupPending(userId);
2701
+ const record = core.sessionManager.getSessionRecord(sessionId);
2702
+ if (!record || record.status === "cancelled" || record.status === "error") {
2728
2703
  try {
2729
- await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
2704
+ await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
2730
2705
  } catch {
2731
2706
  }
2732
2707
  return;
2733
2708
  }
2734
- switch (data) {
2735
- case "m:new":
2736
- await handleNew(ctx, core, chatId);
2737
- break;
2738
- case "m:status":
2739
- await handleStatus(ctx, core);
2740
- break;
2741
- case "m:agents":
2742
- await handleAgents(ctx, core);
2743
- break;
2744
- case "m:help":
2745
- await handleHelp(ctx);
2746
- break;
2747
- case "m:restart":
2748
- await handleRestart(ctx, core);
2749
- break;
2750
- case "m:update":
2751
- await handleUpdate(ctx, core);
2752
- break;
2753
- case "m:integrate":
2754
- await handleIntegrate(ctx, core);
2755
- break;
2756
- case "m:topics":
2757
- await handleTopics(ctx, core);
2758
- break;
2759
- case "m:cleanup:finished":
2760
- await handleCleanup(ctx, core, chatId, ["finished"]);
2761
- break;
2762
- case "m:cleanup:errors":
2763
- await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
2764
- break;
2765
- case "m:cleanup:all":
2766
- await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
2767
- break;
2768
- case "m:cleanup:everything":
2769
- await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
2770
- break;
2771
- case "m:cleanup:everything:confirm":
2772
- await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
2773
- break;
2709
+ const newDangerousMode = !(record.dangerousMode ?? false);
2710
+ core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
2711
+ });
2712
+ log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
2713
+ const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
2714
+ try {
2715
+ await ctx.answerCallbackQuery({ text: toastText });
2716
+ } catch {
2717
+ }
2718
+ try {
2719
+ await ctx.editMessageReplyMarkup({
2720
+ reply_markup: buildDangerousModeKeyboard(sessionId, newDangerousMode)
2721
+ });
2722
+ } catch {
2774
2723
  }
2775
2724
  });
2776
2725
  }
2777
- async function handleMenu(ctx) {
2778
- await ctx.reply(`<b>OpenACP Menu</b>
2779
- Choose an action:`, {
2780
- parse_mode: "HTML",
2781
- reply_markup: buildMenuKeyboard()
2782
- });
2783
- }
2784
- async function handleNew(ctx, core, chatId, assistant) {
2785
- const rawMatch = ctx.match;
2786
- const matchStr = typeof rawMatch === "string" ? rawMatch : "";
2787
- const args = matchStr.split(" ").filter(Boolean);
2788
- const agentName = args[0];
2789
- const workspace = args[1];
2790
- if (agentName && workspace) {
2791
- await createSessionDirect(ctx, core, chatId, agentName, workspace);
2726
+ async function handleEnableDangerous(ctx, core) {
2727
+ const threadId = ctx.message?.message_thread_id;
2728
+ if (!threadId) {
2729
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
2792
2730
  return;
2793
2731
  }
2794
- const currentThreadId = ctx.message?.message_thread_id;
2795
- if (assistant && currentThreadId === assistant.topicId) {
2796
- const assistantSession = assistant.getSession();
2797
- if (assistantSession) {
2798
- const prompt = agentName ? `User wants to create a new session with agent "${agentName}" but didn't specify a workspace. Ask them which project directory to use as workspace.` : `User wants to create a new session. Guide them through choosing an agent and workspace (project directory).`;
2799
- await assistantSession.enqueuePrompt(prompt);
2732
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
2733
+ if (session) {
2734
+ if (session.dangerousMode) {
2735
+ await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
2800
2736
  return;
2801
2737
  }
2738
+ session.dangerousMode = true;
2739
+ core.sessionManager.updateSessionDangerousMode(session.id, true).catch(() => {
2740
+ });
2741
+ } else {
2742
+ const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2743
+ if (!record || record.status === "cancelled" || record.status === "error") {
2744
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
2745
+ return;
2746
+ }
2747
+ if (record.dangerousMode) {
2748
+ await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
2749
+ return;
2750
+ }
2751
+ core.sessionManager.updateSessionDangerousMode(record.sessionId, true).catch(() => {
2752
+ });
2802
2753
  }
2803
- const userId = ctx.from?.id;
2804
- if (!userId) return;
2805
- const agents = core.agentManager.getAvailableAgents();
2806
- const config = core.configManager.get();
2807
- if (agentName || agents.length === 1) {
2808
- const selectedAgent = agentName || config.defaultAgent;
2809
- await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
2810
- return;
2811
- }
2812
- const keyboard = new InlineKeyboard();
2813
- for (const agent of agents) {
2814
- const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
2815
- keyboard.text(label, `m:new:agent:${agent.name}`).row();
2816
- }
2817
- const msg = await ctx.reply(
2818
- `\u{1F916} <b>Choose an agent:</b>`,
2754
+ await ctx.reply(
2755
+ `\u26A0\uFE0F <b>Dangerous mode enabled</b>
2756
+
2757
+ All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
2758
+
2759
+ Use /disable_dangerous to restore normal behaviour.`,
2760
+ { parse_mode: "HTML" }
2761
+ );
2762
+ }
2763
+ async function handleDisableDangerous(ctx, core) {
2764
+ const threadId = ctx.message?.message_thread_id;
2765
+ if (!threadId) {
2766
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
2767
+ return;
2768
+ }
2769
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
2770
+ if (session) {
2771
+ if (!session.dangerousMode) {
2772
+ await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
2773
+ return;
2774
+ }
2775
+ session.dangerousMode = false;
2776
+ core.sessionManager.updateSessionDangerousMode(session.id, false).catch(() => {
2777
+ });
2778
+ } else {
2779
+ const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2780
+ if (!record || record.status === "cancelled" || record.status === "error") {
2781
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
2782
+ return;
2783
+ }
2784
+ if (!record.dangerousMode) {
2785
+ await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
2786
+ return;
2787
+ }
2788
+ core.sessionManager.updateSessionDangerousMode(record.sessionId, false).catch(() => {
2789
+ });
2790
+ }
2791
+ await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
2792
+ }
2793
+ async function handleUpdate(ctx, core) {
2794
+ if (!core.requestRestart) {
2795
+ await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
2796
+ return;
2797
+ }
2798
+ const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
2799
+ const current = getCurrentVersion();
2800
+ const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
2801
+ const latest = await getLatestVersion();
2802
+ if (!latest) {
2803
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
2804
+ return;
2805
+ }
2806
+ if (compareVersions(current, latest) >= 0) {
2807
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
2808
+ return;
2809
+ }
2810
+ await ctx.api.editMessageText(
2811
+ ctx.chat.id,
2812
+ statusMsg.message_id,
2813
+ `\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
2814
+ { parse_mode: "HTML" }
2815
+ );
2816
+ const ok = await runUpdate();
2817
+ if (!ok) {
2818
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
2819
+ return;
2820
+ }
2821
+ await ctx.api.editMessageText(
2822
+ ctx.chat.id,
2823
+ statusMsg.message_id,
2824
+ `\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
2825
+ { parse_mode: "HTML" }
2826
+ );
2827
+ await new Promise((r) => setTimeout(r, 500));
2828
+ await core.requestRestart();
2829
+ }
2830
+ async function handleRestart(ctx, core) {
2831
+ if (!core.requestRestart) {
2832
+ await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
2833
+ return;
2834
+ }
2835
+ await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
2836
+ await new Promise((r) => setTimeout(r, 500));
2837
+ await core.requestRestart();
2838
+ }
2839
+
2840
+ // src/adapters/telegram/commands/new-session.ts
2841
+ var log9 = createChildLogger({ module: "telegram-cmd-new-session" });
2842
+ var pendingNewSessions = /* @__PURE__ */ new Map();
2843
+ var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
2844
+ function cleanupPending(userId) {
2845
+ const pending = pendingNewSessions.get(userId);
2846
+ if (pending) {
2847
+ clearTimeout(pending.timer);
2848
+ pendingNewSessions.delete(userId);
2849
+ }
2850
+ }
2851
+ function botFromCtx(ctx) {
2852
+ return { api: ctx.api };
2853
+ }
2854
+ async function handleNew(ctx, core, chatId, assistant) {
2855
+ const rawMatch = ctx.match;
2856
+ const matchStr = typeof rawMatch === "string" ? rawMatch : "";
2857
+ const args = matchStr.split(" ").filter(Boolean);
2858
+ const agentName = args[0];
2859
+ const workspace = args[1];
2860
+ if (agentName && workspace) {
2861
+ await createSessionDirect(ctx, core, chatId, agentName, workspace);
2862
+ return;
2863
+ }
2864
+ const currentThreadId = ctx.message?.message_thread_id;
2865
+ if (assistant && currentThreadId === assistant.topicId) {
2866
+ const assistantSession = assistant.getSession();
2867
+ if (assistantSession) {
2868
+ const prompt = agentName ? `User wants to create a new session with agent "${agentName}" but didn't specify a workspace. Ask them which project directory to use as workspace.` : `User wants to create a new session. Guide them through choosing an agent and workspace (project directory).`;
2869
+ await assistantSession.enqueuePrompt(prompt);
2870
+ return;
2871
+ }
2872
+ }
2873
+ const userId = ctx.from?.id;
2874
+ if (!userId) return;
2875
+ const agents = core.agentManager.getAvailableAgents();
2876
+ const config = core.configManager.get();
2877
+ if (agentName || agents.length === 1) {
2878
+ const selectedAgent = agentName || config.defaultAgent;
2879
+ await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
2880
+ return;
2881
+ }
2882
+ const keyboard = new InlineKeyboard2();
2883
+ for (const agent of agents) {
2884
+ const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
2885
+ keyboard.text(label, `m:new:agent:${agent.name}`).row();
2886
+ }
2887
+ keyboard.text("\u274C Cancel", "m:new:cancel");
2888
+ const msg = await ctx.reply(
2889
+ `\u{1F916} <b>Choose an agent:</b>`,
2819
2890
  { parse_mode: "HTML", reply_markup: keyboard }
2820
2891
  );
2821
2892
  cleanupPending(userId);
@@ -2829,7 +2900,7 @@ async function handleNew(ctx, core, chatId, assistant) {
2829
2900
  async function startWorkspaceStep(ctx, core, chatId, userId, agentName) {
2830
2901
  const config = core.configManager.get();
2831
2902
  const baseDir = config.workspace.baseDir;
2832
- const keyboard = new InlineKeyboard().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom");
2903
+ const keyboard = new InlineKeyboard2().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom").row().text("\u274C Cancel", "m:new:cancel");
2833
2904
  const text = `\u{1F4C1} <b>Where should ${escapeHtml(agentName)} work?</b>
2834
2905
 
2835
2906
  Enter the path to your project folder \u2014 the agent will read, write, and run code there.
@@ -2860,7 +2931,7 @@ Or use the default directory below:`;
2860
2931
  });
2861
2932
  }
2862
2933
  async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
2863
- const keyboard = new InlineKeyboard().text("\u2705 Create", "m:new:confirm").text("\u274C Cancel", "m:new:cancel");
2934
+ const keyboard = new InlineKeyboard2().text("\u2705 Create", "m:new:confirm").text("\u274C Cancel", "m:new:cancel");
2864
2935
  const text = `\u2705 <b>Ready to create session?</b>
2865
2936
 
2866
2937
  <b>Agent:</b> ${escapeHtml(agentName)}
@@ -2891,7 +2962,7 @@ async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
2891
2962
  });
2892
2963
  }
2893
2964
  async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
2894
- log8.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
2965
+ log9.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
2895
2966
  let threadId;
2896
2967
  try {
2897
2968
  const topicName = `\u{1F504} New Session`;
@@ -2921,99 +2992,262 @@ This is your coding session \u2014 chat here to work with the agent.`,
2921
2992
  reply_markup: buildDangerousModeKeyboard(session.id, false)
2922
2993
  }
2923
2994
  );
2924
- session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
2995
+ session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
2925
2996
  return threadId ?? null;
2926
2997
  } catch (err) {
2927
- log8.error({ err }, "Session creation failed");
2998
+ log9.error({ err }, "Session creation failed");
2928
2999
  if (threadId) {
2929
3000
  try {
2930
3001
  await ctx.api.deleteForumTopic(chatId, threadId);
2931
3002
  } catch {
2932
3003
  }
2933
- }
2934
- const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
2935
- await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
2936
- return null;
2937
- }
2938
- }
2939
- async function handleNewChat(ctx, core, chatId) {
2940
- const threadId = ctx.message?.message_thread_id;
2941
- if (!threadId) {
2942
- await ctx.reply(
2943
- "Use /newchat inside a session topic to inherit its config.",
2944
- { parse_mode: "HTML" }
2945
- );
2946
- return;
2947
- }
2948
- const currentSession = core.sessionManager.getSessionByThread(
2949
- "telegram",
2950
- String(threadId)
2951
- );
2952
- let agentName;
2953
- let workspace;
2954
- if (currentSession) {
2955
- agentName = currentSession.agentName;
2956
- workspace = currentSession.workingDirectory;
2957
- } else {
2958
- const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2959
- if (!record || record.status === "cancelled" || record.status === "error") {
2960
- await ctx.reply("No active session in this topic.", {
2961
- parse_mode: "HTML"
2962
- });
3004
+ }
3005
+ const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
3006
+ await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
3007
+ return null;
3008
+ }
3009
+ }
3010
+ async function handleNewChat(ctx, core, chatId) {
3011
+ const threadId = ctx.message?.message_thread_id;
3012
+ if (!threadId) {
3013
+ await ctx.reply(
3014
+ "Use /newchat inside a session topic to inherit its config.",
3015
+ { parse_mode: "HTML" }
3016
+ );
3017
+ return;
3018
+ }
3019
+ const currentSession = core.sessionManager.getSessionByThread(
3020
+ "telegram",
3021
+ String(threadId)
3022
+ );
3023
+ let agentName;
3024
+ let workspace;
3025
+ if (currentSession) {
3026
+ agentName = currentSession.agentName;
3027
+ workspace = currentSession.workingDirectory;
3028
+ } else {
3029
+ const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
3030
+ if (!record || record.status === "cancelled" || record.status === "error") {
3031
+ await ctx.reply("No active session in this topic.", {
3032
+ parse_mode: "HTML"
3033
+ });
3034
+ return;
3035
+ }
3036
+ agentName = record.agentName;
3037
+ workspace = record.workingDir;
3038
+ }
3039
+ let newThreadId;
3040
+ try {
3041
+ const topicName = `\u{1F504} ${agentName} \u2014 New Chat`;
3042
+ newThreadId = await createSessionTopic(
3043
+ botFromCtx(ctx),
3044
+ chatId,
3045
+ topicName
3046
+ );
3047
+ const topicLink = buildDeepLink(chatId, newThreadId);
3048
+ await ctx.reply(
3049
+ `\u2705 New chat created \u2192 <a href="${topicLink}">Open topic</a>`,
3050
+ { parse_mode: "HTML" }
3051
+ );
3052
+ await ctx.api.sendMessage(chatId, `\u23F3 Setting up session, please wait...`, {
3053
+ message_thread_id: newThreadId,
3054
+ parse_mode: "HTML"
3055
+ });
3056
+ const session = await core.handleNewSession(
3057
+ "telegram",
3058
+ agentName,
3059
+ workspace
3060
+ );
3061
+ session.threadId = String(newThreadId);
3062
+ await core.sessionManager.updateSessionPlatform(session.id, {
3063
+ topicId: newThreadId
3064
+ });
3065
+ await ctx.api.sendMessage(
3066
+ chatId,
3067
+ `\u2705 New chat (same agent &amp; workspace)
3068
+ <b>Agent:</b> ${escapeHtml(session.agentName)}
3069
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
3070
+ {
3071
+ message_thread_id: newThreadId,
3072
+ parse_mode: "HTML",
3073
+ reply_markup: buildDangerousModeKeyboard(session.id, false)
3074
+ }
3075
+ );
3076
+ session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
3077
+ } catch (err) {
3078
+ if (newThreadId) {
3079
+ try {
3080
+ await ctx.api.deleteForumTopic(chatId, newThreadId);
3081
+ } catch {
3082
+ }
3083
+ }
3084
+ const message = err instanceof Error ? err.message : String(err);
3085
+ await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
3086
+ }
3087
+ }
3088
+ async function executeNewSession(bot, core, chatId, agentName, workspace) {
3089
+ const threadId = await createSessionTopic(bot, chatId, "\u{1F504} New Session");
3090
+ const setupMsg = await bot.api.sendMessage(chatId, "\u23F3 Setting up session, please wait...", {
3091
+ message_thread_id: threadId,
3092
+ parse_mode: "HTML"
3093
+ });
3094
+ const firstMsgId = setupMsg.message_id;
3095
+ try {
3096
+ const session = await core.handleNewSession(
3097
+ "telegram",
3098
+ agentName,
3099
+ workspace
3100
+ );
3101
+ session.threadId = String(threadId);
3102
+ await core.sessionManager.updateSessionPlatform(session.id, {
3103
+ topicId: threadId
3104
+ });
3105
+ const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
3106
+ await renameSessionTopic(bot, chatId, threadId, finalName);
3107
+ session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
3108
+ return { session, threadId, firstMsgId };
3109
+ } catch (err) {
3110
+ try {
3111
+ await bot.api.deleteForumTopic(chatId, threadId);
3112
+ } catch {
3113
+ }
3114
+ throw err;
3115
+ }
3116
+ }
3117
+ async function handlePendingWorkspaceInput(ctx, core, chatId, assistantTopicId) {
3118
+ const userId = ctx.from?.id;
3119
+ if (!userId) return false;
3120
+ const pending = pendingNewSessions.get(userId);
3121
+ if (!pending || !ctx.message?.text) return false;
3122
+ if (pending.step !== "workspace_input" && pending.step !== "workspace") return false;
3123
+ const threadId = ctx.message.message_thread_id;
3124
+ if (threadId && threadId !== assistantTopicId) return false;
3125
+ let workspace = ctx.message.text.trim();
3126
+ if (!workspace || !pending.agentName) {
3127
+ await ctx.reply("\u26A0\uFE0F Please enter a valid directory path.", { parse_mode: "HTML" });
3128
+ return true;
3129
+ }
3130
+ if (!workspace.startsWith("/") && !workspace.startsWith("~")) {
3131
+ const baseDir = core.configManager.get().workspace.baseDir;
3132
+ workspace = `${baseDir.replace(/\/$/, "")}/${workspace}`;
3133
+ }
3134
+ await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
3135
+ return true;
3136
+ }
3137
+ async function startInteractiveNewSession(ctx, core, chatId, agentName) {
3138
+ const userId = ctx.from?.id;
3139
+ if (!userId) return;
3140
+ const agents = core.agentManager.getAvailableAgents();
3141
+ const config = core.configManager.get();
3142
+ if (agentName || agents.length === 1) {
3143
+ const selectedAgent = agentName || config.defaultAgent;
3144
+ await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
3145
+ return;
3146
+ }
3147
+ const keyboard = new InlineKeyboard2();
3148
+ for (const agent of agents) {
3149
+ const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
3150
+ keyboard.text(label, `m:new:agent:${agent.name}`).row();
3151
+ }
3152
+ keyboard.text("\u274C Cancel", "m:new:cancel");
3153
+ const msg = await ctx.reply(
3154
+ `\u{1F916} <b>Choose an agent:</b>`,
3155
+ { parse_mode: "HTML", reply_markup: keyboard }
3156
+ );
3157
+ cleanupPending(userId);
3158
+ pendingNewSessions.set(userId, {
3159
+ step: "agent",
3160
+ messageId: msg.message_id,
3161
+ threadId: ctx.callbackQuery?.message?.message_thread_id,
3162
+ timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
3163
+ });
3164
+ }
3165
+ function setupNewSessionCallbacks(bot, core, chatId) {
3166
+ bot.callbackQuery(/^m:new:/, async (ctx) => {
3167
+ const data = ctx.callbackQuery.data;
3168
+ try {
3169
+ await ctx.answerCallbackQuery();
3170
+ } catch {
3171
+ }
3172
+ if (data.startsWith("m:new:agent:")) {
3173
+ const agentName = data.replace("m:new:agent:", "");
3174
+ const userId = ctx.from?.id;
3175
+ if (userId) await startWorkspaceStep(ctx, core, chatId, userId, agentName);
3176
+ return;
3177
+ }
3178
+ if (data === "m:new:ws:default") {
3179
+ const userId = ctx.from?.id;
3180
+ if (!userId) return;
3181
+ const pending = pendingNewSessions.get(userId);
3182
+ if (!pending?.agentName) return;
3183
+ const workspace = core.configManager.get().workspace.baseDir;
3184
+ await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
3185
+ return;
3186
+ }
3187
+ if (data === "m:new:ws:custom") {
3188
+ const userId = ctx.from?.id;
3189
+ if (!userId) return;
3190
+ const pending = pendingNewSessions.get(userId);
3191
+ if (!pending?.agentName) return;
3192
+ try {
3193
+ await ctx.api.editMessageText(
3194
+ chatId,
3195
+ pending.messageId,
3196
+ `\u270F\uFE0F <b>Enter your project path:</b>
3197
+
3198
+ Full path like <code>~/code/my-project</code>
3199
+ Or just the folder name like <code>my-project</code> (will use ${core.configManager.get().workspace.baseDir}/)`,
3200
+ { parse_mode: "HTML" }
3201
+ );
3202
+ } catch {
3203
+ await ctx.reply(
3204
+ `\u270F\uFE0F <b>Enter your project path:</b>`,
3205
+ { parse_mode: "HTML" }
3206
+ );
3207
+ }
3208
+ clearTimeout(pending.timer);
3209
+ pending.step = "workspace_input";
3210
+ pending.timer = setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS);
3211
+ return;
3212
+ }
3213
+ if (data === "m:new:confirm") {
3214
+ const userId = ctx.from?.id;
3215
+ if (!userId) return;
3216
+ const pending = pendingNewSessions.get(userId);
3217
+ if (!pending?.agentName || !pending?.workspace) return;
3218
+ cleanupPending(userId);
3219
+ const confirmMsgId = pending.messageId;
3220
+ try {
3221
+ await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
3222
+ } catch {
3223
+ }
3224
+ const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
3225
+ try {
3226
+ if (resultThreadId) {
3227
+ const link = buildDeepLink(chatId, resultThreadId);
3228
+ await ctx.api.editMessageText(chatId, confirmMsgId, `\u2705 Session created \u2192 <a href="${link}">Open topic</a>`, { parse_mode: "HTML" });
3229
+ } else {
3230
+ await ctx.api.editMessageText(chatId, confirmMsgId, `\u274C Session creation failed.`, { parse_mode: "HTML" });
3231
+ }
3232
+ } catch {
3233
+ }
2963
3234
  return;
2964
3235
  }
2965
- agentName = record.agentName;
2966
- workspace = record.workingDir;
2967
- }
2968
- let newThreadId;
2969
- try {
2970
- const topicName = `\u{1F504} ${agentName} \u2014 New Chat`;
2971
- newThreadId = await createSessionTopic(
2972
- botFromCtx(ctx),
2973
- chatId,
2974
- topicName
2975
- );
2976
- const topicLink = buildDeepLink(chatId, newThreadId);
2977
- await ctx.reply(
2978
- `\u2705 New chat created \u2192 <a href="${topicLink}">Open topic</a>`,
2979
- { parse_mode: "HTML" }
2980
- );
2981
- await ctx.api.sendMessage(chatId, `\u23F3 Setting up session, please wait...`, {
2982
- message_thread_id: newThreadId,
2983
- parse_mode: "HTML"
2984
- });
2985
- const session = await core.handleNewSession(
2986
- "telegram",
2987
- agentName,
2988
- workspace
2989
- );
2990
- session.threadId = String(newThreadId);
2991
- await core.sessionManager.updateSessionPlatform(session.id, {
2992
- topicId: newThreadId
2993
- });
2994
- await ctx.api.sendMessage(
2995
- chatId,
2996
- `\u2705 New chat (same agent &amp; workspace)
2997
- <b>Agent:</b> ${escapeHtml(session.agentName)}
2998
- <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
2999
- {
3000
- message_thread_id: newThreadId,
3001
- parse_mode: "HTML",
3002
- reply_markup: buildDangerousModeKeyboard(session.id, false)
3003
- }
3004
- );
3005
- session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
3006
- } catch (err) {
3007
- if (newThreadId) {
3236
+ if (data === "m:new:cancel") {
3237
+ const userId = ctx.from?.id;
3238
+ if (userId) cleanupPending(userId);
3008
3239
  try {
3009
- await ctx.api.deleteForumTopic(chatId, newThreadId);
3240
+ await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
3010
3241
  } catch {
3011
3242
  }
3243
+ return;
3012
3244
  }
3013
- const message = err instanceof Error ? err.message : String(err);
3014
- await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
3015
- }
3245
+ });
3016
3246
  }
3247
+
3248
+ // src/adapters/telegram/commands/session.ts
3249
+ import { InlineKeyboard as InlineKeyboard3 } from "grammy";
3250
+ var log10 = createChildLogger({ module: "telegram-cmd-session" });
3017
3251
  async function handleCancel(ctx, core, assistant) {
3018
3252
  const threadId = ctx.message?.message_thread_id;
3019
3253
  if (!threadId) return;
@@ -3031,14 +3265,14 @@ async function handleCancel(ctx, core, assistant) {
3031
3265
  String(threadId)
3032
3266
  );
3033
3267
  if (session) {
3034
- log8.info({ sessionId: session.id }, "Cancel session command");
3268
+ log10.info({ sessionId: session.id }, "Cancel session command");
3035
3269
  await session.cancel();
3036
3270
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
3037
3271
  return;
3038
3272
  }
3039
3273
  const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
3040
3274
  if (record && record.status !== "cancelled" && record.status !== "error") {
3041
- log8.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
3275
+ log10.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
3042
3276
  await core.sessionManager.cancelSession(record.sessionId);
3043
3277
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
3044
3278
  }
@@ -3124,8 +3358,7 @@ async function handleTopics(ctx, core) {
3124
3358
  <i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
3125
3359
  const finishedCount = records.filter((r) => r.status === "finished").length;
3126
3360
  const errorCount = records.filter((r) => r.status === "error" || r.status === "cancelled").length;
3127
- const activeCount = records.filter((r) => r.status === "active" || r.status === "initializing").length;
3128
- const keyboard = new InlineKeyboard();
3361
+ const keyboard = new InlineKeyboard3();
3129
3362
  if (finishedCount > 0) {
3130
3363
  keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
3131
3364
  }
@@ -3144,7 +3377,7 @@ ${lines.join("\n")}${truncated}`,
3144
3377
  { parse_mode: "HTML", reply_markup: keyboard }
3145
3378
  );
3146
3379
  } catch (err) {
3147
- log8.error({ err }, "handleTopics error");
3380
+ log10.error({ err }, "handleTopics error");
3148
3381
  await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
3149
3382
  });
3150
3383
  }
@@ -3168,13 +3401,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
3168
3401
  try {
3169
3402
  await ctx.api.deleteForumTopic(chatId, topicId);
3170
3403
  } catch (err) {
3171
- log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3404
+ log10.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3172
3405
  }
3173
3406
  }
3174
3407
  await core.sessionManager.removeRecord(record.sessionId);
3175
3408
  deleted++;
3176
3409
  } catch (err) {
3177
- log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3410
+ log10.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3178
3411
  failed++;
3179
3412
  }
3180
3413
  }
@@ -3211,7 +3444,7 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
3211
3444
  const activeWarning = activeCount > 0 ? `
3212
3445
 
3213
3446
  \u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
3214
- const keyboard = new InlineKeyboard().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
3447
+ const keyboard = new InlineKeyboard3().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
3215
3448
  await ctx.reply(
3216
3449
  `<b>Delete ${cleanable.length} topics?</b>
3217
3450
 
@@ -3247,7 +3480,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
3247
3480
  try {
3248
3481
  await core.sessionManager.cancelSession(record.sessionId);
3249
3482
  } catch (err) {
3250
- log8.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
3483
+ log10.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
3251
3484
  }
3252
3485
  }
3253
3486
  const topicId = record.platform?.topicId;
@@ -3255,13 +3488,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
3255
3488
  try {
3256
3489
  await ctx.api.deleteForumTopic(chatId, topicId);
3257
3490
  } catch (err) {
3258
- log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3491
+ log10.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
3259
3492
  }
3260
3493
  }
3261
3494
  await core.sessionManager.removeRecord(record.sessionId);
3262
3495
  deleted++;
3263
3496
  } catch (err) {
3264
- log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3497
+ log10.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
3265
3498
  failed++;
3266
3499
  }
3267
3500
  }
@@ -3270,238 +3503,115 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
3270
3503
  { parse_mode: "HTML" }
3271
3504
  );
3272
3505
  }
3273
- async function handleAgents(ctx, core) {
3274
- const agents = core.agentManager.getAvailableAgents();
3275
- const defaultAgent = core.configManager.get().defaultAgent;
3276
- const lines = agents.map(
3277
- (a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
3278
- <code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
3279
- );
3280
- const text = lines.length > 0 ? `<b>Available Agents:</b>
3281
-
3282
- ${lines.join("\n")}` : `<b>Available Agents:</b>
3283
-
3284
- No agents configured.`;
3285
- await ctx.reply(text, { parse_mode: "HTML" });
3286
- }
3287
- async function handleHelp(ctx) {
3288
- await ctx.reply(
3289
- `\u{1F4D6} <b>OpenACP Help</b>
3290
-
3291
- \u{1F680} <b>Getting Started</b>
3292
- Tap \u{1F195} New Session to start coding with AI.
3293
- Each session gets its own topic \u2014 chat there to work with the agent.
3294
-
3295
- \u{1F4A1} <b>Common Tasks</b>
3296
- /new [agent] [workspace] \u2014 Create new session
3297
- /cancel \u2014 Cancel session (in session topic)
3298
- /status \u2014 Show session or system status
3299
- /sessions \u2014 List all sessions
3300
- /agents \u2014 List available agents
3301
-
3302
- \u2699\uFE0F <b>System</b>
3303
- /restart \u2014 Restart OpenACP
3304
- /update \u2014 Update to latest version
3305
- /integrate \u2014 Manage agent integrations
3306
- /menu \u2014 Show action menu
3307
-
3308
- \u{1F512} <b>Session Options</b>
3309
- /enable_dangerous \u2014 Auto-approve permissions
3310
- /disable_dangerous \u2014 Restore permission prompts
3311
- /handoff \u2014 Continue session in terminal
3312
- /clear \u2014 Clear assistant history
3313
-
3314
- \u{1F4AC} Need help? Just ask me in this topic!`,
3315
- { parse_mode: "HTML" }
3316
- );
3317
- }
3318
- async function handleClear(ctx, assistant) {
3319
- if (!assistant) {
3320
- await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
3321
- return;
3322
- }
3323
- const threadId = ctx.message?.message_thread_id;
3324
- if (threadId !== assistant.topicId) {
3325
- await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
3326
- return;
3327
- }
3328
- await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
3329
- try {
3330
- await assistant.respawn();
3331
- await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
3332
- } catch (err) {
3333
- const message = err instanceof Error ? err.message : String(err);
3334
- await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
3335
- }
3336
- }
3337
- function buildDangerousModeKeyboard(sessionId, enabled) {
3338
- return new InlineKeyboard().text(
3339
- enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
3340
- `d:${sessionId}`
3341
- );
3506
+ async function executeCancelSession(core, excludeSessionId) {
3507
+ const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
3508
+ const session = sessions[0];
3509
+ if (!session) return null;
3510
+ await session.cancel();
3511
+ return session;
3342
3512
  }
3343
- function setupDangerousModeCallbacks(bot, core) {
3344
- bot.callbackQuery(/^d:/, async (ctx) => {
3345
- const sessionId = ctx.callbackQuery.data.slice(2);
3346
- const session = core.sessionManager.getSession(sessionId);
3347
- if (session) {
3348
- session.dangerousMode = !session.dangerousMode;
3349
- log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
3350
- core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
3351
- });
3352
- const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
3353
- try {
3354
- await ctx.answerCallbackQuery({ text: toastText2 });
3355
- } catch {
3356
- }
3357
- try {
3358
- await ctx.editMessageReplyMarkup({
3359
- reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
3360
- });
3361
- } catch {
3362
- }
3363
- return;
3364
- }
3365
- const record = core.sessionManager.getSessionRecord(sessionId);
3366
- if (!record || record.status === "cancelled" || record.status === "error") {
3367
- try {
3368
- await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
3369
- } catch {
3370
- }
3371
- return;
3372
- }
3373
- const newDangerousMode = !(record.dangerousMode ?? false);
3374
- core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
3375
- });
3376
- log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
3377
- const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
3513
+ function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
3514
+ bot.callbackQuery(/^m:cleanup/, async (ctx) => {
3515
+ const data = ctx.callbackQuery.data;
3378
3516
  try {
3379
- await ctx.answerCallbackQuery({ text: toastText });
3517
+ await ctx.answerCallbackQuery();
3380
3518
  } catch {
3381
3519
  }
3382
- try {
3383
- await ctx.editMessageReplyMarkup({
3384
- reply_markup: buildDangerousModeKeyboard(sessionId, newDangerousMode)
3385
- });
3386
- } catch {
3520
+ switch (data) {
3521
+ case "m:cleanup:finished":
3522
+ await handleCleanup(ctx, core, chatId, ["finished"]);
3523
+ break;
3524
+ case "m:cleanup:errors":
3525
+ await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
3526
+ break;
3527
+ case "m:cleanup:all":
3528
+ await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
3529
+ break;
3530
+ case "m:cleanup:everything":
3531
+ await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
3532
+ break;
3533
+ case "m:cleanup:everything:confirm":
3534
+ await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
3535
+ break;
3387
3536
  }
3388
3537
  });
3389
3538
  }
3390
- async function handleEnableDangerous(ctx, core) {
3391
- const threadId = ctx.message?.message_thread_id;
3392
- if (!threadId) {
3393
- await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
3394
- return;
3395
- }
3396
- const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
3397
- if (session) {
3398
- if (session.dangerousMode) {
3399
- await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
3400
- return;
3401
- }
3402
- session.dangerousMode = true;
3403
- core.sessionManager.updateSessionDangerousMode(session.id, true).catch(() => {
3404
- });
3405
- } else {
3406
- const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
3407
- if (!record || record.status === "cancelled" || record.status === "error") {
3408
- await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
3409
- return;
3410
- }
3411
- if (record.dangerousMode) {
3412
- await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
3413
- return;
3414
- }
3415
- core.sessionManager.updateSessionDangerousMode(record.sessionId, true).catch(() => {
3416
- });
3417
- }
3418
- await ctx.reply(
3419
- `\u26A0\uFE0F <b>Dangerous mode enabled</b>
3420
3539
 
3421
- All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
3540
+ // src/adapters/telegram/commands/menu.ts
3541
+ import { InlineKeyboard as InlineKeyboard4 } from "grammy";
3542
+ function buildMenuKeyboard() {
3543
+ return new InlineKeyboard4().text("\u{1F195} New Session", "m:new").text("\u{1F4CB} Sessions", "m:topics").row().text("\u{1F4CA} Status", "m:status").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");
3544
+ }
3545
+ async function handleMenu(ctx) {
3546
+ await ctx.reply(`<b>OpenACP Menu</b>
3547
+ Choose an action:`, {
3548
+ parse_mode: "HTML",
3549
+ reply_markup: buildMenuKeyboard()
3550
+ });
3551
+ }
3552
+ async function handleAgents(ctx, core) {
3553
+ const agents = core.agentManager.getAvailableAgents();
3554
+ const defaultAgent = core.configManager.get().defaultAgent;
3555
+ const lines = agents.map(
3556
+ (a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
3557
+ <code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
3558
+ );
3559
+ const text = lines.length > 0 ? `<b>Available Agents:</b>
3560
+
3561
+ ${lines.join("\n")}` : `<b>Available Agents:</b>
3422
3562
 
3423
- Use /disable_dangerous to restore normal behaviour.`,
3424
- { parse_mode: "HTML" }
3425
- );
3563
+ No agents configured.`;
3564
+ await ctx.reply(text, { parse_mode: "HTML" });
3426
3565
  }
3427
- async function handleUpdate(ctx, core) {
3428
- if (!core.requestRestart) {
3429
- await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
3430
- return;
3431
- }
3432
- const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
3433
- const current = getCurrentVersion();
3434
- const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
3435
- const latest = await getLatestVersion();
3436
- if (!latest) {
3437
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
3438
- return;
3439
- }
3440
- if (compareVersions(current, latest) >= 0) {
3441
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
3442
- return;
3443
- }
3444
- await ctx.api.editMessageText(
3445
- ctx.chat.id,
3446
- statusMsg.message_id,
3447
- `\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
3448
- { parse_mode: "HTML" }
3449
- );
3450
- const ok = await runUpdate();
3451
- if (!ok) {
3452
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
3453
- return;
3454
- }
3455
- await ctx.api.editMessageText(
3456
- ctx.chat.id,
3457
- statusMsg.message_id,
3458
- `\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
3566
+ async function handleHelp(ctx) {
3567
+ await ctx.reply(
3568
+ `\u{1F4D6} <b>OpenACP Help</b>
3569
+
3570
+ \u{1F680} <b>Getting Started</b>
3571
+ Tap \u{1F195} New Session to start coding with AI.
3572
+ Each session gets its own topic \u2014 chat there to work with the agent.
3573
+
3574
+ \u{1F4A1} <b>Common Tasks</b>
3575
+ /new [agent] [workspace] \u2014 Create new session
3576
+ /cancel \u2014 Cancel session (in session topic)
3577
+ /status \u2014 Show session or system status
3578
+ /sessions \u2014 List all sessions
3579
+ /agents \u2014 List available agents
3580
+
3581
+ \u2699\uFE0F <b>System</b>
3582
+ /restart \u2014 Restart OpenACP
3583
+ /update \u2014 Update to latest version
3584
+ /integrate \u2014 Manage agent integrations
3585
+ /menu \u2014 Show action menu
3586
+
3587
+ \u{1F512} <b>Session Options</b>
3588
+ /enable_dangerous \u2014 Auto-approve permissions
3589
+ /disable_dangerous \u2014 Restore permission prompts
3590
+ /handoff \u2014 Continue session in terminal
3591
+ /clear \u2014 Clear assistant history
3592
+
3593
+ \u{1F4AC} Need help? Just ask me in this topic!`,
3459
3594
  { parse_mode: "HTML" }
3460
3595
  );
3461
- await new Promise((r) => setTimeout(r, 500));
3462
- await core.requestRestart();
3463
3596
  }
3464
- async function handleRestart(ctx, core) {
3465
- if (!core.requestRestart) {
3466
- await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
3597
+ async function handleClear(ctx, assistant) {
3598
+ if (!assistant) {
3599
+ await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
3467
3600
  return;
3468
3601
  }
3469
- await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
3470
- await new Promise((r) => setTimeout(r, 500));
3471
- await core.requestRestart();
3472
- }
3473
- async function handleDisableDangerous(ctx, core) {
3474
3602
  const threadId = ctx.message?.message_thread_id;
3475
- if (!threadId) {
3476
- await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
3603
+ if (threadId !== assistant.topicId) {
3604
+ await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
3477
3605
  return;
3478
3606
  }
3479
- const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
3480
- if (session) {
3481
- if (!session.dangerousMode) {
3482
- await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
3483
- return;
3484
- }
3485
- session.dangerousMode = false;
3486
- core.sessionManager.updateSessionDangerousMode(session.id, false).catch(() => {
3487
- });
3488
- } else {
3489
- const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
3490
- if (!record || record.status === "cancelled" || record.status === "error") {
3491
- await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
3492
- return;
3493
- }
3494
- if (!record.dangerousMode) {
3495
- await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
3496
- return;
3497
- }
3498
- core.sessionManager.updateSessionDangerousMode(record.sessionId, false).catch(() => {
3499
- });
3607
+ await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
3608
+ try {
3609
+ await assistant.respawn();
3610
+ await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
3611
+ } catch (err) {
3612
+ const message = err instanceof Error ? err.message : String(err);
3613
+ await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
3500
3614
  }
3501
- await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
3502
- }
3503
- function botFromCtx(ctx) {
3504
- return { api: ctx.api };
3505
3615
  }
3506
3616
  var TELEGRAM_MSG_LIMIT = 4096;
3507
3617
  function buildSkillMessages(commands) {
@@ -3522,46 +3632,13 @@ function buildSkillMessages(commands) {
3522
3632
  if (current) messages.push(current);
3523
3633
  return messages;
3524
3634
  }
3525
- async function executeNewSession(bot, core, chatId, agentName, workspace) {
3526
- const threadId = await createSessionTopic(bot, chatId, "\u{1F504} New Session");
3527
- const setupMsg = await bot.api.sendMessage(chatId, "\u23F3 Setting up session, please wait...", {
3528
- message_thread_id: threadId,
3529
- parse_mode: "HTML"
3530
- });
3531
- const firstMsgId = setupMsg.message_id;
3532
- try {
3533
- const session = await core.handleNewSession(
3534
- "telegram",
3535
- agentName,
3536
- workspace
3537
- );
3538
- session.threadId = String(threadId);
3539
- await core.sessionManager.updateSessionPlatform(session.id, {
3540
- topicId: threadId
3541
- });
3542
- const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
3543
- await renameSessionTopic(bot, chatId, threadId, finalName);
3544
- session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
3545
- return { session, threadId, firstMsgId };
3546
- } catch (err) {
3547
- try {
3548
- await bot.api.deleteForumTopic(chatId, threadId);
3549
- } catch {
3550
- }
3551
- throw err;
3552
- }
3553
- }
3554
- async function executeCancelSession(core, excludeSessionId) {
3555
- const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
3556
- const session = sessions[0];
3557
- if (!session) return null;
3558
- await session.cancel();
3559
- return session;
3560
- }
3635
+
3636
+ // src/adapters/telegram/commands/integrate.ts
3637
+ import { InlineKeyboard as InlineKeyboard5 } from "grammy";
3561
3638
  async function handleIntegrate(ctx, _core) {
3562
3639
  const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
3563
3640
  const agents = listIntegrations();
3564
- const keyboard = new InlineKeyboard();
3641
+ const keyboard = new InlineKeyboard5();
3565
3642
  for (const agent of agents) {
3566
3643
  keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
3567
3644
  }
@@ -3573,7 +3650,7 @@ Select an agent to manage its integrations.`,
3573
3650
  );
3574
3651
  }
3575
3652
  function buildAgentItemsKeyboard(agentName, items) {
3576
- const keyboard = new InlineKeyboard();
3653
+ const keyboard = new InlineKeyboard5();
3577
3654
  for (const item of items) {
3578
3655
  const installed = item.isInstalled();
3579
3656
  keyboard.text(
@@ -3594,7 +3671,7 @@ function setupIntegrateCallbacks(bot, core) {
3594
3671
  if (data === "i:back") {
3595
3672
  const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
3596
3673
  const agents = listIntegrations();
3597
- const keyboard2 = new InlineKeyboard();
3674
+ const keyboard2 = new InlineKeyboard5();
3598
3675
  for (const agent of agents) {
3599
3676
  keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
3600
3677
  }
@@ -3673,51 +3750,59 @@ ${resultText}`,
3673
3750
  }
3674
3751
  });
3675
3752
  }
3676
- async function handlePendingWorkspaceInput(ctx, core, chatId, assistantTopicId) {
3677
- const userId = ctx.from?.id;
3678
- if (!userId) return false;
3679
- const pending = pendingNewSessions.get(userId);
3680
- if (!pending || !ctx.message?.text) return false;
3681
- if (pending.step !== "workspace_input" && pending.step !== "workspace") return false;
3682
- const threadId = ctx.message.message_thread_id;
3683
- if (threadId && threadId !== assistantTopicId) return false;
3684
- let workspace = ctx.message.text.trim();
3685
- if (!workspace || !pending.agentName) {
3686
- await ctx.reply("\u26A0\uFE0F Please enter a valid directory path.", { parse_mode: "HTML" });
3687
- return true;
3688
- }
3689
- if (!workspace.startsWith("/") && !workspace.startsWith("~")) {
3690
- const baseDir = core.configManager.get().workspace.baseDir;
3691
- workspace = `${baseDir.replace(/\/$/, "")}/${workspace}`;
3692
- }
3693
- await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
3694
- return true;
3753
+
3754
+ // src/adapters/telegram/commands/index.ts
3755
+ function setupCommands(bot, core, chatId, assistant) {
3756
+ bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
3757
+ bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
3758
+ bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
3759
+ bot.command("status", (ctx) => handleStatus(ctx, core));
3760
+ bot.command("sessions", (ctx) => handleTopics(ctx, core));
3761
+ bot.command("agents", (ctx) => handleAgents(ctx, core));
3762
+ bot.command("help", (ctx) => handleHelp(ctx));
3763
+ bot.command("menu", (ctx) => handleMenu(ctx));
3764
+ bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
3765
+ bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
3766
+ bot.command("restart", (ctx) => handleRestart(ctx, core));
3767
+ bot.command("update", (ctx) => handleUpdate(ctx, core));
3768
+ bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
3769
+ bot.command("clear", (ctx) => handleClear(ctx, assistant));
3695
3770
  }
3696
- async function startInteractiveNewSession(ctx, core, chatId, agentName) {
3697
- const userId = ctx.from?.id;
3698
- if (!userId) return;
3699
- const agents = core.agentManager.getAvailableAgents();
3700
- const config = core.configManager.get();
3701
- if (agentName || agents.length === 1) {
3702
- const selectedAgent = agentName || config.defaultAgent;
3703
- await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
3704
- return;
3705
- }
3706
- const keyboard = new InlineKeyboard();
3707
- for (const agent of agents) {
3708
- const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
3709
- keyboard.text(label, `m:new:agent:${agent.name}`).row();
3710
- }
3711
- const msg = await ctx.reply(
3712
- `\u{1F916} <b>Choose an agent:</b>`,
3713
- { parse_mode: "HTML", reply_markup: keyboard }
3714
- );
3715
- cleanupPending(userId);
3716
- pendingNewSessions.set(userId, {
3717
- step: "agent",
3718
- messageId: msg.message_id,
3719
- threadId: ctx.callbackQuery?.message?.message_thread_id,
3720
- timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
3771
+ function setupAllCallbacks(bot, core, chatId, systemTopicIds) {
3772
+ setupNewSessionCallbacks(bot, core, chatId);
3773
+ setupSessionCallbacks(bot, core, chatId, systemTopicIds);
3774
+ bot.callbackQuery(/^m:/, async (ctx) => {
3775
+ const data = ctx.callbackQuery.data;
3776
+ try {
3777
+ await ctx.answerCallbackQuery();
3778
+ } catch {
3779
+ }
3780
+ switch (data) {
3781
+ case "m:new":
3782
+ await handleNew(ctx, core, chatId);
3783
+ break;
3784
+ case "m:status":
3785
+ await handleStatus(ctx, core);
3786
+ break;
3787
+ case "m:agents":
3788
+ await handleAgents(ctx, core);
3789
+ break;
3790
+ case "m:help":
3791
+ await handleHelp(ctx);
3792
+ break;
3793
+ case "m:restart":
3794
+ await handleRestart(ctx, core);
3795
+ break;
3796
+ case "m:update":
3797
+ await handleUpdate(ctx, core);
3798
+ break;
3799
+ case "m:integrate":
3800
+ await handleIntegrate(ctx, core);
3801
+ break;
3802
+ case "m:topics":
3803
+ await handleTopics(ctx, core);
3804
+ break;
3805
+ }
3721
3806
  });
3722
3807
  }
3723
3808
  var STATIC_COMMANDS = [
@@ -3739,9 +3824,9 @@ var STATIC_COMMANDS = [
3739
3824
  ];
3740
3825
 
3741
3826
  // src/adapters/telegram/permissions.ts
3742
- import { InlineKeyboard as InlineKeyboard2 } from "grammy";
3827
+ import { InlineKeyboard as InlineKeyboard6 } from "grammy";
3743
3828
  import { nanoid as nanoid2 } from "nanoid";
3744
- var log9 = createChildLogger({ module: "telegram-permissions" });
3829
+ var log11 = createChildLogger({ module: "telegram-permissions" });
3745
3830
  var PermissionHandler = class {
3746
3831
  constructor(bot, chatId, getSession, sendNotification) {
3747
3832
  this.bot = bot;
@@ -3758,7 +3843,7 @@ var PermissionHandler = class {
3758
3843
  requestId: request.id,
3759
3844
  options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
3760
3845
  });
3761
- const keyboard = new InlineKeyboard2();
3846
+ const keyboard = new InlineKeyboard6();
3762
3847
  for (const option of request.options) {
3763
3848
  const emoji = option.isAllow ? "\u2705" : "\u274C";
3764
3849
  keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
@@ -3801,7 +3886,7 @@ ${escapeHtml(request.description)}`,
3801
3886
  }
3802
3887
  const session = this.getSession(pending.sessionId);
3803
3888
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
3804
- log9.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
3889
+ log11.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
3805
3890
  if (session?.permissionGate.requestId === pending.requestId) {
3806
3891
  session.permissionGate.resolve(optionId);
3807
3892
  }
@@ -4183,10 +4268,10 @@ Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetenti
4183
4268
  `;
4184
4269
 
4185
4270
  // src/adapters/telegram/assistant.ts
4186
- var log10 = createChildLogger({ module: "telegram-assistant" });
4271
+ var log12 = createChildLogger({ module: "telegram-assistant" });
4187
4272
  async function spawnAssistant(core, adapter, assistantTopicId) {
4188
4273
  const config = core.configManager.get();
4189
- log10.info({ agent: config.defaultAgent }, "Creating assistant session...");
4274
+ log12.info({ agent: config.defaultAgent }, "Creating assistant session...");
4190
4275
  const session = await core.sessionManager.createSession(
4191
4276
  "telegram",
4192
4277
  config.defaultAgent,
@@ -4195,7 +4280,7 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
4195
4280
  );
4196
4281
  session.threadId = String(assistantTopicId);
4197
4282
  session.name = "Assistant";
4198
- log10.info({ sessionId: session.id }, "Assistant agent spawned");
4283
+ log12.info({ sessionId: session.id }, "Assistant agent spawned");
4199
4284
  core.wireSessionEvents(session, adapter);
4200
4285
  const allRecords = core.sessionManager.listRecords();
4201
4286
  const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
@@ -4212,9 +4297,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
4212
4297
  };
4213
4298
  const systemPrompt = buildAssistantSystemPrompt(ctx);
4214
4299
  const ready = session.enqueuePrompt(systemPrompt).then(() => {
4215
- log10.info({ sessionId: session.id }, "Assistant system prompt completed");
4300
+ log12.info({ sessionId: session.id }, "Assistant system prompt completed");
4216
4301
  }).catch((err) => {
4217
- log10.warn({ err }, "Assistant system prompt failed");
4302
+ log12.warn({ err }, "Assistant system prompt failed");
4218
4303
  });
4219
4304
  return { session, ready };
4220
4305
  }
@@ -4347,7 +4432,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
4347
4432
  }
4348
4433
 
4349
4434
  // src/adapters/telegram/activity.ts
4350
- var log11 = createChildLogger({ module: "telegram:activity" });
4435
+ var log13 = createChildLogger({ module: "telegram:activity" });
4351
4436
  var THINKING_REFRESH_MS = 15e3;
4352
4437
  var THINKING_MAX_MS = 3 * 60 * 1e3;
4353
4438
  var ThinkingIndicator = class {
@@ -4379,7 +4464,7 @@ var ThinkingIndicator = class {
4379
4464
  this.startRefreshTimer();
4380
4465
  }
4381
4466
  } catch (err) {
4382
- log11.warn({ err }, "ThinkingIndicator.show() failed");
4467
+ log13.warn({ err }, "ThinkingIndicator.show() failed");
4383
4468
  } finally {
4384
4469
  this.sending = false;
4385
4470
  }
@@ -4452,7 +4537,7 @@ var UsageMessage = class {
4452
4537
  if (result) this.msgId = result.message_id;
4453
4538
  }
4454
4539
  } catch (err) {
4455
- log11.warn({ err }, "UsageMessage.send() failed");
4540
+ log13.warn({ err }, "UsageMessage.send() failed");
4456
4541
  }
4457
4542
  }
4458
4543
  getMsgId() {
@@ -4465,7 +4550,7 @@ var UsageMessage = class {
4465
4550
  try {
4466
4551
  await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
4467
4552
  } catch (err) {
4468
- log11.warn({ err }, "UsageMessage.delete() failed");
4553
+ log13.warn({ err }, "UsageMessage.delete() failed");
4469
4554
  }
4470
4555
  }
4471
4556
  };
@@ -4551,7 +4636,7 @@ var PlanCard = class {
4551
4636
  if (result) this.msgId = result.message_id;
4552
4637
  }
4553
4638
  } catch (err) {
4554
- log11.warn({ err }, "PlanCard flush failed");
4639
+ log13.warn({ err }, "PlanCard flush failed");
4555
4640
  }
4556
4641
  }
4557
4642
  };
@@ -4614,7 +4699,7 @@ var ActivityTracker = class {
4614
4699
  })
4615
4700
  );
4616
4701
  } catch (err) {
4617
- log11.warn({ err }, "ActivityTracker.onComplete() Done send failed");
4702
+ log13.warn({ err }, "ActivityTracker.onComplete() Done send failed");
4618
4703
  }
4619
4704
  }
4620
4705
  }
@@ -4697,7 +4782,7 @@ var TelegramSendQueue = class {
4697
4782
 
4698
4783
  // src/adapters/telegram/action-detect.ts
4699
4784
  import { nanoid as nanoid3 } from "nanoid";
4700
- import { InlineKeyboard as InlineKeyboard3 } from "grammy";
4785
+ import { InlineKeyboard as InlineKeyboard7 } from "grammy";
4701
4786
  var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
4702
4787
  var CMD_CANCEL_RE = /\/cancel\b/;
4703
4788
  var KW_NEW_RE = /(?:create|new)\s+session/i;
@@ -4744,7 +4829,7 @@ function removeAction(id) {
4744
4829
  actionMap.delete(id);
4745
4830
  }
4746
4831
  function buildActionKeyboard(actionId, action) {
4747
- const keyboard = new InlineKeyboard3();
4832
+ const keyboard = new InlineKeyboard7();
4748
4833
  if (action.action === "new_session") {
4749
4834
  keyboard.text("\u2705 Create session", `a:${actionId}`);
4750
4835
  keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
@@ -4852,7 +4937,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
4852
4937
  }
4853
4938
 
4854
4939
  // src/adapters/telegram/adapter.ts
4855
- var log12 = createChildLogger({ module: "telegram" });
4940
+ var log14 = createChildLogger({ module: "telegram" });
4856
4941
  function patchedFetch(input, init) {
4857
4942
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
4858
4943
  const nativeController = new AbortController();
@@ -4903,7 +4988,7 @@ var TelegramAdapter = class extends ChannelAdapter {
4903
4988
  this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
4904
4989
  this.bot.catch((err) => {
4905
4990
  const rootCause = err.error instanceof Error ? err.error : err;
4906
- log12.error({ err: rootCause }, "Telegram bot error");
4991
+ log14.error({ err: rootCause }, "Telegram bot error");
4907
4992
  });
4908
4993
  this.bot.api.config.use(async (prev, method, payload, signal) => {
4909
4994
  const maxRetries = 3;
@@ -4917,7 +5002,7 @@ var TelegramAdapter = class extends ChannelAdapter {
4917
5002
  if (rateLimitedMethods.includes(method)) {
4918
5003
  this.sendQueue.onRateLimited();
4919
5004
  }
4920
- log12.warn(
5005
+ log14.warn(
4921
5006
  { method, retryAfter, attempt: attempt + 1 },
4922
5007
  "Rate limited by Telegram, retrying"
4923
5008
  );
@@ -4969,7 +5054,7 @@ var TelegramAdapter = class extends ChannelAdapter {
4969
5054
  () => this.assistantSession?.id
4970
5055
  );
4971
5056
  setupIntegrateCallbacks(this.bot, this.core);
4972
- setupMenuCallbacks(
5057
+ setupAllCallbacks(
4973
5058
  this.bot,
4974
5059
  this.core,
4975
5060
  this.telegramConfig.chatId,
@@ -5042,7 +5127,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5042
5127
  this.setupRoutes();
5043
5128
  this.bot.start({
5044
5129
  allowed_updates: ["message", "callback_query"],
5045
- onStart: () => log12.info(
5130
+ onStart: () => log14.info(
5046
5131
  { chatId: this.telegramConfig.chatId },
5047
5132
  "Telegram bot started"
5048
5133
  )
@@ -5064,10 +5149,10 @@ var TelegramAdapter = class extends ChannelAdapter {
5064
5149
  reply_markup: buildMenuKeyboard()
5065
5150
  });
5066
5151
  } catch (err) {
5067
- log12.warn({ err }, "Failed to send welcome message");
5152
+ log14.warn({ err }, "Failed to send welcome message");
5068
5153
  }
5069
5154
  try {
5070
- log12.info("Spawning assistant session...");
5155
+ log14.info("Spawning assistant session...");
5071
5156
  const { session, ready } = await spawnAssistant(
5072
5157
  this.core,
5073
5158
  this,
@@ -5075,13 +5160,13 @@ var TelegramAdapter = class extends ChannelAdapter {
5075
5160
  );
5076
5161
  this.assistantSession = session;
5077
5162
  this.assistantInitializing = true;
5078
- log12.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
5163
+ log14.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
5079
5164
  ready.then(() => {
5080
5165
  this.assistantInitializing = false;
5081
- log12.info({ sessionId: session.id }, "Assistant ready for user messages");
5166
+ log14.info({ sessionId: session.id }, "Assistant ready for user messages");
5082
5167
  });
5083
5168
  } catch (err) {
5084
- log12.error({ err }, "Failed to spawn assistant");
5169
+ log14.error({ err }, "Failed to spawn assistant");
5085
5170
  this.bot.api.sendMessage(
5086
5171
  this.telegramConfig.chatId,
5087
5172
  `\u26A0\uFE0F <b>Failed to start assistant session.</b>
@@ -5097,7 +5182,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5097
5182
  await this.assistantSession.destroy();
5098
5183
  }
5099
5184
  await this.bot.stop();
5100
- log12.info("Telegram bot stopped");
5185
+ log14.info("Telegram bot stopped");
5101
5186
  }
5102
5187
  setupRoutes() {
5103
5188
  this.bot.on("message:text", async (ctx) => {
@@ -5123,7 +5208,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5123
5208
  ctx.replyWithChatAction("typing").catch(() => {
5124
5209
  });
5125
5210
  handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
5126
- (err) => log12.error({ err }, "Assistant error")
5211
+ (err) => log14.error({ err }, "Assistant error")
5127
5212
  );
5128
5213
  return;
5129
5214
  }
@@ -5140,7 +5225,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5140
5225
  threadId: String(threadId),
5141
5226
  userId: String(ctx.from.id),
5142
5227
  text: ctx.message.text
5143
- }).catch((err) => log12.error({ err }, "handleMessage error"));
5228
+ }).catch((err) => log14.error({ err }, "handleMessage error"));
5144
5229
  });
5145
5230
  }
5146
5231
  // --- ChannelAdapter implementations ---
@@ -5152,7 +5237,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5152
5237
  if (!session) return;
5153
5238
  const threadId = Number(session.threadId);
5154
5239
  if (!threadId || isNaN(threadId)) {
5155
- log12.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
5240
+ log14.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
5156
5241
  return;
5157
5242
  }
5158
5243
  switch (content.type) {
@@ -5224,16 +5309,16 @@ var TelegramAdapter = class extends ChannelAdapter {
5224
5309
  if (toolState) {
5225
5310
  if (meta.viewerLinks) {
5226
5311
  toolState.viewerLinks = meta.viewerLinks;
5227
- log12.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
5312
+ log14.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
5228
5313
  }
5229
5314
  const viewerFilePath = content.metadata?.viewerFilePath;
5230
5315
  if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
5231
5316
  if (meta.name) toolState.name = meta.name;
5232
5317
  if (meta.kind) toolState.kind = meta.kind;
5233
5318
  const isTerminal = meta.status === "completed" || meta.status === "failed";
5234
- if (!isTerminal && !meta.viewerLinks) break;
5319
+ if (!isTerminal) break;
5235
5320
  await toolState.ready;
5236
- log12.debug(
5321
+ log14.debug(
5237
5322
  { toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
5238
5323
  "Tool completed, preparing edit"
5239
5324
  );
@@ -5255,7 +5340,7 @@ var TelegramAdapter = class extends ChannelAdapter {
5255
5340
  )
5256
5341
  );
5257
5342
  } catch (err) {
5258
- log12.warn(
5343
+ log14.warn(
5259
5344
  { err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
5260
5345
  "Tool update edit failed"
5261
5346
  );
@@ -5350,7 +5435,7 @@ Task completed.
5350
5435
  }
5351
5436
  }
5352
5437
  async sendPermissionRequest(sessionId, request) {
5353
- log12.info({ sessionId, requestId: request.id }, "Permission request sent");
5438
+ log14.info({ sessionId, requestId: request.id }, "Permission request sent");
5354
5439
  const session = this.core.sessionManager.getSession(
5355
5440
  sessionId
5356
5441
  );
@@ -5358,7 +5443,7 @@ Task completed.
5358
5443
  if (request.description.includes("openacp")) {
5359
5444
  const allowOption = request.options.find((o) => o.isAllow);
5360
5445
  if (allowOption && session.permissionGate.requestId === request.id) {
5361
- log12.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
5446
+ log14.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
5362
5447
  session.permissionGate.resolve(allowOption.id);
5363
5448
  }
5364
5449
  return;
@@ -5366,7 +5451,7 @@ Task completed.
5366
5451
  if (session.dangerousMode) {
5367
5452
  const allowOption = request.options.find((o) => o.isAllow);
5368
5453
  if (allowOption && session.permissionGate.requestId === request.id) {
5369
- log12.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
5454
+ log14.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
5370
5455
  session.permissionGate.resolve(allowOption.id);
5371
5456
  }
5372
5457
  return;
@@ -5377,7 +5462,7 @@ Task completed.
5377
5462
  }
5378
5463
  async sendNotification(notification) {
5379
5464
  if (notification.sessionId === this.assistantSession?.id) return;
5380
- log12.info(
5465
+ log14.info(
5381
5466
  { sessionId: notification.sessionId, type: notification.type },
5382
5467
  "Notification sent"
5383
5468
  );
@@ -5413,7 +5498,7 @@ Task completed.
5413
5498
  );
5414
5499
  }
5415
5500
  async createSessionThread(sessionId, name) {
5416
- log12.info({ sessionId, name }, "Session topic created");
5501
+ log14.info({ sessionId, name }, "Session topic created");
5417
5502
  return String(
5418
5503
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
5419
5504
  );
@@ -5442,7 +5527,7 @@ Task completed.
5442
5527
  try {
5443
5528
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
5444
5529
  } catch (err) {
5445
- log12.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
5530
+ log14.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
5446
5531
  }
5447
5532
  }
5448
5533
  async sendSkillCommands(sessionId, commands) {
@@ -5515,7 +5600,7 @@ Task completed.
5515
5600
  { disable_notification: true }
5516
5601
  );
5517
5602
  } catch (err) {
5518
- log12.error({ err, sessionId }, "Failed to send skill commands");
5603
+ log14.error({ err, sessionId }, "Failed to send skill commands");
5519
5604
  }
5520
5605
  }
5521
5606
  async cleanupSkillCommands(sessionId) {
@@ -5586,4 +5671,4 @@ export {
5586
5671
  TopicManager,
5587
5672
  TelegramAdapter
5588
5673
  };
5589
- //# sourceMappingURL=chunk-45DFYWJT.js.map
5674
+ //# sourceMappingURL=chunk-KPI4HGJC.js.map