@openacp/cli 0.4.8 → 0.4.9

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.
@@ -2623,6 +2623,15 @@ function buildDeepLink(chatId, messageId) {
2623
2623
  // src/adapters/telegram/commands.ts
2624
2624
  import { InlineKeyboard } from "grammy";
2625
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
+ }
2626
2635
  function setupCommands(bot, core, chatId, assistant) {
2627
2636
  bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
2628
2637
  bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
@@ -2637,9 +2646,10 @@ function setupCommands(bot, core, chatId, assistant) {
2637
2646
  bot.command("restart", (ctx) => handleRestart(ctx, core));
2638
2647
  bot.command("update", (ctx) => handleUpdate(ctx, core));
2639
2648
  bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
2649
+ bot.command("clear", (ctx) => handleClear(ctx, assistant));
2640
2650
  }
2641
2651
  function buildMenuKeyboard() {
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");
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");
2643
2653
  }
2644
2654
  function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
2645
2655
  bot.callbackQuery(/^m:/, async (ctx) => {
@@ -2648,16 +2658,83 @@ function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
2648
2658
  await ctx.answerCallbackQuery();
2649
2659
  } catch {
2650
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;
2709
+ try {
2710
+ await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
2711
+ } catch {
2712
+ }
2713
+ const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
2714
+ 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
+ }
2721
+ } catch {
2722
+ }
2723
+ return;
2724
+ }
2725
+ if (data === "m:new:cancel") {
2726
+ const userId = ctx.from?.id;
2727
+ if (userId) cleanupPending(userId);
2728
+ try {
2729
+ await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
2730
+ } catch {
2731
+ }
2732
+ return;
2733
+ }
2651
2734
  switch (data) {
2652
2735
  case "m:new":
2653
2736
  await handleNew(ctx, core, chatId);
2654
2737
  break;
2655
- case "m:newchat":
2656
- await handleNewChat(ctx, core, chatId);
2657
- break;
2658
- case "m:cancel":
2659
- await handleCancel(ctx, core);
2660
- break;
2661
2738
  case "m:status":
2662
2739
  await handleStatus(ctx, core);
2663
2740
  break;
@@ -2710,16 +2787,111 @@ async function handleNew(ctx, core, chatId, assistant) {
2710
2787
  const args = matchStr.split(" ").filter(Boolean);
2711
2788
  const agentName = args[0];
2712
2789
  const workspace = args[1];
2790
+ if (agentName && workspace) {
2791
+ await createSessionDirect(ctx, core, chatId, agentName, workspace);
2792
+ return;
2793
+ }
2713
2794
  const currentThreadId = ctx.message?.message_thread_id;
2714
- if (assistant && currentThreadId === assistant.topicId && (!agentName || !workspace)) {
2795
+ if (assistant && currentThreadId === assistant.topicId) {
2715
2796
  const assistantSession = assistant.getSession();
2716
2797
  if (assistantSession) {
2717
- const prompt = agentName ? `User wants to create a new session with agent "${agentName}" but didn't specify a workspace. Ask them which workspace to use.` : `User wants to create a new session. Ask them which agent and workspace to use.`;
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).`;
2718
2799
  await assistantSession.enqueuePrompt(prompt);
2719
2800
  return;
2720
2801
  }
2721
2802
  }
2722
- log8.info({ userId: ctx.from?.id, agentName }, "New session command");
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>`,
2819
+ { parse_mode: "HTML", reply_markup: keyboard }
2820
+ );
2821
+ cleanupPending(userId);
2822
+ pendingNewSessions.set(userId, {
2823
+ step: "agent",
2824
+ messageId: msg.message_id,
2825
+ threadId: currentThreadId,
2826
+ timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
2827
+ });
2828
+ }
2829
+ async function startWorkspaceStep(ctx, core, chatId, userId, agentName) {
2830
+ const config = core.configManager.get();
2831
+ 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");
2833
+ const text = `\u{1F4C1} <b>Where should ${escapeHtml(agentName)} work?</b>
2834
+
2835
+ Enter the path to your project folder \u2014 the agent will read, write, and run code there.
2836
+
2837
+ Or use the default directory below:`;
2838
+ let msg;
2839
+ try {
2840
+ const pending = pendingNewSessions.get(userId);
2841
+ if (pending?.messageId) {
2842
+ await ctx.api.editMessageText(chatId, pending.messageId, text, {
2843
+ parse_mode: "HTML",
2844
+ reply_markup: keyboard
2845
+ });
2846
+ msg = { message_id: pending.messageId };
2847
+ } else {
2848
+ msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
2849
+ }
2850
+ } catch {
2851
+ msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
2852
+ }
2853
+ cleanupPending(userId);
2854
+ pendingNewSessions.set(userId, {
2855
+ agentName,
2856
+ step: "workspace",
2857
+ messageId: msg.message_id,
2858
+ threadId: ctx.message?.message_thread_id ?? ctx.callbackQuery?.message?.message_thread_id,
2859
+ timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
2860
+ });
2861
+ }
2862
+ 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");
2864
+ const text = `\u2705 <b>Ready to create session?</b>
2865
+
2866
+ <b>Agent:</b> ${escapeHtml(agentName)}
2867
+ <b>Project:</b> <code>${escapeHtml(workspace)}</code>`;
2868
+ let msg;
2869
+ try {
2870
+ const pending = pendingNewSessions.get(userId);
2871
+ if (pending?.messageId) {
2872
+ await ctx.api.editMessageText(chatId, pending.messageId, text, {
2873
+ parse_mode: "HTML",
2874
+ reply_markup: keyboard
2875
+ });
2876
+ msg = { message_id: pending.messageId };
2877
+ } else {
2878
+ msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
2879
+ }
2880
+ } catch {
2881
+ msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
2882
+ }
2883
+ cleanupPending(userId);
2884
+ pendingNewSessions.set(userId, {
2885
+ agentName,
2886
+ workspace,
2887
+ step: "confirm",
2888
+ messageId: msg.message_id,
2889
+ threadId: ctx.message?.message_thread_id ?? ctx.callbackQuery?.message?.message_thread_id,
2890
+ timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
2891
+ });
2892
+ }
2893
+ async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
2894
+ log8.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
2723
2895
  let threadId;
2724
2896
  try {
2725
2897
  const topicName = `\u{1F504} New Session`;
@@ -2728,15 +2900,9 @@ async function handleNew(ctx, core, chatId, assistant) {
2728
2900
  message_thread_id: threadId,
2729
2901
  parse_mode: "HTML"
2730
2902
  });
2731
- const session = await core.handleNewSession(
2732
- "telegram",
2733
- agentName,
2734
- workspace
2735
- );
2903
+ const session = await core.handleNewSession("telegram", agentName, workspace);
2736
2904
  session.threadId = String(threadId);
2737
- await core.sessionManager.updateSessionPlatform(session.id, {
2738
- topicId: threadId
2739
- });
2905
+ await core.sessionManager.updateSessionPlatform(session.id, { topicId: threadId });
2740
2906
  const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
2741
2907
  try {
2742
2908
  await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
@@ -2744,9 +2910,11 @@ async function handleNew(ctx, core, chatId, assistant) {
2744
2910
  }
2745
2911
  await ctx.api.sendMessage(
2746
2912
  chatId,
2747
- `\u2705 Session started
2913
+ `\u2705 <b>Session started</b>
2748
2914
  <b>Agent:</b> ${escapeHtml(session.agentName)}
2749
- <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
2915
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
2916
+
2917
+ This is your coding session \u2014 chat here to work with the agent.`,
2750
2918
  {
2751
2919
  message_thread_id: threadId,
2752
2920
  parse_mode: "HTML",
@@ -2754,6 +2922,7 @@ async function handleNew(ctx, core, chatId, assistant) {
2754
2922
  }
2755
2923
  );
2756
2924
  session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
2925
+ return threadId ?? null;
2757
2926
  } catch (err) {
2758
2927
  log8.error({ err }, "Session creation failed");
2759
2928
  if (threadId) {
@@ -2764,6 +2933,7 @@ async function handleNew(ctx, core, chatId, assistant) {
2764
2933
  }
2765
2934
  const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
2766
2935
  await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
2936
+ return null;
2767
2937
  }
2768
2938
  }
2769
2939
  async function handleNewChat(ctx, core, chatId) {
@@ -3116,22 +3286,54 @@ No agents configured.`;
3116
3286
  }
3117
3287
  async function handleHelp(ctx) {
3118
3288
  await ctx.reply(
3119
- `<b>OpenACP Commands:</b>
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.
3120
3294
 
3295
+ \u{1F4A1} <b>Common Tasks</b>
3121
3296
  /new [agent] [workspace] \u2014 Create new session
3122
- /newchat \u2014 New chat, same agent &amp; workspace
3123
- /cancel \u2014 Cancel current session
3124
- /status \u2014 Show session/system status
3297
+ /cancel \u2014 Cancel session (in session topic)
3298
+ /status \u2014 Show session or system status
3299
+ /sessions \u2014 List all sessions
3125
3300
  /agents \u2014 List available agents
3126
- /menu \u2014 Show interactive menu
3301
+
3302
+ \u2699\uFE0F <b>System</b>
3127
3303
  /restart \u2014 Restart OpenACP
3128
- /update \u2014 Update to latest version and restart
3129
- /help \u2014 Show this help
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
3130
3313
 
3131
- Or just chat in the \u{1F916} Assistant topic for help!`,
3314
+ \u{1F4AC} Need help? Just ask me in this topic!`,
3132
3315
  { parse_mode: "HTML" }
3133
3316
  );
3134
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
+ }
3135
3337
  function buildDangerousModeKeyboard(sessionId, enabled) {
3136
3338
  return new InlineKeyboard().text(
3137
3339
  enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
@@ -3471,6 +3673,53 @@ ${resultText}`,
3471
3673
  }
3472
3674
  });
3473
3675
  }
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;
3695
+ }
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)
3721
+ });
3722
+ }
3474
3723
  var STATIC_COMMANDS = [
3475
3724
  { command: "new", description: "Create new session" },
3476
3725
  { command: "newchat", description: "New chat, same agent & workspace" },
@@ -3484,6 +3733,7 @@ var STATIC_COMMANDS = [
3484
3733
  { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
3485
3734
  { command: "integrate", description: "Manage agent integrations" },
3486
3735
  { command: "handoff", description: "Continue this session in your terminal" },
3736
+ { command: "clear", description: "Clear assistant history" },
3487
3737
  { command: "restart", description: "Restart OpenACP" },
3488
3738
  { command: "update", description: "Update to latest version and restart" }
3489
3739
  ];
@@ -3568,6 +3818,370 @@ ${escapeHtml(request.description)}`,
3568
3818
  }
3569
3819
  };
3570
3820
 
3821
+ // src/product-guide.ts
3822
+ var PRODUCT_GUIDE = `
3823
+ # OpenACP \u2014 Product Guide
3824
+
3825
+ OpenACP lets you chat with AI coding agents (like Claude Code) through Telegram.
3826
+ You type messages in Telegram, the agent reads/writes/runs code in your project folder, and results stream back in real time.
3827
+
3828
+ ---
3829
+
3830
+ ## Quick Start
3831
+
3832
+ 1. Start OpenACP: \`openacp\` (or \`openacp start\` for background daemon)
3833
+ 2. Open your Telegram group \u2014 you'll see the Assistant topic
3834
+ 3. Tap \u{1F195} New Session or type /new
3835
+ 4. Pick an agent and a project folder
3836
+ 5. Chat in the session topic \u2014 the agent works on your code
3837
+
3838
+ ---
3839
+
3840
+ ## Core Concepts
3841
+
3842
+ ### Sessions
3843
+ A session = one conversation with one AI agent working in one project folder.
3844
+ Each session gets its own Telegram topic. Chat there to give instructions to the agent.
3845
+
3846
+ ### Agents
3847
+ An agent is an AI coding tool (e.g., Claude Code). You can configure multiple agents.
3848
+ The default agent is used when you don't specify one.
3849
+
3850
+ ### Project Folder (Workspace)
3851
+ The directory where the agent reads, writes, and runs code.
3852
+ When creating a session, you choose which folder the agent works in.
3853
+ You can type a full path like \`~/code/my-project\` or just a name like \`my-project\` (it becomes \`<base-dir>/my-project\`).
3854
+
3855
+ ### System Topics
3856
+ - **Assistant** \u2014 Always-on helper that can answer questions, create sessions, check status, troubleshoot
3857
+ - **Notifications** \u2014 System alerts (permission requests, session errors, completions)
3858
+
3859
+ ---
3860
+
3861
+ ## Creating Sessions
3862
+
3863
+ ### From menu
3864
+ Tap \u{1F195} New Session \u2192 choose agent (if multiple) \u2192 choose project folder \u2192 confirm
3865
+
3866
+ ### From command
3867
+ - \`/new\` \u2014 Interactive flow (asks agent + folder)
3868
+ - \`/new claude ~/code/my-project\` \u2014 Create directly with specific agent and folder
3869
+
3870
+ ### From Assistant topic
3871
+ Just ask: "Create a session for my-project with claude" \u2014 the assistant handles it
3872
+
3873
+ ### Quick new chat
3874
+ \`/newchat\` in a session topic \u2014 creates new session with same agent and folder as current one
3875
+
3876
+ ---
3877
+
3878
+ ## Working with Sessions
3879
+
3880
+ ### Chat
3881
+ Type messages in the session topic. The agent responds with code changes, explanations, tool outputs.
3882
+
3883
+ ### What you see while the agent works
3884
+ - **\u{1F4AD} Thinking indicator** \u2014 Shows when the agent is reasoning, with elapsed time
3885
+ - **Text responses** \u2014 Streamed in real time, updated every few seconds
3886
+ - **Tool calls** \u2014 When the agent runs commands or edits files, you see tool name, input, status, and output
3887
+ - **\u{1F4CB} Plan card** \u2014 Visual task progress with completed/in-progress/pending items and progress bar
3888
+ - **"View File" / "View Diff" buttons** \u2014 Opens in browser with Monaco editor (requires tunnel)
3889
+
3890
+ ### Session lifecycle
3891
+ 1. **Creating** \u2014 Topic created, agent spawning
3892
+ 2. **Warming up** \u2014 Agent primes its cache (happens automatically, invisible to you)
3893
+ 3. **Active** \u2014 Ready for your messages
3894
+ 4. **Auto-naming** \u2014 After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.
3895
+ 5. **Finished/Error** \u2014 Session completed or hit an error
3896
+
3897
+ ### Agent skills
3898
+ Some agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.
3899
+
3900
+ ### Permission requests
3901
+ When the agent wants to run a command, it asks for permission.
3902
+ You see buttons: \u2705 Allow, \u274C Reject (and sometimes "Always Allow").
3903
+ A notification also appears in the Notifications topic with a link to the request.
3904
+
3905
+ ### Dangerous mode
3906
+ Auto-approves ALL permission requests \u2014 the agent runs any command without asking.
3907
+ - Enable: \`/enable_dangerous\` or tap the \u2620\uFE0F button in the session
3908
+ - Disable: \`/disable_dangerous\` or tap the \u{1F510} button
3909
+ - \u26A0\uFE0F Use with caution \u2014 the agent can execute anything
3910
+
3911
+ ### Session timeout
3912
+ Idle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).
3913
+ Configure via \`security.sessionTimeoutMinutes\` in config.
3914
+
3915
+ ---
3916
+
3917
+ ## Session Transfer (Handoff)
3918
+
3919
+ ### Telegram \u2192 Terminal
3920
+ 1. Type \`/handoff\` in a session topic
3921
+ 2. You get a command like \`claude --resume <SESSION_ID>\`
3922
+ 3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
3923
+
3924
+ ### Terminal \u2192 Telegram
3925
+ 1. First time: run \`openacp integrate claude\` to install the handoff skill (one-time setup)
3926
+ 2. In Claude Code, use the /openacp:handoff slash command
3927
+ 3. The session appears as a new topic in Telegram and you can continue chatting there
3928
+
3929
+ ### How it works
3930
+ - The agent session ID is shared between platforms
3931
+ - Conversation history is preserved \u2014 pick up where you left off
3932
+ - The agent that supports resume (e.g., Claude with \`--resume\`) handles the actual transfer
3933
+
3934
+ ---
3935
+
3936
+ ## Managing Sessions
3937
+
3938
+ ### Status
3939
+ - \`/status\` \u2014 Shows active sessions count and details
3940
+ - Ask the Assistant: "What sessions are running?"
3941
+
3942
+ ### List all sessions
3943
+ - \`/sessions\` \u2014 Shows all sessions with status (active, finished, error)
3944
+
3945
+ ### Cancel
3946
+ - \`/cancel\` in a session topic \u2014 cancels that session
3947
+ - Ask the Assistant: "Cancel the stuck session"
3948
+
3949
+ ### Cleanup
3950
+ - From \`/sessions\` \u2192 tap cleanup buttons (finished, errors, all)
3951
+ - Ask the Assistant: "Clean up old sessions"
3952
+
3953
+ ---
3954
+
3955
+ ## Assistant Topic
3956
+
3957
+ The Assistant is an always-on AI helper in its own topic. It can:
3958
+ - Answer questions about OpenACP
3959
+ - Create sessions for you
3960
+ - Check status and health
3961
+ - Cancel sessions
3962
+ - Clean up old sessions
3963
+ - Troubleshoot issues
3964
+ - Manage configuration
3965
+
3966
+ Just chat naturally: "How do I create a session?", "What's the status?", "Something is stuck"
3967
+
3968
+ ### Clear history
3969
+ \`/clear\` in the Assistant topic \u2014 resets the conversation
3970
+
3971
+ ---
3972
+
3973
+ ## System Commands
3974
+
3975
+ | Command | Where | What it does |
3976
+ |---------|-------|-------------|
3977
+ | \`/new [agent] [path]\` | Anywhere | Create new session |
3978
+ | \`/newchat\` | Session topic | New session, same agent + folder |
3979
+ | \`/cancel\` | Session topic | Cancel current session |
3980
+ | \`/status\` | Anywhere | Show status |
3981
+ | \`/sessions\` | Anywhere | List all sessions |
3982
+ | \`/agents\` | Anywhere | List available agents |
3983
+ | \`/enable_dangerous\` | Session topic | Auto-approve all permissions |
3984
+ | \`/disable_dangerous\` | Session topic | Restore permission prompts |
3985
+ | \`/handoff\` | Session topic | Transfer session to terminal |
3986
+ | \`/clear\` | Assistant topic | Clear assistant history |
3987
+ | \`/menu\` | Anywhere | Show action menu |
3988
+ | \`/help\` | Anywhere | Show help |
3989
+ | \`/restart\` | Anywhere | Restart OpenACP |
3990
+ | \`/update\` | Anywhere | Update to latest version |
3991
+ | \`/integrate\` | Anywhere | Manage agent integrations |
3992
+
3993
+ ---
3994
+
3995
+ ## Menu Buttons
3996
+
3997
+ | Button | Action |
3998
+ |--------|--------|
3999
+ | \u{1F195} New Session | Create new session (interactive) |
4000
+ | \u{1F4CB} Sessions | List all sessions with cleanup options |
4001
+ | \u{1F4CA} Status | Show active/total session count |
4002
+ | \u{1F916} Agents | List available agents |
4003
+ | \u{1F517} Integrate | Manage agent integrations |
4004
+ | \u2753 Help | Show help text |
4005
+ | \u{1F504} Restart | Restart OpenACP |
4006
+ | \u2B06\uFE0F Update | Check and install updates |
4007
+
4008
+ ---
4009
+
4010
+ ## CLI Commands
4011
+
4012
+ ### Server
4013
+ - \`openacp\` \u2014 Start (uses configured mode: foreground or daemon)
4014
+ - \`openacp start\` \u2014 Start as background daemon
4015
+ - \`openacp stop\` \u2014 Stop daemon
4016
+ - \`openacp status\` \u2014 Show daemon status
4017
+ - \`openacp logs\` \u2014 Tail daemon logs
4018
+ - \`openacp --foreground\` \u2014 Force foreground mode (useful for debugging or containers)
4019
+
4020
+ ### Auto-start (run on boot)
4021
+ - macOS: installs a LaunchAgent in \`~/Library/LaunchAgents/\`
4022
+ - Linux: installs a systemd user service in \`~/.config/systemd/user/\`
4023
+ - Enabled automatically when you start the daemon. Remove with \`openacp stop\`.
4024
+
4025
+ ### Configuration
4026
+ - \`openacp config\` \u2014 Interactive config editor
4027
+ - \`openacp reset\` \u2014 Delete all data and start fresh
4028
+
4029
+ ### Plugins
4030
+ - \`openacp install <package>\` \u2014 Install adapter plugin (e.g., \`@openacp/adapter-discord\`)
4031
+ - \`openacp uninstall <package>\` \u2014 Remove adapter plugin
4032
+ - \`openacp plugins\` \u2014 List installed plugins
4033
+
4034
+ ### Integration
4035
+ - \`openacp integrate <agent>\` \u2014 Install agent integration (e.g., Claude handoff skill)
4036
+ - \`openacp integrate <agent> --uninstall\` \u2014 Remove integration
4037
+
4038
+ ### API (requires running daemon)
4039
+ \`openacp api <command>\` \u2014 Interact with running daemon:
4040
+
4041
+ | Command | Description |
4042
+ |---------|-------------|
4043
+ | \`status\` | List active sessions |
4044
+ | \`session <id>\` | Session details |
4045
+ | \`new <agent> <path>\` | Create session |
4046
+ | \`send <id> "text"\` | Send prompt |
4047
+ | \`cancel <id>\` | Cancel session |
4048
+ | \`dangerous <id> on/off\` | Toggle dangerous mode |
4049
+ | \`topics [--status x,y]\` | List topics |
4050
+ | \`delete-topic <id> [--force]\` | Delete topic |
4051
+ | \`cleanup [--status x,y]\` | Cleanup old topics |
4052
+ | \`agents\` | List agents |
4053
+ | \`health\` | System health |
4054
+ | \`config\` | Show config |
4055
+ | \`config set <key> <value>\` | Update config |
4056
+ | \`adapters\` | List adapters |
4057
+ | \`tunnel\` | Tunnel status |
4058
+ | \`notify "message"\` | Send notification |
4059
+ | \`version\` | Daemon version |
4060
+ | \`restart\` | Restart daemon |
4061
+
4062
+ ---
4063
+
4064
+ ## File Viewer (Tunnel)
4065
+
4066
+ When tunnel is enabled, file edits and diffs get "View" buttons that open in your browser:
4067
+ - **Monaco Editor** \u2014 Full VS Code editor with syntax highlighting
4068
+ - **Diff viewer** \u2014 Side-by-side or inline comparison
4069
+ - **Line highlighting** \u2014 Click lines to highlight
4070
+ - Dark/light theme toggle
4071
+
4072
+ ### Setup
4073
+ Enable in config: set \`tunnel.enabled\` to \`true\`.
4074
+ Providers: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.
4075
+
4076
+ ---
4077
+
4078
+ ## Configuration
4079
+
4080
+ Config file: \`~/.openacp/config.json\`
4081
+
4082
+ ### Telegram
4083
+ - **telegram.botToken** \u2014 Your Telegram bot token
4084
+ - **telegram.chatId** \u2014 Your Telegram supergroup ID
4085
+
4086
+ ### Agents
4087
+ - **agents.<name>.command** \u2014 Agent executable path (e.g., \`claude\`, \`codex\`)
4088
+ - **agents.<name>.args** \u2014 Arguments to pass to the agent command
4089
+ - **agents.<name>.env** \u2014 Custom environment variables for the agent subprocess
4090
+ - **defaultAgent** \u2014 Which agent to use by default
4091
+
4092
+ ### Workspace
4093
+ - **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
4094
+
4095
+ ### Security
4096
+ - **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
4097
+ - **security.maxConcurrentSessions** \u2014 Max parallel sessions (default: 5)
4098
+ - **security.sessionTimeoutMinutes** \u2014 Auto-cancel idle sessions (default: 60)
4099
+
4100
+ ### Tunnel / File Viewer
4101
+ - **tunnel.enabled** \u2014 Enable file viewer tunnel
4102
+ - **tunnel.provider** \u2014 Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale
4103
+ - **tunnel.port** \u2014 Local port for tunnel server (default: 3100)
4104
+ - **tunnel.auth.enabled** \u2014 Enable authentication for tunnel URLs
4105
+ - **tunnel.auth.token** \u2014 Auth token for tunnel access
4106
+ - **tunnel.storeTtlMinutes** \u2014 How long viewer links stay cached (default: 60)
4107
+
4108
+ ### Logging
4109
+ - **logging.level** \u2014 Log level: silent, debug, info, warn, error, fatal (default: info)
4110
+ - **logging.logDir** \u2014 Log directory (default: \`~/.openacp/logs\`)
4111
+ - **logging.maxFileSize** \u2014 Max log file size before rotation
4112
+ - **logging.maxFiles** \u2014 Max number of rotated log files
4113
+ - **logging.sessionLogRetentionDays** \u2014 Auto-delete old session logs (default: 30)
4114
+
4115
+ ### Data Retention
4116
+ - **sessionStore.ttlDays** \u2014 How long session records persist (default: 30). Old records are cleaned up automatically.
4117
+
4118
+ ### Environment variables
4119
+ Override config with env vars:
4120
+ - \`OPENACP_TELEGRAM_BOT_TOKEN\`
4121
+ - \`OPENACP_TELEGRAM_CHAT_ID\`
4122
+ - \`OPENACP_DEFAULT_AGENT\`
4123
+ - \`OPENACP_RUN_MODE\` \u2014 foreground or daemon
4124
+ - \`OPENACP_API_PORT\` \u2014 API server port (default: 21420)
4125
+ - \`OPENACP_TUNNEL_ENABLED\`
4126
+ - \`OPENACP_TUNNEL_PORT\`
4127
+ - \`OPENACP_TUNNEL_PROVIDER\`
4128
+ - \`OPENACP_LOG_LEVEL\`
4129
+ - \`OPENACP_LOG_DIR\`
4130
+ - \`OPENACP_DEBUG\` \u2014 Sets log level to debug
4131
+
4132
+ ---
4133
+
4134
+ ## Troubleshooting
4135
+
4136
+ ### Session stuck / not responding
4137
+ - Check status: ask Assistant "Is anything stuck?"
4138
+ - Cancel and create new: \`/cancel\` then \`/new\`
4139
+ - Check system health: Assistant can run health check
4140
+
4141
+ ### Agent not found
4142
+ - Check available agents: \`/agents\`
4143
+ - Verify agent command is installed and in PATH
4144
+ - Check config: agent command + args must be correct
4145
+
4146
+ ### Permission request not showing
4147
+ - Check Notifications topic for the alert
4148
+ - Try \`/enable_dangerous\` to auto-approve (if you trust the agent)
4149
+
4150
+ ### Session disappeared after restart
4151
+ - Sessions persist across restarts
4152
+ - Send a message in the old topic \u2014 it auto-resumes
4153
+ - If topic was deleted, the session record may still exist in status
4154
+
4155
+ ### Bot not responding at all
4156
+ - Check daemon: \`openacp status\`
4157
+ - Check logs: \`openacp logs\`
4158
+ - Restart: \`openacp start\` or \`/restart\`
4159
+
4160
+ ### Messages going to wrong topic
4161
+ - Each session is bound to a specific Telegram topic
4162
+ - If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session
4163
+
4164
+ ### Viewing logs
4165
+ - Session-specific logs: \`~/.openacp/logs/sessions/\`
4166
+ - System logs: \`openacp logs\` to tail live
4167
+ - Set \`OPENACP_DEBUG=true\` for verbose output
4168
+
4169
+ ---
4170
+
4171
+ ## Data & Storage
4172
+
4173
+ All data is stored in \`~/.openacp/\`:
4174
+ - \`config.json\` \u2014 Configuration
4175
+ - \`sessions/\` \u2014 Session records and state
4176
+ - \`topics/\` \u2014 Topic-to-session mappings
4177
+ - \`logs/\` \u2014 System and session logs
4178
+ - \`plugins/\` \u2014 Installed adapter plugins
4179
+ - \`openacp.pid\` \u2014 Daemon PID file
4180
+
4181
+ Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`).
4182
+ Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`).
4183
+ `;
4184
+
3571
4185
  // src/adapters/telegram/assistant.ts
3572
4186
  var log10 = createChildLogger({ module: "telegram-assistant" });
3573
4187
  async function spawnAssistant(core, adapter, assistantTopicId) {
@@ -3604,52 +4218,100 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
3604
4218
  });
