@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.
- package/dist/{chunk-BBPWAWE3.js → chunk-45DFYWJT.js} +784 -86
- package/dist/chunk-45DFYWJT.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/{main-NV7YN3VY.js → main-7T5YHFHO.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-BBPWAWE3.js.map +0 -1
- /package/dist/{main-NV7YN3VY.js.map → main-7T5YHFHO.js.map} +0 -0
|
@@ -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{
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/
|
|
3123
|
-
/
|
|
3124
|
-
/
|
|
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
|
-
|
|
3301
|
+
|
|
3302
|
+
\u2699\uFE0F <b>System</b>
|
|
3127
3303
|
/restart \u2014 Restart OpenACP
|
|
3128
|
-
/update \u2014 Update to latest version
|
|
3129
|
-
/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3621
|
-
|
|
3622
|
-
-
|
|
3623
|
-
-
|
|
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
|
-
|
|
3630
|
-
|
|
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
|
|
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
|
-
|
|
3642
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
3665
|
-
-
|
|
3666
|
-
-
|
|
3667
|
-
-
|
|
3668
|
-
-
|
|
3669
|
-
-
|
|
3670
|
-
-
|
|
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
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
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
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
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
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
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-
|
|
5589
|
+
//# sourceMappingURL=chunk-45DFYWJT.js.map
|