@openacp/cli 0.4.6 → 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/README.md +8 -8
- package/dist/{chunk-2M4O7AFI.js → chunk-45DFYWJT.js} +1172 -119
- package/dist/chunk-45DFYWJT.js.map +1 -0
- package/dist/{chunk-IUPHAXGA.js → chunk-6MJLVZXV.js} +3 -3
- package/dist/{chunk-3QACY5E3.js → chunk-C6YIUTGR.js} +2 -2
- package/dist/{chunk-2SY7Y2VB.js → chunk-HZD3CGPK.js} +2 -2
- package/dist/{chunk-ARWC4S35.js → chunk-UAUTLC4E.js} +13 -7
- package/dist/{chunk-ARWC4S35.js.map → chunk-UAUTLC4E.js.map} +1 -1
- package/dist/{chunk-WF5XDN4D.js → chunk-ZRFBLD3W.js} +6 -2
- package/dist/chunk-ZRFBLD3W.js.map +1 -0
- package/dist/cli.js +37 -31
- package/dist/cli.js.map +1 -1
- package/dist/{config-J5YQOMDU.js → config-H2DSEHNW.js} +2 -2
- package/dist/config-editor-SKS4LJLT.js +11 -0
- package/dist/{daemon-SLGQGRKO.js → daemon-VF6HJQXD.js} +3 -3
- package/dist/index.d.ts +18 -0
- package/dist/index.js +6 -6
- package/dist/integrate-WUPLRJD3.js +145 -0
- package/dist/integrate-WUPLRJD3.js.map +1 -0
- package/dist/{main-3POGUQPY.js → main-7T5YHFHO.js} +11 -11
- package/dist/{setup-CEDO6VWV.js → setup-FCVL75K6.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-2M4O7AFI.js.map +0 -1
- package/dist/chunk-WF5XDN4D.js.map +0 -1
- package/dist/config-editor-EPOKAEP6.js +0 -11
- package/dist/integrate-HYDSHAF3.js +0 -123
- package/dist/integrate-HYDSHAF3.js.map +0 -1
- /package/dist/{chunk-IUPHAXGA.js.map → chunk-6MJLVZXV.js.map} +0 -0
- /package/dist/{chunk-3QACY5E3.js.map → chunk-C6YIUTGR.js.map} +0 -0
- /package/dist/{chunk-2SY7Y2VB.js.map → chunk-HZD3CGPK.js.map} +0 -0
- /package/dist/{config-J5YQOMDU.js.map → config-H2DSEHNW.js.map} +0 -0
- /package/dist/{config-editor-EPOKAEP6.js.map → config-editor-SKS4LJLT.js.map} +0 -0
- /package/dist/{daemon-SLGQGRKO.js.map → daemon-VF6HJQXD.js.map} +0 -0
- /package/dist/{main-3POGUQPY.js.map → main-7T5YHFHO.js.map} +0 -0
- /package/dist/{setup-CEDO6VWV.js.map → setup-FCVL75K6.js.map} +0 -0
|
@@ -830,6 +830,7 @@ var SessionManager = class {
|
|
|
830
830
|
createdAt: session.createdAt.toISOString(),
|
|
831
831
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
832
832
|
name: session.name,
|
|
833
|
+
dangerousMode: false,
|
|
833
834
|
platform: {}
|
|
834
835
|
});
|
|
835
836
|
}
|
|
@@ -2016,7 +2017,7 @@ var ApiServer = class {
|
|
|
2016
2017
|
return;
|
|
2017
2018
|
}
|
|
2018
2019
|
target[lastKey] = value;
|
|
2019
|
-
const { ConfigSchema } = await import("./config-
|
|
2020
|
+
const { ConfigSchema } = await import("./config-H2DSEHNW.js");
|
|
2020
2021
|
const result = ConfigSchema.safeParse(cloned);
|
|
2021
2022
|
if (!result.success) {
|
|
2022
2023
|
this.sendJson(res, 400, {
|
|
@@ -2418,7 +2419,7 @@ function formatUsage(usage) {
|
|
|
2418
2419
|
return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
|
|
2419
2420
|
${bar} ${pct}%`;
|
|
2420
2421
|
}
|
|
2421
|
-
function splitMessage(text, maxLength =
|
|
2422
|
+
function splitMessage(text, maxLength = 3800) {
|
|
2422
2423
|
if (text.length <= maxLength) return [text];
|
|
2423
2424
|
const chunks = [];
|
|
2424
2425
|
let remaining = text;
|
|
@@ -2428,14 +2429,23 @@ function splitMessage(text, maxLength = 4096) {
|
|
|
2428
2429
|
break;
|
|
2429
2430
|
}
|
|
2430
2431
|
let splitAt = remaining.lastIndexOf("\n\n", maxLength);
|
|
2431
|
-
if (splitAt === -1 || splitAt < maxLength * 0.
|
|
2432
|
+
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
2432
2433
|
splitAt = remaining.lastIndexOf("\n", maxLength);
|
|
2433
2434
|
}
|
|
2434
|
-
if (splitAt === -1 || splitAt < maxLength * 0.
|
|
2435
|
+
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
2435
2436
|
splitAt = maxLength;
|
|
2436
2437
|
}
|
|
2438
|
+
const candidate = remaining.slice(0, splitAt);
|
|
2439
|
+
const fences = candidate.match(/```/g);
|
|
2440
|
+
if (fences && fences.length % 2 !== 0) {
|
|
2441
|
+
const closingFence = remaining.indexOf("```", splitAt);
|
|
2442
|
+
if (closingFence !== -1) {
|
|
2443
|
+
const afterFence = remaining.indexOf("\n", closingFence + 3);
|
|
2444
|
+
splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2437
2447
|
chunks.push(remaining.slice(0, splitAt));
|
|
2438
|
-
remaining = remaining.slice(splitAt).
|
|
2448
|
+
remaining = remaining.slice(splitAt).replace(/^\n+/, "");
|
|
2439
2449
|
}
|
|
2440
2450
|
return chunks;
|
|
2441
2451
|
}
|
|
@@ -2472,14 +2482,22 @@ var MessageDraft = class {
|
|
|
2472
2482
|
async flush() {
|
|
2473
2483
|
if (!this.buffer) return;
|
|
2474
2484
|
if (this.firstFlushPending) return;
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2485
|
+
let displayBuffer = this.buffer;
|
|
2486
|
+
if (displayBuffer.length > 3800) {
|
|
2487
|
+
let cutAt = displayBuffer.lastIndexOf("\n", 3800);
|
|
2488
|
+
if (cutAt < 800) cutAt = 3800;
|
|
2489
|
+
displayBuffer = displayBuffer.slice(0, cutAt) + "\n\u2026";
|
|
2490
|
+
}
|
|
2491
|
+
let html = markdownToTelegramHtml(displayBuffer);
|
|
2492
|
+
if (!html) return;
|
|
2493
|
+
if (html.length > 4096) {
|
|
2494
|
+
html = html.slice(0, 4090) + "\n\u2026";
|
|
2495
|
+
}
|
|
2478
2496
|
if (!this.messageId) {
|
|
2479
2497
|
this.firstFlushPending = true;
|
|
2480
2498
|
try {
|
|
2481
2499
|
const result = await this.sendQueue.enqueue(
|
|
2482
|
-
() => this.bot.api.sendMessage(this.chatId,
|
|
2500
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2483
2501
|
message_thread_id: this.threadId,
|
|
2484
2502
|
parse_mode: "HTML",
|
|
2485
2503
|
disable_notification: true
|
|
@@ -2497,7 +2515,7 @@ var MessageDraft = class {
|
|
|
2497
2515
|
} else {
|
|
2498
2516
|
try {
|
|
2499
2517
|
await this.sendQueue.enqueue(
|
|
2500
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId,
|
|
2518
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2501
2519
|
parse_mode: "HTML"
|
|
2502
2520
|
}),
|
|
2503
2521
|
{ type: "text", key: this.sessionId }
|
|
@@ -2517,21 +2535,20 @@ var MessageDraft = class {
|
|
|
2517
2535
|
if (this.messageId && this.buffer === this.lastSentBuffer) {
|
|
2518
2536
|
return this.messageId;
|
|
2519
2537
|
}
|
|
2520
|
-
const
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
const chunk = chunks[i];
|
|
2538
|
+
const mdChunks = splitMessage(this.buffer);
|
|
2539
|
+
for (let i = 0; i < mdChunks.length; i++) {
|
|
2540
|
+
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
2541
|
+
try {
|
|
2525
2542
|
if (i === 0 && this.messageId) {
|
|
2526
2543
|
await this.sendQueue.enqueue(
|
|
2527
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId,
|
|
2544
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2528
2545
|
parse_mode: "HTML"
|
|
2529
2546
|
}),
|
|
2530
2547
|
{ type: "other" }
|
|
2531
2548
|
);
|
|
2532
2549
|
} else {
|
|
2533
2550
|
const msg = await this.sendQueue.enqueue(
|
|
2534
|
-
() => this.bot.api.sendMessage(this.chatId,
|
|
2551
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2535
2552
|
message_thread_id: this.threadId,
|
|
2536
2553
|
parse_mode: "HTML",
|
|
2537
2554
|
disable_notification: true
|
|
@@ -2542,17 +2559,25 @@ var MessageDraft = class {
|
|
|
2542
2559
|
this.messageId = msg.message_id;
|
|
2543
2560
|
}
|
|
2544
2561
|
}
|
|
2545
|
-
}
|
|
2546
|
-
} catch {
|
|
2547
|
-
if (this.buffer !== this.lastSentBuffer) {
|
|
2562
|
+
} catch {
|
|
2548
2563
|
try {
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2564
|
+
if (i === 0 && this.messageId) {
|
|
2565
|
+
await this.sendQueue.enqueue(
|
|
2566
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
2567
|
+
{ type: "other" }
|
|
2568
|
+
);
|
|
2569
|
+
} else {
|
|
2570
|
+
const msg = await this.sendQueue.enqueue(
|
|
2571
|
+
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
2572
|
+
message_thread_id: this.threadId,
|
|
2573
|
+
disable_notification: true
|
|
2574
|
+
}),
|
|
2575
|
+
{ type: "other" }
|
|
2576
|
+
);
|
|
2577
|
+
if (msg) {
|
|
2578
|
+
this.messageId = msg.message_id;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2556
2581
|
} catch {
|
|
2557
2582
|
}
|
|
2558
2583
|
}
|
|
@@ -2598,11 +2623,21 @@ function buildDeepLink(chatId, messageId) {
|
|
|
2598
2623
|
// src/adapters/telegram/commands.ts
|
|
2599
2624
|
import { InlineKeyboard } from "grammy";
|
|
2600
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
|
+
}
|
|
2601
2635
|
function setupCommands(bot, core, chatId, assistant) {
|
|
2602
2636
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
2603
2637
|
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
2604
2638
|
bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
|
|
2605
2639
|
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
2640
|
+
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
2606
2641
|
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
2607
2642
|
bot.command("help", (ctx) => handleHelp(ctx));
|
|
2608
2643
|
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
@@ -2610,27 +2645,96 @@ function setupCommands(bot, core, chatId, assistant) {
|
|
|
2610
2645
|
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
2611
2646
|
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
2612
2647
|
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
2648
|
+
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
2649
|
+
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
2613
2650
|
}
|
|
2614
2651
|
function buildMenuKeyboard() {
|
|
2615
|
-
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");
|
|
2616
2653
|
}
|
|
2617
|
-
function setupMenuCallbacks(bot, core, chatId) {
|
|
2654
|
+
function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
|
|
2618
2655
|
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
2619
2656
|
const data = ctx.callbackQuery.data;
|
|
2620
2657
|
try {
|
|
2621
2658
|
await ctx.answerCallbackQuery();
|
|
2622
2659
|
} catch {
|
|
2623
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
|
+
}
|
|
2624
2734
|
switch (data) {
|
|
2625
2735
|
case "m:new":
|
|
2626
2736
|
await handleNew(ctx, core, chatId);
|
|
2627
2737
|
break;
|
|
2628
|
-
case "m:newchat":
|
|
2629
|
-
await handleNewChat(ctx, core, chatId);
|
|
2630
|
-
break;
|
|
2631
|
-
case "m:cancel":
|
|
2632
|
-
await handleCancel(ctx, core);
|
|
2633
|
-
break;
|
|
2634
2738
|
case "m:status":
|
|
2635
2739
|
await handleStatus(ctx, core);
|
|
2636
2740
|
break;
|
|
@@ -2646,6 +2750,27 @@ function setupMenuCallbacks(bot, core, chatId) {
|
|
|
2646
2750
|
case "m:update":
|
|
2647
2751
|
await handleUpdate(ctx, core);
|
|
2648
2752
|
break;
|
|
2753
|
+
case "m:integrate":
|
|
2754
|
+
await handleIntegrate(ctx, core);
|
|
2755
|
+
break;
|
|
2756
|
+
case "m:topics":
|
|
2757
|
+
await handleTopics(ctx, core);
|
|
2758
|
+
break;
|
|
2759
|
+
case "m:cleanup:finished":
|
|
2760
|
+
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
2761
|
+
break;
|
|
2762
|
+
case "m:cleanup:errors":
|
|
2763
|
+
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
2764
|
+
break;
|
|
2765
|
+
case "m:cleanup:all":
|
|
2766
|
+
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
2767
|
+
break;
|
|
2768
|
+
case "m:cleanup:everything":
|
|
2769
|
+
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
2770
|
+
break;
|
|
2771
|
+
case "m:cleanup:everything:confirm":
|
|
2772
|
+
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
2773
|
+
break;
|
|
2649
2774
|
}
|
|
2650
2775
|
});
|
|
2651
2776
|
}
|
|
@@ -2662,16 +2787,111 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2662
2787
|
const args = matchStr.split(" ").filter(Boolean);
|
|
2663
2788
|
const agentName = args[0];
|
|
2664
2789
|
const workspace = args[1];
|
|
2790
|
+
if (agentName && workspace) {
|
|
2791
|
+
await createSessionDirect(ctx, core, chatId, agentName, workspace);
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2665
2794
|
const currentThreadId = ctx.message?.message_thread_id;
|
|
2666
|
-
if (assistant && currentThreadId === assistant.topicId
|
|
2795
|
+
if (assistant && currentThreadId === assistant.topicId) {
|
|
2667
2796
|
const assistantSession = assistant.getSession();
|
|
2668
2797
|
if (assistantSession) {
|
|
2669
|
-
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).`;
|
|
2670
2799
|
await assistantSession.enqueuePrompt(prompt);
|
|
2671
2800
|
return;
|
|
2672
2801
|
}
|
|
2673
2802
|
}
|
|
2674
|
-
|
|
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)");
|
|
2675
2895
|
let threadId;
|
|
2676
2896
|
try {
|
|
2677
2897
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2680,15 +2900,9 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2680
2900
|
message_thread_id: threadId,
|
|
2681
2901
|
parse_mode: "HTML"
|
|
2682
2902
|
});
|
|
2683
|
-
const session = await core.handleNewSession(
|
|
2684
|
-
"telegram",
|
|
2685
|
-
agentName,
|
|
2686
|
-
workspace
|
|
2687
|
-
);
|
|
2903
|
+
const session = await core.handleNewSession("telegram", agentName, workspace);
|
|
2688
2904
|
session.threadId = String(threadId);
|
|
2689
|
-
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
2690
|
-
topicId: threadId
|
|
2691
|
-
});
|
|
2905
|
+
await core.sessionManager.updateSessionPlatform(session.id, { topicId: threadId });
|
|
2692
2906
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2693
2907
|
try {
|
|
2694
2908
|
await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
|
|
@@ -2696,9 +2910,11 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2696
2910
|
}
|
|
2697
2911
|
await ctx.api.sendMessage(
|
|
2698
2912
|
chatId,
|
|
2699
|
-
`\u2705 Session started
|
|
2913
|
+
`\u2705 <b>Session started</b>
|
|
2700
2914
|
<b>Agent:</b> ${escapeHtml(session.agentName)}
|
|
2701
|
-
<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.`,
|
|
2702
2918
|
{
|
|
2703
2919
|
message_thread_id: threadId,
|
|
2704
2920
|
parse_mode: "HTML",
|
|
@@ -2706,6 +2922,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2706
2922
|
}
|
|
2707
2923
|
);
|
|
2708
2924
|
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2925
|
+
return threadId ?? null;
|
|
2709
2926
|
} catch (err) {
|
|
2710
2927
|
log8.error({ err }, "Session creation failed");
|
|
2711
2928
|
if (threadId) {
|
|
@@ -2716,6 +2933,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2716
2933
|
}
|
|
2717
2934
|
const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
|
|
2718
2935
|
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
2936
|
+
return null;
|
|
2719
2937
|
}
|
|
2720
2938
|
}
|
|
2721
2939
|
async function handleNewChat(ctx, core, chatId) {
|
|
@@ -2870,6 +3088,188 @@ Total sessions: ${sessions.length}`,
|
|
|
2870
3088
|
);
|
|
2871
3089
|
}
|
|
2872
3090
|
}
|
|
3091
|
+
async function handleTopics(ctx, core) {
|
|
3092
|
+
try {
|
|
3093
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3094
|
+
const records = allRecords.filter((r) => {
|
|
3095
|
+
const platform = r.platform;
|
|
3096
|
+
return !!platform?.topicId;
|
|
3097
|
+
});
|
|
3098
|
+
const headlessCount = allRecords.length - records.length;
|
|
3099
|
+
if (records.length === 0) {
|
|
3100
|
+
const extra = headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "";
|
|
3101
|
+
await ctx.reply(`No sessions with topics found.${extra}`, { parse_mode: "HTML" });
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
const statusEmoji = {
|
|
3105
|
+
active: "\u{1F7E2}",
|
|
3106
|
+
initializing: "\u{1F7E1}",
|
|
3107
|
+
finished: "\u2705",
|
|
3108
|
+
error: "\u274C",
|
|
3109
|
+
cancelled: "\u26D4"
|
|
3110
|
+
};
|
|
3111
|
+
const statusOrder = { active: 0, initializing: 1, error: 2, finished: 3, cancelled: 4 };
|
|
3112
|
+
records.sort((a, b) => (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5));
|
|
3113
|
+
const MAX_DISPLAY = 30;
|
|
3114
|
+
const displayed = records.slice(0, MAX_DISPLAY);
|
|
3115
|
+
const lines = displayed.map((r) => {
|
|
3116
|
+
const emoji = statusEmoji[r.status] || "\u26AA";
|
|
3117
|
+
const name = r.name?.trim();
|
|
3118
|
+
const label = name ? escapeHtml(name) : `<i>${escapeHtml(r.agentName)} session</i>`;
|
|
3119
|
+
return `${emoji} ${label} <code>[${r.status}]</code>`;
|
|
3120
|
+
});
|
|
3121
|
+
const header = `<b>Sessions: ${records.length}</b>` + (headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "");
|
|
3122
|
+
const truncated = records.length > MAX_DISPLAY ? `
|
|
3123
|
+
|
|
3124
|
+
<i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
|
|
3125
|
+
const finishedCount = records.filter((r) => r.status === "finished").length;
|
|
3126
|
+
const errorCount = records.filter((r) => r.status === "error" || r.status === "cancelled").length;
|
|
3127
|
+
const activeCount = records.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
3128
|
+
const keyboard = new InlineKeyboard();
|
|
3129
|
+
if (finishedCount > 0) {
|
|
3130
|
+
keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
|
|
3131
|
+
}
|
|
3132
|
+
if (errorCount > 0) {
|
|
3133
|
+
keyboard.text(`Cleanup errors (${errorCount})`, "m:cleanup:errors").row();
|
|
3134
|
+
}
|
|
3135
|
+
if (finishedCount + errorCount > 0) {
|
|
3136
|
+
keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
|
|
3137
|
+
}
|
|
3138
|
+
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${records.length})`, "m:cleanup:everything").row();
|
|
3139
|
+
keyboard.text("Refresh", "m:topics");
|
|
3140
|
+
await ctx.reply(
|
|
3141
|
+
`${header}
|
|
3142
|
+
|
|
3143
|
+
${lines.join("\n")}${truncated}`,
|
|
3144
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3145
|
+
);
|
|
3146
|
+
} catch (err) {
|
|
3147
|
+
log8.error({ err }, "handleTopics error");
|
|
3148
|
+
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
async function handleCleanup(ctx, core, chatId, statuses) {
|
|
3153
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3154
|
+
const cleanable = allRecords.filter((r) => {
|
|
3155
|
+
const platform = r.platform;
|
|
3156
|
+
return !!platform?.topicId && statuses.includes(r.status);
|
|
3157
|
+
});
|
|
3158
|
+
if (cleanable.length === 0) {
|
|
3159
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
let deleted = 0;
|
|
3163
|
+
let failed = 0;
|
|
3164
|
+
for (const record of cleanable) {
|
|
3165
|
+
try {
|
|
3166
|
+
const topicId = record.platform?.topicId;
|
|
3167
|
+
if (topicId) {
|
|
3168
|
+
try {
|
|
3169
|
+
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3170
|
+
} catch (err) {
|
|
3171
|
+
log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
await core.sessionManager.removeRecord(record.sessionId);
|
|
3175
|
+
deleted++;
|
|
3176
|
+
} catch (err) {
|
|
3177
|
+
log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3178
|
+
failed++;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
await ctx.reply(
|
|
3182
|
+
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3183
|
+
{ parse_mode: "HTML" }
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
3187
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3188
|
+
const cleanable = allRecords.filter((r) => {
|
|
3189
|
+
const platform = r.platform;
|
|
3190
|
+
if (!platform?.topicId) return false;
|
|
3191
|
+
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3192
|
+
return true;
|
|
3193
|
+
});
|
|
3194
|
+
if (cleanable.length === 0) {
|
|
3195
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
const statusCounts = /* @__PURE__ */ new Map();
|
|
3199
|
+
for (const r of cleanable) {
|
|
3200
|
+
statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
|
|
3201
|
+
}
|
|
3202
|
+
const statusEmoji = {
|
|
3203
|
+
active: "\u{1F7E2}",
|
|
3204
|
+
initializing: "\u{1F7E1}",
|
|
3205
|
+
finished: "\u2705",
|
|
3206
|
+
error: "\u274C",
|
|
3207
|
+
cancelled: "\u26D4"
|
|
3208
|
+
};
|
|
3209
|
+
const breakdown = Array.from(statusCounts.entries()).map(([status, count]) => `${statusEmoji[status] ?? "\u26AA"} ${status}: ${count}`).join("\n");
|
|
3210
|
+
const activeCount = (statusCounts.get("active") ?? 0) + (statusCounts.get("initializing") ?? 0);
|
|
3211
|
+
const activeWarning = activeCount > 0 ? `
|
|
3212
|
+
|
|
3213
|
+
\u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
|
|
3214
|
+
const keyboard = new InlineKeyboard().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
|
|
3215
|
+
await ctx.reply(
|
|
3216
|
+
`<b>Delete ${cleanable.length} topics?</b>
|
|
3217
|
+
|
|
3218
|
+
This will:
|
|
3219
|
+
\u2022 Delete all session topics from this group
|
|
3220
|
+
\u2022 Cancel any running agent sessions
|
|
3221
|
+
\u2022 Remove all session records
|
|
3222
|
+
|
|
3223
|
+
<b>Breakdown:</b>
|
|
3224
|
+
${breakdown}${activeWarning}
|
|
3225
|
+
|
|
3226
|
+
<i>Notifications and Assistant topics will NOT be deleted.</i>`,
|
|
3227
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3228
|
+
);
|
|
3229
|
+
}
|
|
3230
|
+
async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds) {
|
|
3231
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3232
|
+
const cleanable = allRecords.filter((r) => {
|
|
3233
|
+
const platform = r.platform;
|
|
3234
|
+
if (!platform?.topicId) return false;
|
|
3235
|
+
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3236
|
+
return true;
|
|
3237
|
+
});
|
|
3238
|
+
if (cleanable.length === 0) {
|
|
3239
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
let deleted = 0;
|
|
3243
|
+
let failed = 0;
|
|
3244
|
+
for (const record of cleanable) {
|
|
3245
|
+
try {
|
|
3246
|
+
if (record.status === "active" || record.status === "initializing") {
|
|
3247
|
+
try {
|
|
3248
|
+
await core.sessionManager.cancelSession(record.sessionId);
|
|
3249
|
+
} catch (err) {
|
|
3250
|
+
log8.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
const topicId = record.platform?.topicId;
|
|
3254
|
+
if (topicId) {
|
|
3255
|
+
try {
|
|
3256
|
+
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3257
|
+
} catch (err) {
|
|
3258
|
+
log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
await core.sessionManager.removeRecord(record.sessionId);
|
|
3262
|
+
deleted++;
|
|
3263
|
+
} catch (err) {
|
|
3264
|
+
log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3265
|
+
failed++;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
await ctx.reply(
|
|
3269
|
+
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3270
|
+
{ parse_mode: "HTML" }
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
2873
3273
|
async function handleAgents(ctx, core) {
|
|
2874
3274
|
const agents = core.agentManager.getAvailableAgents();
|
|
2875
3275
|
const defaultAgent = core.configManager.get().defaultAgent;
|
|
@@ -2886,22 +3286,54 @@ No agents configured.`;
|
|
|
2886
3286
|
}
|
|
2887
3287
|
async function handleHelp(ctx) {
|
|
2888
3288
|
await ctx.reply(
|
|
2889
|
-
|
|
3289
|
+
`\u{1F4D6} <b>OpenACP Help</b>
|
|
2890
3290
|
|
|
3291
|
+
\u{1F680} <b>Getting Started</b>
|
|
3292
|
+
Tap \u{1F195} New Session to start coding with AI.
|
|
3293
|
+
Each session gets its own topic \u2014 chat there to work with the agent.
|
|
3294
|
+
|
|
3295
|
+
\u{1F4A1} <b>Common Tasks</b>
|
|
2891
3296
|
/new [agent] [workspace] \u2014 Create new session
|
|
2892
|
-
/
|
|
2893
|
-
/
|
|
2894
|
-
/
|
|
3297
|
+
/cancel \u2014 Cancel session (in session topic)
|
|
3298
|
+
/status \u2014 Show session or system status
|
|
3299
|
+
/sessions \u2014 List all sessions
|
|
2895
3300
|
/agents \u2014 List available agents
|
|
2896
|
-
|
|
3301
|
+
|
|
3302
|
+
\u2699\uFE0F <b>System</b>
|
|
2897
3303
|
/restart \u2014 Restart OpenACP
|
|
2898
|
-
/update \u2014 Update to latest version
|
|
2899
|
-
/
|
|
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
|
|
2900
3313
|
|
|
2901
|
-
|
|
3314
|
+
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
2902
3315
|
{ parse_mode: "HTML" }
|
|
2903
3316
|
);
|
|
2904
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
|
+
}
|
|
2905
3337
|
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
2906
3338
|
return new InlineKeyboard().text(
|
|
2907
3339
|
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
@@ -3126,16 +3558,182 @@ async function executeCancelSession(core, excludeSessionId) {
|
|
|
3126
3558
|
await session.cancel();
|
|
3127
3559
|
return session;
|
|
3128
3560
|
}
|
|
3561
|
+
async function handleIntegrate(ctx, _core) {
|
|
3562
|
+
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3563
|
+
const agents = listIntegrations();
|
|
3564
|
+
const keyboard = new InlineKeyboard();
|
|
3565
|
+
for (const agent of agents) {
|
|
3566
|
+
keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3567
|
+
}
|
|
3568
|
+
await ctx.reply(
|
|
3569
|
+
`<b>\u{1F517} Integrations</b>
|
|
3570
|
+
|
|
3571
|
+
Select an agent to manage its integrations.`,
|
|
3572
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
function buildAgentItemsKeyboard(agentName, items) {
|
|
3576
|
+
const keyboard = new InlineKeyboard();
|
|
3577
|
+
for (const item of items) {
|
|
3578
|
+
const installed = item.isInstalled();
|
|
3579
|
+
keyboard.text(
|
|
3580
|
+
installed ? `\u2705 ${item.name} \u2014 Uninstall` : `\u{1F4E6} ${item.name} \u2014 Install`,
|
|
3581
|
+
installed ? `i:uninstall:${agentName}:${item.id}` : `i:install:${agentName}:${item.id}`
|
|
3582
|
+
).row();
|
|
3583
|
+
}
|
|
3584
|
+
keyboard.text("\u2190 Back", "i:back").row();
|
|
3585
|
+
return keyboard;
|
|
3586
|
+
}
|
|
3587
|
+
function setupIntegrateCallbacks(bot, core) {
|
|
3588
|
+
bot.callbackQuery(/^i:/, async (ctx) => {
|
|
3589
|
+
const data = ctx.callbackQuery.data;
|
|
3590
|
+
try {
|
|
3591
|
+
await ctx.answerCallbackQuery();
|
|
3592
|
+
} catch {
|
|
3593
|
+
}
|
|
3594
|
+
if (data === "i:back") {
|
|
3595
|
+
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3596
|
+
const agents = listIntegrations();
|
|
3597
|
+
const keyboard2 = new InlineKeyboard();
|
|
3598
|
+
for (const agent of agents) {
|
|
3599
|
+
keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3600
|
+
}
|
|
3601
|
+
try {
|
|
3602
|
+
await ctx.editMessageText(
|
|
3603
|
+
`<b>\u{1F517} Integrations</b>
|
|
3604
|
+
|
|
3605
|
+
Select an agent to manage its integrations.`,
|
|
3606
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3607
|
+
);
|
|
3608
|
+
} catch {
|
|
3609
|
+
}
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
const agentMatch = data.match(/^i:agent:(.+)$/);
|
|
3613
|
+
if (agentMatch) {
|
|
3614
|
+
const agentName2 = agentMatch[1];
|
|
3615
|
+
const { getIntegration: getIntegration2 } = await import("./integrate-WUPLRJD3.js");
|
|
3616
|
+
const integration2 = getIntegration2(agentName2);
|
|
3617
|
+
if (!integration2) {
|
|
3618
|
+
await ctx.reply(`\u274C No integration available for '${escapeHtml(agentName2)}'.`, { parse_mode: "HTML" });
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
const keyboard2 = buildAgentItemsKeyboard(agentName2, integration2.items);
|
|
3622
|
+
try {
|
|
3623
|
+
await ctx.editMessageText(
|
|
3624
|
+
`<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>
|
|
3625
|
+
|
|
3626
|
+
${integration2.items.map((i) => `\u2022 <b>${escapeHtml(i.name)}</b> \u2014 ${escapeHtml(i.description)}`).join("\n")}`,
|
|
3627
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3628
|
+
);
|
|
3629
|
+
} catch {
|
|
3630
|
+
await ctx.reply(
|
|
3631
|
+
`<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>`,
|
|
3632
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
const actionMatch = data.match(/^i:(install|uninstall):([^:]+):(.+)$/);
|
|
3638
|
+
if (!actionMatch) return;
|
|
3639
|
+
const action = actionMatch[1];
|
|
3640
|
+
const agentName = actionMatch[2];
|
|
3641
|
+
const itemId = actionMatch[3];
|
|
3642
|
+
const { getIntegration } = await import("./integrate-WUPLRJD3.js");
|
|
3643
|
+
const integration = getIntegration(agentName);
|
|
3644
|
+
if (!integration) return;
|
|
3645
|
+
const item = integration.items.find((i) => i.id === itemId);
|
|
3646
|
+
if (!item) return;
|
|
3647
|
+
const result = action === "install" ? await item.install() : await item.uninstall();
|
|
3648
|
+
const installed = action === "install" && result.success;
|
|
3649
|
+
await core.configManager.save({
|
|
3650
|
+
integrations: {
|
|
3651
|
+
[agentName]: {
|
|
3652
|
+
installed,
|
|
3653
|
+
installedAt: installed ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
});
|
|
3657
|
+
const statusEmoji = result.success ? "\u2705" : "\u274C";
|
|
3658
|
+
const actionLabel = action === "install" ? "installed" : "uninstalled";
|
|
3659
|
+
const logsText = result.logs.map((l) => `<code>${escapeHtml(l)}</code>`).join("\n");
|
|
3660
|
+
const resultText = `${statusEmoji} <b>${escapeHtml(item.name)}</b> ${actionLabel}.
|
|
3661
|
+
|
|
3662
|
+
${logsText}`;
|
|
3663
|
+
const keyboard = buildAgentItemsKeyboard(agentName, integration.items);
|
|
3664
|
+
try {
|
|
3665
|
+
await ctx.editMessageText(
|
|
3666
|
+
`<b>\u{1F517} ${escapeHtml(agentName)} Integrations</b>
|
|
3667
|
+
|
|
3668
|
+
${resultText}`,
|
|
3669
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3670
|
+
);
|
|
3671
|
+
} catch {
|
|
3672
|
+
await ctx.reply(resultText, { parse_mode: "HTML" });
|
|
3673
|
+
}
|
|
3674
|
+
});
|
|
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
|
+
}
|
|
3129
3723
|
var STATIC_COMMANDS = [
|
|
3130
3724
|
{ command: "new", description: "Create new session" },
|
|
3131
3725
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
3132
3726
|
{ command: "cancel", description: "Cancel current session" },
|
|
3133
3727
|
{ command: "status", description: "Show status" },
|
|
3728
|
+
{ command: "sessions", description: "List all sessions" },
|
|
3134
3729
|
{ command: "agents", description: "List available agents" },
|
|
3135
3730
|
{ command: "help", description: "Help" },
|
|
3136
3731
|
{ command: "menu", description: "Show menu" },
|
|
3137
3732
|
{ command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
|
|
3138
3733
|
{ command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
|
|
3734
|
+
{ command: "integrate", description: "Manage agent integrations" },
|
|
3735
|
+
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
3736
|
+
{ command: "clear", description: "Clear assistant history" },
|
|
3139
3737
|
{ command: "restart", description: "Restart OpenACP" },
|
|
3140
3738
|
{ command: "update", description: "Update to latest version and restart" }
|
|
3141
3739
|
];
|
|
@@ -3220,6 +3818,370 @@ ${escapeHtml(request.description)}`,
|
|
|
3220
3818
|
}
|
|
3221
3819
|
};
|
|
3222
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
|
+
|
|
3223
4185
|
// src/adapters/telegram/assistant.ts
|
|
3224
4186
|
var log10 = createChildLogger({ module: "telegram-assistant" });
|
|
3225
4187
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
@@ -3256,52 +4218,100 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
3256
4218
|
});
|
|
3257
4219
|
return { session, ready };
|
|
3258
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
|
+
}
|
|
3259
4242
|
function buildAssistantSystemPrompt(ctx) {
|
|
3260
4243
|
const { config, activeSessionCount, totalSessionCount, topicSummary } = ctx;
|
|
3261
4244
|
const agentNames = Object.keys(config.agents).join(", ");
|
|
3262
4245
|
const topicBreakdown = topicSummary.map((s) => `${s.status}: ${s.count}`).join(", ") || "none";
|
|
3263
|
-
return `You are the OpenACP Assistant
|
|
4246
|
+
return `You are the OpenACP Assistant \u2014 a helpful guide for managing AI coding sessions.
|
|
3264
4247
|
|
|
3265
4248
|
## Current State
|
|
3266
4249
|
- Active sessions: ${activeSessionCount} / ${totalSessionCount} total
|
|
3267
4250
|
- Topics by status: ${topicBreakdown}
|
|
3268
4251
|
- Available agents: ${agentNames}
|
|
3269
4252
|
- Default agent: ${config.defaultAgent}
|
|
3270
|
-
- 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>\`
|
|
4263
|
+
|
|
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
|
|
4268
|
+
|
|
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
|
|
3271
4273
|
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
-
|
|
3275
|
-
-
|
|
3276
|
-
- /cancel \u2014 Cancel current session
|
|
3277
|
-
- /status \u2014 Show status
|
|
3278
|
-
- /agents \u2014 List agents
|
|
3279
|
-
- /help \u2014 Show help
|
|
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
|
|
3280
4278
|
|
|
3281
|
-
|
|
3282
|
-
|
|
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>\`
|
|
3283
4283
|
|
|
3284
|
-
###
|
|
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
|
|
3285
4297
|
\`\`\`bash
|
|
4298
|
+
# Session management
|
|
3286
4299
|
openacp api status # List active sessions
|
|
3287
4300
|
openacp api session <id> # Session detail
|
|
4301
|
+
openacp api new <agent> <workspace> # Create new session
|
|
3288
4302
|
openacp api send <id> "prompt text" # Send prompt to session
|
|
3289
4303
|
openacp api cancel <id> # Cancel session
|
|
3290
4304
|
openacp api dangerous <id> on|off # Toggle dangerous mode
|
|
3291
|
-
\`\`\`
|
|
3292
4305
|
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
openacp api topics # List topics
|
|
4306
|
+
# Topic management
|
|
4307
|
+
openacp api topics # List all topics
|
|
3296
4308
|
openacp api topics --status finished,error
|
|
3297
4309
|
openacp api delete-topic <id> # Delete topic
|
|
3298
4310
|
openacp api delete-topic <id> --force # Force delete active
|
|
3299
4311
|
openacp api cleanup # Cleanup finished topics
|
|
3300
4312
|
openacp api cleanup --status finished,error
|
|
3301
|
-
\`\`\`
|
|
3302
4313
|
|
|
3303
|
-
|
|
3304
|
-
\`\`\`bash
|
|
4314
|
+
# System
|
|
3305
4315
|
openacp api health # System health
|
|
3306
4316
|
openacp api config # Show config
|
|
3307
4317
|
openacp api config set <key> <value> # Update config
|
|
@@ -3313,13 +4323,18 @@ openacp api restart # Restart daemon
|
|
|
3313
4323
|
\`\`\`
|
|
3314
4324
|
|
|
3315
4325
|
## Guidelines
|
|
3316
|
-
-
|
|
3317
|
-
-
|
|
3318
|
-
-
|
|
3319
|
-
-
|
|
3320
|
-
-
|
|
3321
|
-
-
|
|
3322
|
-
-
|
|
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}`;
|
|
3323
4338
|
}
|
|
3324
4339
|
async function handleAssistantMessage(session, text) {
|
|
3325
4340
|
if (!session) return;
|
|
@@ -3761,27 +4776,38 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
3761
4776
|
removeAction(actionId);
|
|
3762
4777
|
try {
|
|
3763
4778
|
if (action.action === "new_session") {
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
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 + `
|
|
3777
4793
|
|
|
3778
4794
|
\u2705 Session created \u2192 <a href="${topicLink}">Go to topic</a>`,
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
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);
|
|
3785
4811
|
}
|
|
3786
4812
|
} else if (action.action === "cancel_session") {
|
|
3787
4813
|
const assistantId = getAssistantSessionId();
|
|
@@ -3942,10 +4968,12 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3942
4968
|
this.telegramConfig.chatId,
|
|
3943
4969
|
() => this.assistantSession?.id
|
|
3944
4970
|
);
|
|
4971
|
+
setupIntegrateCallbacks(this.bot, this.core);
|
|
3945
4972
|
setupMenuCallbacks(
|
|
3946
4973
|
this.bot,
|
|
3947
4974
|
this.core,
|
|
3948
|
-
this.telegramConfig.chatId
|
|
4975
|
+
this.telegramConfig.chatId,
|
|
4976
|
+
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId }
|
|
3949
4977
|
);
|
|
3950
4978
|
setupCommands(
|
|
3951
4979
|
this.bot,
|
|
@@ -3953,7 +4981,23 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3953
4981
|
this.telegramConfig.chatId,
|
|
3954
4982
|
{
|
|
3955
4983
|
topicId: this.assistantTopicId,
|
|
3956
|
-
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
|
+
}
|
|
3957
5001
|
}
|
|
3958
5002
|
);
|
|
3959
5003
|
this.permissionHandler.setupCallbackHandler();
|
|
@@ -3967,24 +5011,26 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3967
5011
|
return;
|
|
3968
5012
|
}
|
|
3969
5013
|
const session = this.core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
3970
|
-
|
|
3971
|
-
|
|
5014
|
+
const record = session ? void 0 : this.core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
5015
|
+
const agentName = session?.agentName ?? record?.agentName;
|
|
5016
|
+
const agentSessionId = session?.agentSessionId ?? record?.agentSessionId;
|
|
5017
|
+
if (!agentName || !agentSessionId) {
|
|
5018
|
+
await ctx.reply("No session found for this topic.", {
|
|
3972
5019
|
message_thread_id: threadId
|
|
3973
5020
|
});
|
|
3974
5021
|
return;
|
|
3975
5022
|
}
|
|
3976
5023
|
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-7HC6D4CH.js");
|
|
3977
|
-
const caps = getAgentCapabilities2(
|
|
5024
|
+
const caps = getAgentCapabilities2(agentName);
|
|
3978
5025
|
if (!caps.supportsResume || !caps.resumeCommand) {
|
|
3979
|
-
await ctx.reply("This agent does not support
|
|
5026
|
+
await ctx.reply("This agent does not support session transfer.", {
|
|
3980
5027
|
message_thread_id: threadId
|
|
3981
5028
|
});
|
|
3982
5029
|
return;
|
|
3983
5030
|
}
|
|
3984
|
-
const agentSessionId = session.agentSessionId;
|
|
3985
5031
|
const command = caps.resumeCommand(agentSessionId);
|
|
3986
5032
|
await ctx.reply(
|
|
3987
|
-
`
|
|
5033
|
+
`Run this in your terminal to continue the session:
|
|
3988
5034
|
|
|
3989
5035
|
<code>${command}</code>`,
|
|
3990
5036
|
{
|
|
@@ -4004,14 +5050,14 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4004
5050
|
try {
|
|
4005
5051
|
const config = this.core.configManager.get();
|
|
4006
5052
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
4007
|
-
const
|
|
4008
|
-
const
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
5053
|
+
const allRecords = this.core.sessionManager.listRecords();
|
|
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
|
+
});
|
|
4015
5061
|
await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
|
|
4016
5062
|
message_thread_id: this.assistantTopicId,
|
|
4017
5063
|
parse_mode: "HTML",
|
|
@@ -4056,6 +5102,9 @@ Workspace: <code>${workspace}</code>
|
|
|
4056
5102
|
setupRoutes() {
|
|
4057
5103
|
this.bot.on("message:text", async (ctx) => {
|
|
4058
5104
|
const threadId = ctx.message.message_thread_id;
|
|
5105
|
+
if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
4059
5108
|
if (!threadId) {
|
|
4060
5109
|
const html = redirectToAssistant(
|
|
4061
5110
|
this.telegramConfig.chatId,
|
|
@@ -4102,6 +5151,10 @@ Workspace: <code>${workspace}</code>
|
|
|
4102
5151
|
);
|
|
4103
5152
|
if (!session) return;
|
|
4104
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
|
+
}
|
|
4105
5158
|
switch (content.type) {
|
|
4106
5159
|
case "thought": {
|
|
4107
5160
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
@@ -4533,4 +5586,4 @@ export {
|
|
|
4533
5586
|
TopicManager,
|
|
4534
5587
|
TelegramAdapter
|
|
4535
5588
|
};
|
|
4536
|
-
//# sourceMappingURL=chunk-
|
|
5589
|
+
//# sourceMappingURL=chunk-45DFYWJT.js.map
|