3605
4219
  return { session, ready };
3606
4220
  }
4221
+ function buildWelcomeMessage(ctx) {
4222
+ const { activeCount, errorCount, totalCount, agents, defaultAgent } = ctx;
4223
+ const agentList = agents.map((a) => `${a}${a === defaultAgent ? " (default)" : ""}`).join(", ");
4224
+ if (totalCount === 0) {
4225
+ return `\u{1F44B} <b>OpenACP is ready!</b>
4226
+
4227
+ No sessions yet. Tap \u{1F195} New Session to start, or ask me anything!`;
4228
+ }
4229
+ if (errorCount > 0) {
4230
+ return `\u{1F44B} <b>OpenACP is ready!</b>
4231
+
4232
+ \u{1F4CA} ${activeCount} active, ${errorCount} errors / ${totalCount} total
4233
+ \u26A0\uFE0F ${errorCount} session${errorCount > 1 ? "s have" : " has"} errors \u2014 ask me to check if you'd like.
4234
+
4235
+ Agents: ${agentList}`;
4236
+ }
4237
+ return `\u{1F44B} <b>OpenACP is ready!</b>
4238
+
4239
+ \u{1F4CA} ${activeCount} active / ${totalCount} total
4240
+ Agents: ${agentList}`;
4241
+ }
3607
4242
  function buildAssistantSystemPrompt(ctx) {
3608
4243
  const { config, activeSessionCount, totalSessionCount, topicSummary } = ctx;
3609
4244
  const agentNames = Object.keys(config.agents).join(", ");
3610
4245
  const topicBreakdown = topicSummary.map((s) => `${s.status}: ${s.count}`).join(", ") || "none";
3611
- return `You are the OpenACP Assistant. Help users manage their AI coding sessions and topics.
4246
+ return `You are the OpenACP Assistant \u2014 a helpful guide for managing AI coding sessions.
3612
4247
 
3613
4248
  ## Current State
3614
4249
  - Active sessions: ${activeSessionCount} / ${totalSessionCount} total
3615
4250
  - Topics by status: ${topicBreakdown}
3616
4251
  - Available agents: ${agentNames}
3617
4252
  - Default agent: ${config.defaultAgent}
3618
- - Workspace base: ${config.workspace.baseDir}
4253
+ - Workspace base directory: ${config.workspace.baseDir}
4254
+
4255
+ ## Action Playbook
4256
+
4257
+ ### Create Session
4258
+ - The workspace is the project directory where the agent will work (read, write, execute code). It is NOT the base directory \u2014 it should be a specific project folder like \`~/code/my-project\` or \`${config.workspace.baseDir}/my-app\`.
4259
+ - Ask which agent to use (if multiple are configured). Show available: ${agentNames}
4260
+ - Ask which project directory to use as workspace. Suggest \`${config.workspace.baseDir}\` as the base, but explain the user can provide any path.
4261
+ - Confirm before creating: show agent name + full workspace path.
4262
+ - Create via: \`openacp api new <agent> <workspace>\`
3619
4263
 
3620
- ## Session Management Commands
3621
- These are Telegram bot commands (type directly in chat):
3622
- - /new [agent] [workspace] \u2014 Create new session
3623
- - /newchat \u2014 New chat with same agent & workspace
3624
- - /cancel \u2014 Cancel current session
3625
- - /status \u2014 Show status
3626
- - /agents \u2014 List agents
3627
- - /help \u2014 Show help
4264
+ ### Check Status / List Sessions
4265
+ - Run \`openacp api status\` for active sessions overview
4266
+ - Run \`openacp api topics\` for full list with statuses
4267
+ - Format the output nicely for the user
3628
4268
 
3629
- ## Management Commands (via CLI)
3630
- You have access to bash. Use these commands to manage OpenACP:
4269
+ ### Cancel Session
4270
+ - Run \`openacp api status\` to see what's active
4271
+ - If 1 active session \u2192 ask user to confirm \u2192 \`openacp api cancel <id>\`
4272
+ - If multiple \u2192 list them, ask user which one to cancel
3631
4273
 
3632
- ### Session management
4274
+ ### Troubleshoot (Session Stuck, Errors)
4275
+ - Run \`openacp api health\` + \`openacp api status\` to diagnose
4276
+ - Small issue (stuck session) \u2192 suggest cancel + create new
4277
+ - Big issue (system-level) \u2192 suggest restart, ask for confirmation first
4278
+
4279
+ ### Cleanup Old Sessions
4280
+ - Run \`openacp api topics --status finished,error\` to see what can be cleaned
4281
+ - Report the count, ask user to confirm
4282
+ - Execute: \`openacp api cleanup --status <statuses>\`
4283
+
4284
+ ### Configuration
4285
+ - View: \`openacp api config\`
4286
+ - Update: \`openacp api config set <key> <value>\`
4287
+
4288
+ ### Restart / Update
4289
+ - Always ask for confirmation \u2014 these are disruptive actions
4290
+ - Guide user: "Tap \u{1F504} Restart button or type /restart"
4291
+
4292
+ ### Toggle Dangerous Mode
4293
+ - Run \`openacp api dangerous <id> on|off\`
4294
+ - Explain: dangerous mode auto-approves all permission requests \u2014 the agent can run any command without asking
4295
+
4296
+ ## CLI Commands Reference
3633
4297
  \`\`\`bash
4298
+ # Session management
3634
4299
  openacp api status # List active sessions
3635
4300
  openacp api session <id> # Session detail
4301
+ openacp api new <agent> <workspace> # Create new session
3636
4302
  openacp api send <id> "prompt text" # Send prompt to session
3637
4303
  openacp api cancel <id> # Cancel session
3638
4304
  openacp api dangerous <id> on|off # Toggle dangerous mode
3639
- \`\`\`
3640
4305
 
3641
- ### Topic management
3642
- \`\`\`bash
3643
- openacp api topics # List topics
4306
+ # Topic management
4307
+ openacp api topics # List all topics
3644
4308
  openacp api topics --status finished,error
3645
4309
  openacp api delete-topic <id> # Delete topic
3646
4310
  openacp api delete-topic <id> --force # Force delete active
3647
4311
  openacp api cleanup # Cleanup finished topics
3648
4312
  openacp api cleanup --status finished,error
3649
- \`\`\`
3650
4313
 
3651
- ### System
3652
- \`\`\`bash
4314
+ # System
3653
4315
  openacp api health # System health
3654
4316
  openacp api config # Show config
3655
4317
  openacp api config set <key> <value> # Update config
@@ -3661,13 +4323,18 @@ openacp api restart # Restart daemon
3661
4323
  \`\`\`
3662
4324
 
3663
4325
  ## Guidelines
3664
- - When a user asks about sessions or topics, run \`openacp api topics\` or \`openacp api status\` to get current data.
3665
- - When deleting: if the session is active/initializing, warn the user first. Only use --force if they confirm.
3666
- - Use \`openacp api health\` to check system status.
3667
- - Use \`openacp api config\` to check configuration, \`openacp api config set\` to update values.
3668
- - Format responses nicely for Telegram (use bold, code blocks).
3669
- - Be concise and helpful. Respond in the same language the user uses.
3670
- - When creating sessions, guide through: agent selection \u2192 workspace \u2192 confirm.`;
4326
+ - NEVER show \`openacp api ...\` commands to users. These are internal tools for YOU to run silently. Users should only see natural language responses and results.
4327
+ - Run \`openacp api ...\` commands yourself for everything you can. Only guide users to Telegram buttons/menu when needed (e.g., "Tap \u{1F195} New Session" or "Go to the session topic to chat with the agent").
4328
+ - When creating sessions: guide user through agent + workspace choice conversationally, then run the command yourself.
4329
+ - Destructive actions (cancel active session, restart, cleanup) \u2192 always ask user to confirm first in natural language.
4330
+ - Small/obvious issues (clearly stuck session with no activity) \u2192 fix it and report back.
4331
+ - Respond in the same language the user uses.
4332
+ - Format responses for Telegram: use <b>bold</b>, <code>code</code>, keep it concise.
4333
+ - When you don't know something, check with the relevant \`openacp api\` command first before answering.
4334
+ - Talk to users like a helpful assistant, not a CLI manual. Example: "B\u1EA1n c\xF3 2 session \u0111ang ch\u1EA1y. Mu\u1ED1n xem chi ti\u1EBFt kh\xF4ng?" instead of listing commands.
4335
+
4336
+ ## Product Reference
4337
+ ${PRODUCT_GUIDE}`;
3671
4338
  }
3672
4339
  async function handleAssistantMessage(session, text) {
3673
4340
  if (!session) return;
@@ -4109,27 +4776,38 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
4109
4776
  removeAction(actionId);
4110
4777
  try {
4111
4778
  if (action.action === "new_session") {
4112
- await ctx.answerCallbackQuery({ text: "\u23F3 Creating session..." });
4113
- const { threadId, firstMsgId } = await executeNewSession(
4114
- bot,
4115
- core,
4116
- chatId,
4117
- action.agent,
4118
- action.workspace
4119
- );
4120
- const topicLink = `https://t.me/c/${String(chatId).replace("-100", "")}/${firstMsgId ?? threadId}`;
4121
- const originalText = ctx.callbackQuery.message?.text ?? "";
4122
- try {
4123
- await ctx.editMessageText(
4124
- originalText + `
4779
+ if (action.agent && action.workspace) {
4780
+ await ctx.answerCallbackQuery({ text: "\u23F3 Creating session..." });
4781
+ const { threadId, firstMsgId } = await executeNewSession(
4782
+ bot,
4783
+ core,
4784
+ chatId,
4785
+ action.agent,
4786
+ action.workspace
4787
+ );
4788
+ const topicLink = `https://t.me/c/${String(chatId).replace("-100", "")}/${firstMsgId ?? threadId}`;
4789
+ const originalText = ctx.callbackQuery.message?.text ?? "";
4790
+ try {
4791
+ await ctx.editMessageText(
4792
+ originalText + `
4125
4793
 
4126
4794
  \u2705 Session created \u2192 <a href="${topicLink}">Go to topic</a>`,
4127
- { parse_mode: "HTML" }
4128
- );
4129
- } catch {
4130
- await ctx.editMessageReplyMarkup({
4131
- reply_markup: { inline_keyboard: [] }
4132
- });
4795
+ { parse_mode: "HTML" }
4796
+ );
4797
+ } catch {
4798
+ await ctx.editMessageReplyMarkup({
4799
+ reply_markup: { inline_keyboard: [] }
4800
+ });
4801
+ }
4802
+ } else {
4803
+ await ctx.answerCallbackQuery();
4804
+ try {
4805
+ await ctx.editMessageReplyMarkup({
4806
+ reply_markup: { inline_keyboard: [] }
4807
+ });
4808
+ } catch {
4809
+ }
4810
+ await startInteractiveNewSession(ctx, core, chatId, action.agent);
4133
4811
  }
4134
4812
  } else if (action.action === "cancel_session") {
4135
4813
  const assistantId = getAssistantSessionId();
@@ -4303,7 +4981,23 @@ var TelegramAdapter = class extends ChannelAdapter {
4303
4981
  this.telegramConfig.chatId,
4304
4982
  {
4305
4983
  topicId: this.assistantTopicId,
4306
- getSession: () => this.assistantSession
4984
+ getSession: () => this.assistantSession,
4985
+ respawn: async () => {
4986
+ if (this.assistantSession) {
4987
+ await this.assistantSession.destroy();
4988
+ this.assistantSession = null;
4989
+ }
4990
+ const { session, ready } = await spawnAssistant(
4991
+ this.core,
4992
+ this,
4993
+ this.assistantTopicId
4994
+ );
4995
+ this.assistantSession = session;
4996
+ this.assistantInitializing = true;
4997
+ ready.then(() => {
4998
+ this.assistantInitializing = false;
4999
+ });
5000
+ }
4307
5001
  }
4308
5002
  );
4309
5003
  this.permissionHandler.setupCallbackHandler();
@@ -4356,17 +5050,14 @@ var TelegramAdapter = class extends ChannelAdapter {
4356
5050
  try {
4357
5051
  const config = this.core.configManager.get();
4358
5052
  const agents = this.core.agentManager.getAvailableAgents();
4359
- const agentList = agents.map((a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`).join(", ");
4360
- const workspace = escapeHtml(config.workspace.baseDir);
4361
5053
  const allRecords = this.core.sessionManager.listRecords();
4362
- const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
4363
- const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
4364
-
4365
- Available agents: ${agentList}
4366
- Workspace: <code>${workspace}</code>
4367
- Sessions: ${activeCount} active / ${allRecords.length} total
4368
-
4369
- <b>Select an action:</b>`;
5054
+ const welcomeText = buildWelcomeMessage({
5055
+ activeCount: allRecords.filter((r) => r.status === "active" || r.status === "initializing").length,
5056
+ errorCount: allRecords.filter((r) => r.status === "error").length,
5057
+ totalCount: allRecords.length,
5058
+ agents: agents.map((a) => a.name),
5059
+ defaultAgent: config.defaultAgent
5060
+ });
4370
5061
  await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
4371
5062
  message_thread_id: this.assistantTopicId,
4372
5063
  parse_mode: "HTML",
@@ -4411,6 +5102,9 @@ Sessions: ${activeCount} active / ${allRecords.length} total
4411
5102
  setupRoutes() {
4412
5103
  this.bot.on("message:text", async (ctx) => {
4413
5104
  const threadId = ctx.message.message_thread_id;
5105
+ if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
5106
+ return;
5107
+ }
4414
5108
  if (!threadId) {
4415
5109
  const html = redirectToAssistant(
4416
5110
  this.telegramConfig.chatId,
@@ -4457,6 +5151,10 @@ Sessions: ${activeCount} active / ${allRecords.length} total
4457
5151
  );
4458
5152
  if (!session) return;
4459
5153
  const threadId = Number(session.threadId);
5154
+ if (!threadId || isNaN(threadId)) {
5155
+ log12.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
5156
+ return;
5157
+ }
4460
5158
  switch (content.type) {
4461
5159
  case "thought": {
4462
5160
  const tracker = this.getOrCreateTracker(sessionId, threadId);
@@ -4888,4 +5586,4 @@ export {
4888
5586
  TopicManager,
4889
5587
  TelegramAdapter
4890
5588
  };
4891
- //# sourceMappingURL=chunk-BBPWAWE3.js.map
5589
+ //# sourceMappingURL=chunk-45DFYWJT.js.map