@openacp/cli 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BBPWAWE3.js → chunk-KPI4HGJC.js} +1236 -453
- package/dist/chunk-KPI4HGJC.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/{main-NV7YN3VY.js → main-G6XDM7EZ.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-BBPWAWE3.js.map +0 -1
- /package/dist/{main-NV7YN3VY.js.map → main-G6XDM7EZ.js.map} +0 -0
|
@@ -2428,12 +2428,14 @@ function splitMessage(text, maxLength = 3800) {
|
|
|
2428
2428
|
chunks.push(remaining);
|
|
2429
2429
|
break;
|
|
2430
2430
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2431
|
+
const wouldLeaveSmall = remaining.length < maxLength * 1.3;
|
|
2432
|
+
const searchLimit = wouldLeaveSmall ? Math.floor(remaining.length / 2) + 300 : maxLength;
|
|
2433
|
+
let splitAt = remaining.lastIndexOf("\n\n", searchLimit);
|
|
2434
|
+
if (splitAt === -1 || splitAt < searchLimit * 0.2) {
|
|
2435
|
+
splitAt = remaining.lastIndexOf("\n", searchLimit);
|
|
2434
2436
|
}
|
|
2435
|
-
if (splitAt === -1 || splitAt <
|
|
2436
|
-
splitAt =
|
|
2437
|
+
if (splitAt === -1 || splitAt < searchLimit * 0.2) {
|
|
2438
|
+
splitAt = searchLimit;
|
|
2437
2439
|
}
|
|
2438
2440
|
const candidate = remaining.slice(0, splitAt);
|
|
2439
2441
|
const fences = candidate.match(/```/g);
|
|
@@ -2466,6 +2468,7 @@ var MessageDraft = class {
|
|
|
2466
2468
|
flushTimer;
|
|
2467
2469
|
flushPromise = Promise.resolve();
|
|
2468
2470
|
lastSentBuffer = "";
|
|
2471
|
+
displayTruncated = false;
|
|
2469
2472
|
append(text) {
|
|
2470
2473
|
if (!text) return;
|
|
2471
2474
|
this.buffer += text;
|
|
@@ -2482,16 +2485,20 @@ var MessageDraft = class {
|
|
|
2482
2485
|
async flush() {
|
|
2483
2486
|
if (!this.buffer) return;
|
|
2484
2487
|
if (this.firstFlushPending) return;
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
let cutAt = displayBuffer.lastIndexOf("\n", 3800);
|
|
2488
|
-
if (cutAt < 800) cutAt = 3800;
|
|
2489
|
-
displayBuffer = displayBuffer.slice(0, cutAt) + "\n\u2026";
|
|
2490
|
-
}
|
|
2491
|
-
let html = markdownToTelegramHtml(displayBuffer);
|
|
2488
|
+
const snapshot = this.buffer;
|
|
2489
|
+
let html = markdownToTelegramHtml(snapshot);
|
|
2492
2490
|
if (!html) return;
|
|
2491
|
+
let truncated = false;
|
|
2493
2492
|
if (html.length > 4096) {
|
|
2494
|
-
|
|
2493
|
+
const ratio = 4e3 / html.length;
|
|
2494
|
+
const targetLen = Math.floor(snapshot.length * ratio);
|
|
2495
|
+
let cutAt = snapshot.lastIndexOf("\n", targetLen);
|
|
2496
|
+
if (cutAt < targetLen * 0.5) cutAt = targetLen;
|
|
2497
|
+
html = markdownToTelegramHtml(snapshot.slice(0, cutAt) + "\n\u2026");
|
|
2498
|
+
truncated = true;
|
|
2499
|
+
if (html.length > 4096) {
|
|
2500
|
+
html = html.slice(0, 4090) + "\n\u2026";
|
|
2501
|
+
}
|
|
2495
2502
|
}
|
|
2496
2503
|
if (!this.messageId) {
|
|
2497
2504
|
this.firstFlushPending = true;
|
|
@@ -2506,7 +2513,12 @@ var MessageDraft = class {
|
|
|
2506
2513
|
);
|
|
2507
2514
|
if (result) {
|
|
2508
2515
|
this.messageId = result.message_id;
|
|
2509
|
-
|
|
2516
|
+
if (!truncated) {
|
|
2517
|
+
this.lastSentBuffer = snapshot;
|
|
2518
|
+
this.displayTruncated = false;
|
|
2519
|
+
} else {
|
|
2520
|
+
this.displayTruncated = true;
|
|
2521
|
+
}
|
|
2510
2522
|
}
|
|
2511
2523
|
} catch {
|
|
2512
2524
|
} finally {
|
|
@@ -2514,13 +2526,20 @@ var MessageDraft = class {
|
|
|
2514
2526
|
}
|
|
2515
2527
|
} else {
|
|
2516
2528
|
try {
|
|
2517
|
-
await this.sendQueue.enqueue(
|
|
2529
|
+
const result = await this.sendQueue.enqueue(
|
|
2518
2530
|
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2519
2531
|
parse_mode: "HTML"
|
|
2520
2532
|
}),
|
|
2521
2533
|
{ type: "text", key: this.sessionId }
|
|
2522
2534
|
);
|
|
2523
|
-
|
|
2535
|
+
if (result !== void 0) {
|
|
2536
|
+
if (!truncated) {
|
|
2537
|
+
this.lastSentBuffer = snapshot;
|
|
2538
|
+
this.displayTruncated = false;
|
|
2539
|
+
} else {
|
|
2540
|
+
this.displayTruncated = true;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2524
2543
|
} catch {
|
|
2525
2544
|
}
|
|
2526
2545
|
}
|
|
@@ -2532,9 +2551,34 @@ var MessageDraft = class {
|
|
|
2532
2551
|
}
|
|
2533
2552
|
await this.flushPromise;
|
|
2534
2553
|
if (!this.buffer) return this.messageId;
|
|
2535
|
-
if (this.messageId && this.buffer === this.lastSentBuffer) {
|
|
2554
|
+
if (this.messageId && this.buffer === this.lastSentBuffer && !this.displayTruncated) {
|
|
2536
2555
|
return this.messageId;
|
|
2537
2556
|
}
|
|
2557
|
+
const fullHtml = markdownToTelegramHtml(this.buffer);
|
|
2558
|
+
if (fullHtml.length <= 4096) {
|
|
2559
|
+
try {
|
|
2560
|
+
if (this.messageId) {
|
|
2561
|
+
await this.sendQueue.enqueue(
|
|
2562
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, fullHtml, {
|
|
2563
|
+
parse_mode: "HTML"
|
|
2564
|
+
}),
|
|
2565
|
+
{ type: "other" }
|
|
2566
|
+
);
|
|
2567
|
+
} else {
|
|
2568
|
+
const msg = await this.sendQueue.enqueue(
|
|
2569
|
+
() => this.bot.api.sendMessage(this.chatId, fullHtml, {
|
|
2570
|
+
message_thread_id: this.threadId,
|
|
2571
|
+
parse_mode: "HTML",
|
|
2572
|
+
disable_notification: true
|
|
2573
|
+
}),
|
|
2574
|
+
{ type: "other" }
|
|
2575
|
+
);
|
|
2576
|
+
if (msg) this.messageId = msg.message_id;
|
|
2577
|
+
}
|
|
2578
|
+
return this.messageId;
|
|
2579
|
+
} catch {
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2538
2582
|
const mdChunks = splitMessage(this.buffer);
|
|
2539
2583
|
for (let i = 0; i < mdChunks.length; i++) {
|
|
2540
2584
|
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
@@ -2620,89 +2664,192 @@ function buildDeepLink(chatId, messageId) {
|
|
|
2620
2664
|
return `https://t.me/c/${cleanId}/${messageId}`;
|
|
2621
2665
|
}
|
|
2622
2666
|
|
|
2623
|
-
// src/adapters/telegram/commands.ts
|
|
2667
|
+
// src/adapters/telegram/commands/new-session.ts
|
|
2668
|
+
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2669
|
+
|
|
2670
|
+
// src/adapters/telegram/commands/admin.ts
|
|
2624
2671
|
import { InlineKeyboard } from "grammy";
|
|
2625
|
-
var log8 = createChildLogger({ module: "telegram-
|
|
2626
|
-
function
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
2632
|
-
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
2633
|
-
bot.command("help", (ctx) => handleHelp(ctx));
|
|
2634
|
-
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
2635
|
-
bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
|
|
2636
|
-
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
2637
|
-
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
2638
|
-
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
2639
|
-
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
2640
|
-
}
|
|
2641
|
-
function buildMenuKeyboard() {
|
|
2642
|
-
return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F4CB} Sessions", "m:topics").text("\u{1F916} Agents", "m:agents").row().text("\u{1F517} Integrate", "m:integrate").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
|
|
2672
|
+
var log8 = createChildLogger({ module: "telegram-cmd-admin" });
|
|
2673
|
+
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
2674
|
+
return new InlineKeyboard().text(
|
|
2675
|
+
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
2676
|
+
`d:${sessionId}`
|
|
2677
|
+
);
|
|
2643
2678
|
}
|
|
2644
|
-
function
|
|
2645
|
-
bot.callbackQuery(/^
|
|
2646
|
-
const
|
|
2679
|
+
function setupDangerousModeCallbacks(bot, core) {
|
|
2680
|
+
bot.callbackQuery(/^d:/, async (ctx) => {
|
|
2681
|
+
const sessionId = ctx.callbackQuery.data.slice(2);
|
|
2682
|
+
const session = core.sessionManager.getSession(sessionId);
|
|
2683
|
+
if (session) {
|
|
2684
|
+
session.dangerousMode = !session.dangerousMode;
|
|
2685
|
+
log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
2686
|
+
core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
|
|
2687
|
+
});
|
|
2688
|
+
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2689
|
+
try {
|
|
2690
|
+
await ctx.answerCallbackQuery({ text: toastText2 });
|
|
2691
|
+
} catch {
|
|
2692
|
+
}
|
|
2693
|
+
try {
|
|
2694
|
+
await ctx.editMessageReplyMarkup({
|
|
2695
|
+
reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
|
|
2696
|
+
});
|
|
2697
|
+
} catch {
|
|
2698
|
+
}
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const record = core.sessionManager.getSessionRecord(sessionId);
|
|
2702
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2703
|
+
try {
|
|
2704
|
+
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
|
|
2705
|
+
} catch {
|
|
2706
|
+
}
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
2710
|
+
core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
|
|
2711
|
+
});
|
|
2712
|
+
log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
2713
|
+
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2647
2714
|
try {
|
|
2648
|
-
await ctx.answerCallbackQuery();
|
|
2715
|
+
await ctx.answerCallbackQuery({ text: toastText });
|
|
2649
2716
|
} catch {
|
|
2650
2717
|
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
await handleNewChat(ctx, core, chatId);
|
|
2657
|
-
break;
|
|
2658
|
-
case "m:cancel":
|
|
2659
|
-
await handleCancel(ctx, core);
|
|
2660
|
-
break;
|
|
2661
|
-
case "m:status":
|
|
2662
|
-
await handleStatus(ctx, core);
|
|
2663
|
-
break;
|
|
2664
|
-
case "m:agents":
|
|
2665
|
-
await handleAgents(ctx, core);
|
|
2666
|
-
break;
|
|
2667
|
-
case "m:help":
|
|
2668
|
-
await handleHelp(ctx);
|
|
2669
|
-
break;
|
|
2670
|
-
case "m:restart":
|
|
2671
|
-
await handleRestart(ctx, core);
|
|
2672
|
-
break;
|
|
2673
|
-
case "m:update":
|
|
2674
|
-
await handleUpdate(ctx, core);
|
|
2675
|
-
break;
|
|
2676
|
-
case "m:integrate":
|
|
2677
|
-
await handleIntegrate(ctx, core);
|
|
2678
|
-
break;
|
|
2679
|
-
case "m:topics":
|
|
2680
|
-
await handleTopics(ctx, core);
|
|
2681
|
-
break;
|
|
2682
|
-
case "m:cleanup:finished":
|
|
2683
|
-
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
2684
|
-
break;
|
|
2685
|
-
case "m:cleanup:errors":
|
|
2686
|
-
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
2687
|
-
break;
|
|
2688
|
-
case "m:cleanup:all":
|
|
2689
|
-
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
2690
|
-
break;
|
|
2691
|
-
case "m:cleanup:everything":
|
|
2692
|
-
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
2693
|
-
break;
|
|
2694
|
-
case "m:cleanup:everything:confirm":
|
|
2695
|
-
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
2696
|
-
break;
|
|
2718
|
+
try {
|
|
2719
|
+
await ctx.editMessageReplyMarkup({
|
|
2720
|
+
reply_markup: buildDangerousModeKeyboard(sessionId, newDangerousMode)
|
|
2721
|
+
});
|
|
2722
|
+
} catch {
|
|
2697
2723
|
}
|
|
2698
2724
|
});
|
|
2699
2725
|
}
|
|
2700
|
-
async function
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
parse_mode: "HTML"
|
|
2704
|
-
|
|
2705
|
-
}
|
|
2726
|
+
async function handleEnableDangerous(ctx, core) {
|
|
2727
|
+
const threadId = ctx.message?.message_thread_id;
|
|
2728
|
+
if (!threadId) {
|
|
2729
|
+
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
2733
|
+
if (session) {
|
|
2734
|
+
if (session.dangerousMode) {
|
|
2735
|
+
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
session.dangerousMode = true;
|
|
2739
|
+
core.sessionManager.updateSessionDangerousMode(session.id, true).catch(() => {
|
|
2740
|
+
});
|
|
2741
|
+
} else {
|
|
2742
|
+
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2743
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2744
|
+
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
if (record.dangerousMode) {
|
|
2748
|
+
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
core.sessionManager.updateSessionDangerousMode(record.sessionId, true).catch(() => {
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
await ctx.reply(
|
|
2755
|
+
`\u26A0\uFE0F <b>Dangerous mode enabled</b>
|
|
2756
|
+
|
|
2757
|
+
All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
|
|
2758
|
+
|
|
2759
|
+
Use /disable_dangerous to restore normal behaviour.`,
|
|
2760
|
+
{ parse_mode: "HTML" }
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
async function handleDisableDangerous(ctx, core) {
|
|
2764
|
+
const threadId = ctx.message?.message_thread_id;
|
|
2765
|
+
if (!threadId) {
|
|
2766
|
+
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
2770
|
+
if (session) {
|
|
2771
|
+
if (!session.dangerousMode) {
|
|
2772
|
+
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
session.dangerousMode = false;
|
|
2776
|
+
core.sessionManager.updateSessionDangerousMode(session.id, false).catch(() => {
|
|
2777
|
+
});
|
|
2778
|
+
} else {
|
|
2779
|
+
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2780
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2781
|
+
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
if (!record.dangerousMode) {
|
|
2785
|
+
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
core.sessionManager.updateSessionDangerousMode(record.sessionId, false).catch(() => {
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
|
|
2792
|
+
}
|
|
2793
|
+
async function handleUpdate(ctx, core) {
|
|
2794
|
+
if (!core.requestRestart) {
|
|
2795
|
+
await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
|
|
2799
|
+
const current = getCurrentVersion();
|
|
2800
|
+
const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
|
|
2801
|
+
const latest = await getLatestVersion();
|
|
2802
|
+
if (!latest) {
|
|
2803
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
if (compareVersions(current, latest) >= 0) {
|
|
2807
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
await ctx.api.editMessageText(
|
|
2811
|
+
ctx.chat.id,
|
|
2812
|
+
statusMsg.message_id,
|
|
2813
|
+
`\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
|
|
2814
|
+
{ parse_mode: "HTML" }
|
|
2815
|
+
);
|
|
2816
|
+
const ok = await runUpdate();
|
|
2817
|
+
if (!ok) {
|
|
2818
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
await ctx.api.editMessageText(
|
|
2822
|
+
ctx.chat.id,
|
|
2823
|
+
statusMsg.message_id,
|
|
2824
|
+
`\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
|
|
2825
|
+
{ parse_mode: "HTML" }
|
|
2826
|
+
);
|
|
2827
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2828
|
+
await core.requestRestart();
|
|
2829
|
+
}
|
|
2830
|
+
async function handleRestart(ctx, core) {
|
|
2831
|
+
if (!core.requestRestart) {
|
|
2832
|
+
await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
|
|
2836
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2837
|
+
await core.requestRestart();
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// src/adapters/telegram/commands/new-session.ts
|
|
2841
|
+
var log9 = createChildLogger({ module: "telegram-cmd-new-session" });
|
|
2842
|
+
var pendingNewSessions = /* @__PURE__ */ new Map();
|
|
2843
|
+
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2844
|
+
function cleanupPending(userId) {
|
|
2845
|
+
const pending = pendingNewSessions.get(userId);
|
|
2846
|
+
if (pending) {
|
|
2847
|
+
clearTimeout(pending.timer);
|
|
2848
|
+
pendingNewSessions.delete(userId);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
function botFromCtx(ctx) {
|
|
2852
|
+
return { api: ctx.api };
|
|
2706
2853
|
}
|
|
2707
2854
|
async function handleNew(ctx, core, chatId, assistant) {
|
|
2708
2855
|
const rawMatch = ctx.match;
|
|
@@ -2710,16 +2857,112 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2710
2857
|
const args = matchStr.split(" ").filter(Boolean);
|
|
2711
2858
|
const agentName = args[0];
|
|
2712
2859
|
const workspace = args[1];
|
|
2860
|
+
if (agentName && workspace) {
|
|
2861
|
+
await createSessionDirect(ctx, core, chatId, agentName, workspace);
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2713
2864
|
const currentThreadId = ctx.message?.message_thread_id;
|
|
2714
|
-
if (assistant && currentThreadId === assistant.topicId
|
|
2865
|
+
if (assistant && currentThreadId === assistant.topicId) {
|
|
2715
2866
|
const assistantSession = assistant.getSession();
|
|
2716
2867
|
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
|
|
2868
|
+
const prompt = agentName ? `User wants to create a new session with agent "${agentName}" but didn't specify a workspace. Ask them which project directory to use as workspace.` : `User wants to create a new session. Guide them through choosing an agent and workspace (project directory).`;
|
|
2718
2869
|
await assistantSession.enqueuePrompt(prompt);
|
|
2719
2870
|
return;
|
|
2720
2871
|
}
|
|
2721
2872
|
}
|
|
2722
|
-
|
|
2873
|
+
const userId = ctx.from?.id;
|
|
2874
|
+
if (!userId) return;
|
|
2875
|
+
const agents = core.agentManager.getAvailableAgents();
|
|
2876
|
+
const config = core.configManager.get();
|
|
2877
|
+
if (agentName || agents.length === 1) {
|
|
2878
|
+
const selectedAgent = agentName || config.defaultAgent;
|
|
2879
|
+
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
const keyboard = new InlineKeyboard2();
|
|
2883
|
+
for (const agent of agents) {
|
|
2884
|
+
const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
2885
|
+
keyboard.text(label, `m:new:agent:${agent.name}`).row();
|
|
2886
|
+
}
|
|
2887
|
+
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
2888
|
+
const msg = await ctx.reply(
|
|
2889
|
+
`\u{1F916} <b>Choose an agent:</b>`,
|
|
2890
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
2891
|
+
);
|
|
2892
|
+
cleanupPending(userId);
|
|
2893
|
+
pendingNewSessions.set(userId, {
|
|
2894
|
+
step: "agent",
|
|
2895
|
+
messageId: msg.message_id,
|
|
2896
|
+
threadId: currentThreadId,
|
|
2897
|
+
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
async function startWorkspaceStep(ctx, core, chatId, userId, agentName) {
|
|
2901
|
+
const config = core.configManager.get();
|
|
2902
|
+
const baseDir = config.workspace.baseDir;
|
|
2903
|
+
const keyboard = new InlineKeyboard2().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom").row().text("\u274C Cancel", "m:new:cancel");
|
|
2904
|
+
const text = `\u{1F4C1} <b>Where should ${escapeHtml(agentName)} work?</b>
|
|
2905
|
+
|
|
2906
|
+
Enter the path to your project folder \u2014 the agent will read, write, and run code there.
|
|
2907
|
+
|
|
2908
|
+
Or use the default directory below:`;
|
|
2909
|
+
let msg;
|
|
2910
|
+
try {
|
|
2911
|
+
const pending = pendingNewSessions.get(userId);
|
|
2912
|
+
if (pending?.messageId) {
|
|
2913
|
+
await ctx.api.editMessageText(chatId, pending.messageId, text, {
|
|
2914
|
+
parse_mode: "HTML",
|
|
2915
|
+
reply_markup: keyboard
|
|
2916
|
+
});
|
|
2917
|
+
msg = { message_id: pending.messageId };
|
|
2918
|
+
} else {
|
|
2919
|
+
msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
2920
|
+
}
|
|
2921
|
+
} catch {
|
|
2922
|
+
msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
2923
|
+
}
|
|
2924
|
+
cleanupPending(userId);
|
|
2925
|
+
pendingNewSessions.set(userId, {
|
|
2926
|
+
agentName,
|
|
2927
|
+
step: "workspace",
|
|
2928
|
+
messageId: msg.message_id,
|
|
2929
|
+
threadId: ctx.message?.message_thread_id ?? ctx.callbackQuery?.message?.message_thread_id,
|
|
2930
|
+
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
|
|
2934
|
+
const keyboard = new InlineKeyboard2().text("\u2705 Create", "m:new:confirm").text("\u274C Cancel", "m:new:cancel");
|
|
2935
|
+
const text = `\u2705 <b>Ready to create session?</b>
|
|
2936
|
+
|
|
2937
|
+
<b>Agent:</b> ${escapeHtml(agentName)}
|
|
2938
|
+
<b>Project:</b> <code>${escapeHtml(workspace)}</code>`;
|
|
2939
|
+
let msg;
|
|
2940
|
+
try {
|
|
2941
|
+
const pending = pendingNewSessions.get(userId);
|
|
2942
|
+
if (pending?.messageId) {
|
|
2943
|
+
await ctx.api.editMessageText(chatId, pending.messageId, text, {
|
|
2944
|
+
parse_mode: "HTML",
|
|
2945
|
+
reply_markup: keyboard
|
|
2946
|
+
});
|
|
2947
|
+
msg = { message_id: pending.messageId };
|
|
2948
|
+
} else {
|
|
2949
|
+
msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
2950
|
+
}
|
|
2951
|
+
} catch {
|
|
2952
|
+
msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
2953
|
+
}
|
|
2954
|
+
cleanupPending(userId);
|
|
2955
|
+
pendingNewSessions.set(userId, {
|
|
2956
|
+
agentName,
|
|
2957
|
+
workspace,
|
|
2958
|
+
step: "confirm",
|
|
2959
|
+
messageId: msg.message_id,
|
|
2960
|
+
threadId: ctx.message?.message_thread_id ?? ctx.callbackQuery?.message?.message_thread_id,
|
|
2961
|
+
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
|
|
2965
|
+
log9.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
|
|
2723
2966
|
let threadId;
|
|
2724
2967
|
try {
|
|
2725
2968
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2728,15 +2971,9 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2728
2971
|
message_thread_id: threadId,
|
|
2729
2972
|
parse_mode: "HTML"
|
|
2730
2973
|
});
|
|
2731
|
-
const session = await core.handleNewSession(
|
|
2732
|
-
"telegram",
|
|
2733
|
-
agentName,
|
|
2734
|
-
workspace
|
|
2735
|
-
);
|
|
2974
|
+
const session = await core.handleNewSession("telegram", agentName, workspace);
|
|
2736
2975
|
session.threadId = String(threadId);
|
|
2737
|
-
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
2738
|
-
topicId: threadId
|
|
2739
|
-
});
|
|
2976
|
+
await core.sessionManager.updateSessionPlatform(session.id, { topicId: threadId });
|
|
2740
2977
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2741
2978
|
try {
|
|
2742
2979
|
await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
|
|
@@ -2744,18 +2981,21 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2744
2981
|
}
|
|
2745
2982
|
await ctx.api.sendMessage(
|
|
2746
2983
|
chatId,
|
|
2747
|
-
`\u2705 Session started
|
|
2984
|
+
`\u2705 <b>Session started</b>
|
|
2748
2985
|
<b>Agent:</b> ${escapeHtml(session.agentName)}
|
|
2749
|
-
<b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code
|
|
2986
|
+
<b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
|
|
2987
|
+
|
|
2988
|
+
This is your coding session \u2014 chat here to work with the agent.`,
|
|
2750
2989
|
{
|
|
2751
2990
|
message_thread_id: threadId,
|
|
2752
2991
|
parse_mode: "HTML",
|
|
2753
2992
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2754
2993
|
}
|
|
2755
2994
|
);
|
|
2756
|
-
session.warmup().catch((err) =>
|
|
2995
|
+
session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
|
|
2996
|
+
return threadId ?? null;
|
|
2757
2997
|
} catch (err) {
|
|
2758
|
-
|
|
2998
|
+
log9.error({ err }, "Session creation failed");
|
|
2759
2999
|
if (threadId) {
|
|
2760
3000
|
try {
|
|
2761
3001
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -2764,6 +3004,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2764
3004
|
}
|
|
2765
3005
|
const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
|
|
2766
3006
|
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
3007
|
+
return null;
|
|
2767
3008
|
}
|
|
2768
3009
|
}
|
|
2769
3010
|
async function handleNewChat(ctx, core, chatId) {
|
|
@@ -2831,19 +3072,182 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
2831
3072
|
parse_mode: "HTML",
|
|
2832
3073
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2833
3074
|
}
|
|
2834
|
-
);
|
|
2835
|
-
session.warmup().catch((err) =>
|
|
2836
|
-
} catch (err) {
|
|
2837
|
-
if (newThreadId) {
|
|
3075
|
+
);
|
|
3076
|
+
session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
|
|
3077
|
+
} catch (err) {
|
|
3078
|
+
if (newThreadId) {
|
|
3079
|
+
try {
|
|
3080
|
+
await ctx.api.deleteForumTopic(chatId, newThreadId);
|
|
3081
|
+
} catch {
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3085
|
+
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
async function executeNewSession(bot, core, chatId, agentName, workspace) {
|
|
3089
|
+
const threadId = await createSessionTopic(bot, chatId, "\u{1F504} New Session");
|
|
3090
|
+
const setupMsg = await bot.api.sendMessage(chatId, "\u23F3 Setting up session, please wait...", {
|
|
3091
|
+
message_thread_id: threadId,
|
|
3092
|
+
parse_mode: "HTML"
|
|
3093
|
+
});
|
|
3094
|
+
const firstMsgId = setupMsg.message_id;
|
|
3095
|
+
try {
|
|
3096
|
+
const session = await core.handleNewSession(
|
|
3097
|
+
"telegram",
|
|
3098
|
+
agentName,
|
|
3099
|
+
workspace
|
|
3100
|
+
);
|
|
3101
|
+
session.threadId = String(threadId);
|
|
3102
|
+
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
3103
|
+
topicId: threadId
|
|
3104
|
+
});
|
|
3105
|
+
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
3106
|
+
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
3107
|
+
session.warmup().catch((err) => log9.error({ err }, "Warm-up error"));
|
|
3108
|
+
return { session, threadId, firstMsgId };
|
|
3109
|
+
} catch (err) {
|
|
3110
|
+
try {
|
|
3111
|
+
await bot.api.deleteForumTopic(chatId, threadId);
|
|
3112
|
+
} catch {
|
|
3113
|
+
}
|
|
3114
|
+
throw err;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
async function handlePendingWorkspaceInput(ctx, core, chatId, assistantTopicId) {
|
|
3118
|
+
const userId = ctx.from?.id;
|
|
3119
|
+
if (!userId) return false;
|
|
3120
|
+
const pending = pendingNewSessions.get(userId);
|
|
3121
|
+
if (!pending || !ctx.message?.text) return false;
|
|
3122
|
+
if (pending.step !== "workspace_input" && pending.step !== "workspace") return false;
|
|
3123
|
+
const threadId = ctx.message.message_thread_id;
|
|
3124
|
+
if (threadId && threadId !== assistantTopicId) return false;
|
|
3125
|
+
let workspace = ctx.message.text.trim();
|
|
3126
|
+
if (!workspace || !pending.agentName) {
|
|
3127
|
+
await ctx.reply("\u26A0\uFE0F Please enter a valid directory path.", { parse_mode: "HTML" });
|
|
3128
|
+
return true;
|
|
3129
|
+
}
|
|
3130
|
+
if (!workspace.startsWith("/") && !workspace.startsWith("~")) {
|
|
3131
|
+
const baseDir = core.configManager.get().workspace.baseDir;
|
|
3132
|
+
workspace = `${baseDir.replace(/\/$/, "")}/${workspace}`;
|
|
3133
|
+
}
|
|
3134
|
+
await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
|
|
3135
|
+
return true;
|
|
3136
|
+
}
|
|
3137
|
+
async function startInteractiveNewSession(ctx, core, chatId, agentName) {
|
|
3138
|
+
const userId = ctx.from?.id;
|
|
3139
|
+
if (!userId) return;
|
|
3140
|
+
const agents = core.agentManager.getAvailableAgents();
|
|
3141
|
+
const config = core.configManager.get();
|
|
3142
|
+
if (agentName || agents.length === 1) {
|
|
3143
|
+
const selectedAgent = agentName || config.defaultAgent;
|
|
3144
|
+
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
const keyboard = new InlineKeyboard2();
|
|
3148
|
+
for (const agent of agents) {
|
|
3149
|
+
const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
3150
|
+
keyboard.text(label, `m:new:agent:${agent.name}`).row();
|
|
3151
|
+
}
|
|
3152
|
+
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
3153
|
+
const msg = await ctx.reply(
|
|
3154
|
+
`\u{1F916} <b>Choose an agent:</b>`,
|
|
3155
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3156
|
+
);
|
|
3157
|
+
cleanupPending(userId);
|
|
3158
|
+
pendingNewSessions.set(userId, {
|
|
3159
|
+
step: "agent",
|
|
3160
|
+
messageId: msg.message_id,
|
|
3161
|
+
threadId: ctx.callbackQuery?.message?.message_thread_id,
|
|
3162
|
+
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
function setupNewSessionCallbacks(bot, core, chatId) {
|
|
3166
|
+
bot.callbackQuery(/^m:new:/, async (ctx) => {
|
|
3167
|
+
const data = ctx.callbackQuery.data;
|
|
3168
|
+
try {
|
|
3169
|
+
await ctx.answerCallbackQuery();
|
|
3170
|
+
} catch {
|
|
3171
|
+
}
|
|
3172
|
+
if (data.startsWith("m:new:agent:")) {
|
|
3173
|
+
const agentName = data.replace("m:new:agent:", "");
|
|
3174
|
+
const userId = ctx.from?.id;
|
|
3175
|
+
if (userId) await startWorkspaceStep(ctx, core, chatId, userId, agentName);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
if (data === "m:new:ws:default") {
|
|
3179
|
+
const userId = ctx.from?.id;
|
|
3180
|
+
if (!userId) return;
|
|
3181
|
+
const pending = pendingNewSessions.get(userId);
|
|
3182
|
+
if (!pending?.agentName) return;
|
|
3183
|
+
const workspace = core.configManager.get().workspace.baseDir;
|
|
3184
|
+
await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
if (data === "m:new:ws:custom") {
|
|
3188
|
+
const userId = ctx.from?.id;
|
|
3189
|
+
if (!userId) return;
|
|
3190
|
+
const pending = pendingNewSessions.get(userId);
|
|
3191
|
+
if (!pending?.agentName) return;
|
|
3192
|
+
try {
|
|
3193
|
+
await ctx.api.editMessageText(
|
|
3194
|
+
chatId,
|
|
3195
|
+
pending.messageId,
|
|
3196
|
+
`\u270F\uFE0F <b>Enter your project path:</b>
|
|
3197
|
+
|
|
3198
|
+
Full path like <code>~/code/my-project</code>
|
|
3199
|
+
Or just the folder name like <code>my-project</code> (will use ${core.configManager.get().workspace.baseDir}/)`,
|
|
3200
|
+
{ parse_mode: "HTML" }
|
|
3201
|
+
);
|
|
3202
|
+
} catch {
|
|
3203
|
+
await ctx.reply(
|
|
3204
|
+
`\u270F\uFE0F <b>Enter your project path:</b>`,
|
|
3205
|
+
{ parse_mode: "HTML" }
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
clearTimeout(pending.timer);
|
|
3209
|
+
pending.step = "workspace_input";
|
|
3210
|
+
pending.timer = setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS);
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
if (data === "m:new:confirm") {
|
|
3214
|
+
const userId = ctx.from?.id;
|
|
3215
|
+
if (!userId) return;
|
|
3216
|
+
const pending = pendingNewSessions.get(userId);
|
|
3217
|
+
if (!pending?.agentName || !pending?.workspace) return;
|
|
3218
|
+
cleanupPending(userId);
|
|
3219
|
+
const confirmMsgId = pending.messageId;
|
|
2838
3220
|
try {
|
|
2839
|
-
await ctx.api.
|
|
3221
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
|
|
3222
|
+
} catch {
|
|
3223
|
+
}
|
|
3224
|
+
const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
|
|
3225
|
+
try {
|
|
3226
|
+
if (resultThreadId) {
|
|
3227
|
+
const link = buildDeepLink(chatId, resultThreadId);
|
|
3228
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u2705 Session created \u2192 <a href="${link}">Open topic</a>`, { parse_mode: "HTML" });
|
|
3229
|
+
} else {
|
|
3230
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u274C Session creation failed.`, { parse_mode: "HTML" });
|
|
3231
|
+
}
|
|
2840
3232
|
} catch {
|
|
2841
3233
|
}
|
|
3234
|
+
return;
|
|
2842
3235
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3236
|
+
if (data === "m:new:cancel") {
|
|
3237
|
+
const userId = ctx.from?.id;
|
|
3238
|
+
if (userId) cleanupPending(userId);
|
|
3239
|
+
try {
|
|
3240
|
+
await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
|
|
3241
|
+
} catch {
|
|
3242
|
+
}
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
2846
3246
|
}
|
|
3247
|
+
|
|
3248
|
+
// src/adapters/telegram/commands/session.ts
|
|
3249
|
+
import { InlineKeyboard as InlineKeyboard3 } from "grammy";
|
|
3250
|
+
var log10 = createChildLogger({ module: "telegram-cmd-session" });
|
|
2847
3251
|
async function handleCancel(ctx, core, assistant) {
|
|
2848
3252
|
const threadId = ctx.message?.message_thread_id;
|
|
2849
3253
|
if (!threadId) return;
|
|
@@ -2861,14 +3265,14 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
2861
3265
|
String(threadId)
|
|
2862
3266
|
);
|
|
2863
3267
|
if (session) {
|
|
2864
|
-
|
|
3268
|
+
log10.info({ sessionId: session.id }, "Cancel session command");
|
|
2865
3269
|
await session.cancel();
|
|
2866
3270
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2867
3271
|
return;
|
|
2868
3272
|
}
|
|
2869
3273
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2870
3274
|
if (record && record.status !== "cancelled" && record.status !== "error") {
|
|
2871
|
-
|
|
3275
|
+
log10.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
|
|
2872
3276
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
2873
3277
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2874
3278
|
}
|
|
@@ -2954,8 +3358,7 @@ async function handleTopics(ctx, core) {
|
|
|
2954
3358
|
<i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
|
|
2955
3359
|
const finishedCount = records.filter((r) => r.status === "finished").length;
|
|
2956
3360
|
const errorCount = records.filter((r) => r.status === "error" || r.status === "cancelled").length;
|
|
2957
|
-
const
|
|
2958
|
-
const keyboard = new InlineKeyboard();
|
|
3361
|
+
const keyboard = new InlineKeyboard3();
|
|
2959
3362
|
if (finishedCount > 0) {
|
|
2960
3363
|
keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
|
|
2961
3364
|
}
|
|
@@ -2974,7 +3377,7 @@ ${lines.join("\n")}${truncated}`,
|
|
|
2974
3377
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
2975
3378
|
);
|
|
2976
3379
|
} catch (err) {
|
|
2977
|
-
|
|
3380
|
+
log10.error({ err }, "handleTopics error");
|
|
2978
3381
|
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
2979
3382
|
});
|
|
2980
3383
|
}
|
|
@@ -2998,13 +3401,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
|
|
|
2998
3401
|
try {
|
|
2999
3402
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3000
3403
|
} catch (err) {
|
|
3001
|
-
|
|
3404
|
+
log10.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3002
3405
|
}
|
|
3003
3406
|
}
|
|
3004
3407
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
3005
3408
|
deleted++;
|
|
3006
3409
|
} catch (err) {
|
|
3007
|
-
|
|
3410
|
+
log10.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3008
3411
|
failed++;
|
|
3009
3412
|
}
|
|
3010
3413
|
}
|
|
@@ -3041,7 +3444,7 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
|
3041
3444
|
const activeWarning = activeCount > 0 ? `
|
|
3042
3445
|
|
|
3043
3446
|
\u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
|
|
3044
|
-
const keyboard = new
|
|
3447
|
+
const keyboard = new InlineKeyboard3().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
|
|
3045
3448
|
await ctx.reply(
|
|
3046
3449
|
`<b>Delete ${cleanable.length} topics?</b>
|
|
3047
3450
|
|
|
@@ -3077,7 +3480,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3077
3480
|
try {
|
|
3078
3481
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
3079
3482
|
} catch (err) {
|
|
3080
|
-
|
|
3483
|
+
log10.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3081
3484
|
}
|
|
3082
3485
|
}
|
|
3083
3486
|
const topicId = record.platform?.topicId;
|
|
@@ -3085,13 +3488,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3085
3488
|
try {
|
|
3086
3489
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3087
3490
|
} catch (err) {
|
|
3088
|
-
|
|
3491
|
+
log10.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3089
3492
|
}
|
|
3090
3493
|
}
|
|
3091
3494
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
3092
3495
|
deleted++;
|
|
3093
3496
|
} catch (err) {
|
|
3094
|
-
|
|
3497
|
+
log10.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3095
3498
|
failed++;
|
|
3096
3499
|
}
|
|
3097
3500
|
}
|
|
@@ -3100,206 +3503,115 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3100
3503
|
{ parse_mode: "HTML" }
|
|
3101
3504
|
);
|
|
3102
3505
|
}
|
|
3103
|
-
async function
|
|
3104
|
-
const
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
);
|
|
3110
|
-
const text = lines.length > 0 ? `<b>Available Agents:</b>
|
|
3111
|
-
|
|
3112
|
-
${lines.join("\n")}` : `<b>Available Agents:</b>
|
|
3113
|
-
|
|
3114
|
-
No agents configured.`;
|
|
3115
|
-
await ctx.reply(text, { parse_mode: "HTML" });
|
|
3116
|
-
}
|
|
3117
|
-
async function handleHelp(ctx) {
|
|
3118
|
-
await ctx.reply(
|
|
3119
|
-
`<b>OpenACP Commands:</b>
|
|
3120
|
-
|
|
3121
|
-
/new [agent] [workspace] \u2014 Create new session
|
|
3122
|
-
/newchat \u2014 New chat, same agent & workspace
|
|
3123
|
-
/cancel \u2014 Cancel current session
|
|
3124
|
-
/status \u2014 Show session/system status
|
|
3125
|
-
/agents \u2014 List available agents
|
|
3126
|
-
/menu \u2014 Show interactive menu
|
|
3127
|
-
/restart \u2014 Restart OpenACP
|
|
3128
|
-
/update \u2014 Update to latest version and restart
|
|
3129
|
-
/help \u2014 Show this help
|
|
3130
|
-
|
|
3131
|
-
Or just chat in the \u{1F916} Assistant topic for help!`,
|
|
3132
|
-
{ parse_mode: "HTML" }
|
|
3133
|
-
);
|
|
3134
|
-
}
|
|
3135
|
-
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
3136
|
-
return new InlineKeyboard().text(
|
|
3137
|
-
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
3138
|
-
`d:${sessionId}`
|
|
3139
|
-
);
|
|
3506
|
+
async function executeCancelSession(core, excludeSessionId) {
|
|
3507
|
+
const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
3508
|
+
const session = sessions[0];
|
|
3509
|
+
if (!session) return null;
|
|
3510
|
+
await session.cancel();
|
|
3511
|
+
return session;
|
|
3140
3512
|
}
|
|
3141
|
-
function
|
|
3142
|
-
bot.callbackQuery(/^
|
|
3143
|
-
const
|
|
3144
|
-
const session = core.sessionManager.getSession(sessionId);
|
|
3145
|
-
if (session) {
|
|
3146
|
-
session.dangerousMode = !session.dangerousMode;
|
|
3147
|
-
log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
3148
|
-
core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
|
|
3149
|
-
});
|
|
3150
|
-
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
3151
|
-
try {
|
|
3152
|
-
await ctx.answerCallbackQuery({ text: toastText2 });
|
|
3153
|
-
} catch {
|
|
3154
|
-
}
|
|
3155
|
-
try {
|
|
3156
|
-
await ctx.editMessageReplyMarkup({
|
|
3157
|
-
reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
|
|
3158
|
-
});
|
|
3159
|
-
} catch {
|
|
3160
|
-
}
|
|
3161
|
-
return;
|
|
3162
|
-
}
|
|
3163
|
-
const record = core.sessionManager.getSessionRecord(sessionId);
|
|
3164
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3165
|
-
try {
|
|
3166
|
-
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
|
|
3167
|
-
} catch {
|
|
3168
|
-
}
|
|
3169
|
-
return;
|
|
3170
|
-
}
|
|
3171
|
-
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
3172
|
-
core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
|
|
3173
|
-
});
|
|
3174
|
-
log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
3175
|
-
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
3513
|
+
function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
|
|
3514
|
+
bot.callbackQuery(/^m:cleanup/, async (ctx) => {
|
|
3515
|
+
const data = ctx.callbackQuery.data;
|
|
3176
3516
|
try {
|
|
3177
|
-
await ctx.answerCallbackQuery(
|
|
3517
|
+
await ctx.answerCallbackQuery();
|
|
3178
3518
|
} catch {
|
|
3179
3519
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3520
|
+
switch (data) {
|
|
3521
|
+
case "m:cleanup:finished":
|
|
3522
|
+
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
3523
|
+
break;
|
|
3524
|
+
case "m:cleanup:errors":
|
|
3525
|
+
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
3526
|
+
break;
|
|
3527
|
+
case "m:cleanup:all":
|
|
3528
|
+
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
3529
|
+
break;
|
|
3530
|
+
case "m:cleanup:everything":
|
|
3531
|
+
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
3532
|
+
break;
|
|
3533
|
+
case "m:cleanup:everything:confirm":
|
|
3534
|
+
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
3535
|
+
break;
|
|
3185
3536
|
}
|
|
3186
3537
|
});
|
|
3187
3538
|
}
|
|
3188
|
-
async function handleEnableDangerous(ctx, core) {
|
|
3189
|
-
const threadId = ctx.message?.message_thread_id;
|
|
3190
|
-
if (!threadId) {
|
|
3191
|
-
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
3192
|
-
return;
|
|
3193
|
-
}
|
|
3194
|
-
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
3195
|
-
if (session) {
|
|
3196
|
-
if (session.dangerousMode) {
|
|
3197
|
-
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
3198
|
-
return;
|
|
3199
|
-
}
|
|
3200
|
-
session.dangerousMode = true;
|
|
3201
|
-
core.sessionManager.updateSessionDangerousMode(session.id, true).catch(() => {
|
|
3202
|
-
});
|
|
3203
|
-
} else {
|
|
3204
|
-
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3205
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3206
|
-
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
3207
|
-
return;
|
|
3208
|
-
}
|
|
3209
|
-
if (record.dangerousMode) {
|
|
3210
|
-
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
core.sessionManager.updateSessionDangerousMode(record.sessionId, true).catch(() => {
|
|
3214
|
-
});
|
|
3215
|
-
}
|
|
3216
|
-
await ctx.reply(
|
|
3217
|
-
`\u26A0\uFE0F <b>Dangerous mode enabled</b>
|
|
3218
3539
|
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
);
|
|
3224
|
-
}
|
|
3225
|
-
async function handleUpdate(ctx, core) {
|
|
3226
|
-
if (!core.requestRestart) {
|
|
3227
|
-
await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
|
|
3231
|
-
const current = getCurrentVersion();
|
|
3232
|
-
const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
|
|
3233
|
-
const latest = await getLatestVersion();
|
|
3234
|
-
if (!latest) {
|
|
3235
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
if (compareVersions(current, latest) >= 0) {
|
|
3239
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
await ctx.api.editMessageText(
|
|
3243
|
-
ctx.chat.id,
|
|
3244
|
-
statusMsg.message_id,
|
|
3245
|
-
`\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
|
|
3246
|
-
{ parse_mode: "HTML" }
|
|
3247
|
-
);
|
|
3248
|
-
const ok = await runUpdate();
|
|
3249
|
-
if (!ok) {
|
|
3250
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
|
|
3251
|
-
return;
|
|
3252
|
-
}
|
|
3253
|
-
await ctx.api.editMessageText(
|
|
3254
|
-
ctx.chat.id,
|
|
3255
|
-
statusMsg.message_id,
|
|
3256
|
-
`\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
|
|
3257
|
-
{ parse_mode: "HTML" }
|
|
3258
|
-
);
|
|
3259
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
3260
|
-
await core.requestRestart();
|
|
3540
|
+
// src/adapters/telegram/commands/menu.ts
|
|
3541
|
+
import { InlineKeyboard as InlineKeyboard4 } from "grammy";
|
|
3542
|
+
function buildMenuKeyboard() {
|
|
3543
|
+
return new InlineKeyboard4().text("\u{1F195} New Session", "m:new").text("\u{1F4CB} Sessions", "m:topics").row().text("\u{1F4CA} Status", "m:status").text("\u{1F916} Agents", "m:agents").row().text("\u{1F517} Integrate", "m:integrate").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
|
|
3261
3544
|
}
|
|
3262
|
-
async function
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
3269
|
-
await core.requestRestart();
|
|
3545
|
+
async function handleMenu(ctx) {
|
|
3546
|
+
await ctx.reply(`<b>OpenACP Menu</b>
|
|
3547
|
+
Choose an action:`, {
|
|
3548
|
+
parse_mode: "HTML",
|
|
3549
|
+
reply_markup: buildMenuKeyboard()
|
|
3550
|
+
});
|
|
3270
3551
|
}
|
|
3271
|
-
async function
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
core.sessionManager.updateSessionDangerousMode(session.id, false).catch(() => {
|
|
3285
|
-
});
|
|
3286
|
-
} else {
|
|
3287
|
-
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3288
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3289
|
-
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
3290
|
-
return;
|
|
3291
|
-
}
|
|
3292
|
-
if (!record.dangerousMode) {
|
|
3293
|
-
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
3294
|
-
return;
|
|
3295
|
-
}
|
|
3296
|
-
core.sessionManager.updateSessionDangerousMode(record.sessionId, false).catch(() => {
|
|
3297
|
-
});
|
|
3298
|
-
}
|
|
3299
|
-
await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
|
|
3552
|
+
async function handleAgents(ctx, core) {
|
|
3553
|
+
const agents = core.agentManager.getAvailableAgents();
|
|
3554
|
+
const defaultAgent = core.configManager.get().defaultAgent;
|
|
3555
|
+
const lines = agents.map(
|
|
3556
|
+
(a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
|
|
3557
|
+
<code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
|
|
3558
|
+
);
|
|
3559
|
+
const text = lines.length > 0 ? `<b>Available Agents:</b>
|
|
3560
|
+
|
|
3561
|
+
${lines.join("\n")}` : `<b>Available Agents:</b>
|
|
3562
|
+
|
|
3563
|
+
No agents configured.`;
|
|
3564
|
+
await ctx.reply(text, { parse_mode: "HTML" });
|
|
3300
3565
|
}
|
|
3301
|
-
function
|
|
3302
|
-
|
|
3566
|
+
async function handleHelp(ctx) {
|
|
3567
|
+
await ctx.reply(
|
|
3568
|
+
`\u{1F4D6} <b>OpenACP Help</b>
|
|
3569
|
+
|
|
3570
|
+
\u{1F680} <b>Getting Started</b>
|
|
3571
|
+
Tap \u{1F195} New Session to start coding with AI.
|
|
3572
|
+
Each session gets its own topic \u2014 chat there to work with the agent.
|
|
3573
|
+
|
|
3574
|
+
\u{1F4A1} <b>Common Tasks</b>
|
|
3575
|
+
/new [agent] [workspace] \u2014 Create new session
|
|
3576
|
+
/cancel \u2014 Cancel session (in session topic)
|
|
3577
|
+
/status \u2014 Show session or system status
|
|
3578
|
+
/sessions \u2014 List all sessions
|
|
3579
|
+
/agents \u2014 List available agents
|
|
3580
|
+
|
|
3581
|
+
\u2699\uFE0F <b>System</b>
|
|
3582
|
+
/restart \u2014 Restart OpenACP
|
|
3583
|
+
/update \u2014 Update to latest version
|
|
3584
|
+
/integrate \u2014 Manage agent integrations
|
|
3585
|
+
/menu \u2014 Show action menu
|
|
3586
|
+
|
|
3587
|
+
\u{1F512} <b>Session Options</b>
|
|
3588
|
+
/enable_dangerous \u2014 Auto-approve permissions
|
|
3589
|
+
/disable_dangerous \u2014 Restore permission prompts
|
|
3590
|
+
/handoff \u2014 Continue session in terminal
|
|
3591
|
+
/clear \u2014 Clear assistant history
|
|
3592
|
+
|
|
3593
|
+
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
3594
|
+
{ parse_mode: "HTML" }
|
|
3595
|
+
);
|
|
3596
|
+
}
|
|
3597
|
+
async function handleClear(ctx, assistant) {
|
|
3598
|
+
if (!assistant) {
|
|
3599
|
+
await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
const threadId = ctx.message?.message_thread_id;
|
|
3603
|
+
if (threadId !== assistant.topicId) {
|
|
3604
|
+
await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
|
|
3608
|
+
try {
|
|
3609
|
+
await assistant.respawn();
|
|
3610
|
+
await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
|
|
3611
|
+
} catch (err) {
|
|
3612
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3613
|
+
await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
|
|
3614
|
+
}
|
|
3303
3615
|
}
|
|
3304
3616
|
var TELEGRAM_MSG_LIMIT = 4096;
|
|
3305
3617
|
function buildSkillMessages(commands) {
|
|
@@ -3320,46 +3632,13 @@ function buildSkillMessages(commands) {
|
|
|
3320
3632
|
if (current) messages.push(current);
|
|
3321
3633
|
return messages;
|
|
3322
3634
|
}
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
message_thread_id: threadId,
|
|
3327
|
-
parse_mode: "HTML"
|
|
3328
|
-
});
|
|
3329
|
-
const firstMsgId = setupMsg.message_id;
|
|
3330
|
-
try {
|
|
3331
|
-
const session = await core.handleNewSession(
|
|
3332
|
-
"telegram",
|
|
3333
|
-
agentName,
|
|
3334
|
-
workspace
|
|
3335
|
-
);
|
|
3336
|
-
session.threadId = String(threadId);
|
|
3337
|
-
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
3338
|
-
topicId: threadId
|
|
3339
|
-
});
|
|
3340
|
-
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
3341
|
-
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
3342
|
-
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
3343
|
-
return { session, threadId, firstMsgId };
|
|
3344
|
-
} catch (err) {
|
|
3345
|
-
try {
|
|
3346
|
-
await bot.api.deleteForumTopic(chatId, threadId);
|
|
3347
|
-
} catch {
|
|
3348
|
-
}
|
|
3349
|
-
throw err;
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
async function executeCancelSession(core, excludeSessionId) {
|
|
3353
|
-
const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
3354
|
-
const session = sessions[0];
|
|
3355
|
-
if (!session) return null;
|
|
3356
|
-
await session.cancel();
|
|
3357
|
-
return session;
|
|
3358
|
-
}
|
|
3635
|
+
|
|
3636
|
+
// src/adapters/telegram/commands/integrate.ts
|
|
3637
|
+
import { InlineKeyboard as InlineKeyboard5 } from "grammy";
|
|
3359
3638
|
async function handleIntegrate(ctx, _core) {
|
|
3360
3639
|
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3361
3640
|
const agents = listIntegrations();
|
|
3362
|
-
const keyboard = new
|
|
3641
|
+
const keyboard = new InlineKeyboard5();
|
|
3363
3642
|
for (const agent of agents) {
|
|
3364
3643
|
keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3365
3644
|
}
|
|
@@ -3371,7 +3650,7 @@ Select an agent to manage its integrations.`,
|
|
|
3371
3650
|
);
|
|
3372
3651
|
}
|
|
3373
3652
|
function buildAgentItemsKeyboard(agentName, items) {
|
|
3374
|
-
const keyboard = new
|
|
3653
|
+
const keyboard = new InlineKeyboard5();
|
|
3375
3654
|
for (const item of items) {
|
|
3376
3655
|
const installed = item.isInstalled();
|
|
3377
3656
|
keyboard.text(
|
|
@@ -3392,7 +3671,7 @@ function setupIntegrateCallbacks(bot, core) {
|
|
|
3392
3671
|
if (data === "i:back") {
|
|
3393
3672
|
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3394
3673
|
const agents = listIntegrations();
|
|
3395
|
-
const keyboard2 = new
|
|
3674
|
+
const keyboard2 = new InlineKeyboard5();
|
|
3396
3675
|
for (const agent of agents) {
|
|
3397
3676
|
keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3398
3677
|
}
|
|
@@ -3471,6 +3750,61 @@ ${resultText}`,
|
|
|
3471
3750
|
}
|
|
3472
3751
|
});
|
|
3473
3752
|
}
|
|
3753
|
+
|
|
3754
|
+
// src/adapters/telegram/commands/index.ts
|
|
3755
|
+
function setupCommands(bot, core, chatId, assistant) {
|
|
3756
|
+
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
3757
|
+
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
3758
|
+
bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
|
|
3759
|
+
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
3760
|
+
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
3761
|
+
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
3762
|
+
bot.command("help", (ctx) => handleHelp(ctx));
|
|
3763
|
+
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
3764
|
+
bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
|
|
3765
|
+
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
3766
|
+
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
3767
|
+
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
3768
|
+
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
3769
|
+
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
3770
|
+
}
|
|
3771
|
+
function setupAllCallbacks(bot, core, chatId, systemTopicIds) {
|
|
3772
|
+
setupNewSessionCallbacks(bot, core, chatId);
|
|
3773
|
+
setupSessionCallbacks(bot, core, chatId, systemTopicIds);
|
|
3774
|
+
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
3775
|
+
const data = ctx.callbackQuery.data;
|
|
3776
|
+
try {
|
|
3777
|
+
await ctx.answerCallbackQuery();
|
|
3778
|
+
} catch {
|
|
3779
|
+
}
|
|
3780
|
+
switch (data) {
|
|
3781
|
+
case "m:new":
|
|
3782
|
+
await handleNew(ctx, core, chatId);
|
|
3783
|
+
break;
|
|
3784
|
+
case "m:status":
|
|
3785
|
+
await handleStatus(ctx, core);
|
|
3786
|
+
break;
|
|
3787
|
+
case "m:agents":
|
|
3788
|
+
await handleAgents(ctx, core);
|
|
3789
|
+
break;
|
|
3790
|
+
case "m:help":
|
|
3791
|
+
await handleHelp(ctx);
|
|
3792
|
+
break;
|
|
3793
|
+
case "m:restart":
|
|
3794
|
+
await handleRestart(ctx, core);
|
|
3795
|
+
break;
|
|
3796
|
+
case "m:update":
|
|
3797
|
+
await handleUpdate(ctx, core);
|
|
3798
|
+
break;
|
|
3799
|
+
case "m:integrate":
|
|
3800
|
+
await handleIntegrate(ctx, core);
|
|
3801
|
+
break;
|
|
3802
|
+
case "m:topics":
|
|
3803
|
+
await handleTopics(ctx, core);
|
|
3804
|
+
break;
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3474
3808
|
var STATIC_COMMANDS = [
|
|
3475
3809
|
{ command: "new", description: "Create new session" },
|
|
3476
3810
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
@@ -3484,14 +3818,15 @@ var STATIC_COMMANDS = [
|
|
|
3484
3818
|
{ command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
|
|
3485
3819
|
{ command: "integrate", description: "Manage agent integrations" },
|
|
3486
3820
|
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
3821
|
+
{ command: "clear", description: "Clear assistant history" },
|
|
3487
3822
|
{ command: "restart", description: "Restart OpenACP" },
|
|
3488
3823
|
{ command: "update", description: "Update to latest version and restart" }
|
|
3489
3824
|
];
|
|
3490
3825
|
|
|
3491
3826
|
// src/adapters/telegram/permissions.ts
|
|
3492
|
-
import { InlineKeyboard as
|
|
3827
|
+
import { InlineKeyboard as InlineKeyboard6 } from "grammy";
|
|
3493
3828
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3494
|
-
var
|
|
3829
|
+
var log11 = createChildLogger({ module: "telegram-permissions" });
|
|
3495
3830
|
var PermissionHandler = class {
|
|
3496
3831
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
3497
3832
|
this.bot = bot;
|
|
@@ -3508,7 +3843,7 @@ var PermissionHandler = class {
|
|
|
3508
3843
|
requestId: request.id,
|
|
3509
3844
|
options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
|
|
3510
3845
|
});
|
|
3511
|
-
const keyboard = new
|
|
3846
|
+
const keyboard = new InlineKeyboard6();
|
|
3512
3847
|
for (const option of request.options) {
|
|
3513
3848
|
const emoji = option.isAllow ? "\u2705" : "\u274C";
|
|
3514
3849
|
keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
|
|
@@ -3551,7 +3886,7 @@ ${escapeHtml(request.description)}`,
|
|
|
3551
3886
|
}
|
|
3552
3887
|
const session = this.getSession(pending.sessionId);
|
|
3553
3888
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
3554
|
-
|
|
3889
|
+
log11.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
3555
3890
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
3556
3891
|
session.permissionGate.resolve(optionId);
|
|
3557
3892
|
}
|
|
@@ -3568,11 +3903,375 @@ ${escapeHtml(request.description)}`,
|
|
|
3568
3903
|
}
|
|
3569
3904
|
};
|
|
3570
3905
|
|
|
3906
|
+
// src/product-guide.ts
|
|
3907
|
+
var PRODUCT_GUIDE = `
|
|
3908
|
+
# OpenACP \u2014 Product Guide
|
|
3909
|
+
|
|
3910
|
+
OpenACP lets you chat with AI coding agents (like Claude Code) through Telegram.
|
|
3911
|
+
You type messages in Telegram, the agent reads/writes/runs code in your project folder, and results stream back in real time.
|
|
3912
|
+
|
|
3913
|
+
---
|
|
3914
|
+
|
|
3915
|
+
## Quick Start
|
|
3916
|
+
|
|
3917
|
+
1. Start OpenACP: \`openacp\` (or \`openacp start\` for background daemon)
|
|
3918
|
+
2. Open your Telegram group \u2014 you'll see the Assistant topic
|
|
3919
|
+
3. Tap \u{1F195} New Session or type /new
|
|
3920
|
+
4. Pick an agent and a project folder
|
|
3921
|
+
5. Chat in the session topic \u2014 the agent works on your code
|
|
3922
|
+
|
|
3923
|
+
---
|
|
3924
|
+
|
|
3925
|
+
## Core Concepts
|
|
3926
|
+
|
|
3927
|
+
### Sessions
|
|
3928
|
+
A session = one conversation with one AI agent working in one project folder.
|
|
3929
|
+
Each session gets its own Telegram topic. Chat there to give instructions to the agent.
|
|
3930
|
+
|
|
3931
|
+
### Agents
|
|
3932
|
+
An agent is an AI coding tool (e.g., Claude Code). You can configure multiple agents.
|
|
3933
|
+
The default agent is used when you don't specify one.
|
|
3934
|
+
|
|
3935
|
+
### Project Folder (Workspace)
|
|
3936
|
+
The directory where the agent reads, writes, and runs code.
|
|
3937
|
+
When creating a session, you choose which folder the agent works in.
|
|
3938
|
+
You can type a full path like \`~/code/my-project\` or just a name like \`my-project\` (it becomes \`<base-dir>/my-project\`).
|
|
3939
|
+
|
|
3940
|
+
### System Topics
|
|
3941
|
+
- **Assistant** \u2014 Always-on helper that can answer questions, create sessions, check status, troubleshoot
|
|
3942
|
+
- **Notifications** \u2014 System alerts (permission requests, session errors, completions)
|
|
3943
|
+
|
|
3944
|
+
---
|
|
3945
|
+
|
|
3946
|
+
## Creating Sessions
|
|
3947
|
+
|
|
3948
|
+
### From menu
|
|
3949
|
+
Tap \u{1F195} New Session \u2192 choose agent (if multiple) \u2192 choose project folder \u2192 confirm
|
|
3950
|
+
|
|
3951
|
+
### From command
|
|
3952
|
+
- \`/new\` \u2014 Interactive flow (asks agent + folder)
|
|
3953
|
+
- \`/new claude ~/code/my-project\` \u2014 Create directly with specific agent and folder
|
|
3954
|
+
|
|
3955
|
+
### From Assistant topic
|
|
3956
|
+
Just ask: "Create a session for my-project with claude" \u2014 the assistant handles it
|
|
3957
|
+
|
|
3958
|
+
### Quick new chat
|
|
3959
|
+
\`/newchat\` in a session topic \u2014 creates new session with same agent and folder as current one
|
|
3960
|
+
|
|
3961
|
+
---
|
|
3962
|
+
|
|
3963
|
+
## Working with Sessions
|
|
3964
|
+
|
|
3965
|
+
### Chat
|
|
3966
|
+
Type messages in the session topic. The agent responds with code changes, explanations, tool outputs.
|
|
3967
|
+
|
|
3968
|
+
### What you see while the agent works
|
|
3969
|
+
- **\u{1F4AD} Thinking indicator** \u2014 Shows when the agent is reasoning, with elapsed time
|
|
3970
|
+
- **Text responses** \u2014 Streamed in real time, updated every few seconds
|
|
3971
|
+
- **Tool calls** \u2014 When the agent runs commands or edits files, you see tool name, input, status, and output
|
|
3972
|
+
- **\u{1F4CB} Plan card** \u2014 Visual task progress with completed/in-progress/pending items and progress bar
|
|
3973
|
+
- **"View File" / "View Diff" buttons** \u2014 Opens in browser with Monaco editor (requires tunnel)
|
|
3974
|
+
|
|
3975
|
+
### Session lifecycle
|
|
3976
|
+
1. **Creating** \u2014 Topic created, agent spawning
|
|
3977
|
+
2. **Warming up** \u2014 Agent primes its cache (happens automatically, invisible to you)
|
|
3978
|
+
3. **Active** \u2014 Ready for your messages
|
|
3979
|
+
4. **Auto-naming** \u2014 After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.
|
|
3980
|
+
5. **Finished/Error** \u2014 Session completed or hit an error
|
|
3981
|
+
|
|
3982
|
+
### Agent skills
|
|
3983
|
+
Some agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.
|
|
3984
|
+
|
|
3985
|
+
### Permission requests
|
|
3986
|
+
When the agent wants to run a command, it asks for permission.
|
|
3987
|
+
You see buttons: \u2705 Allow, \u274C Reject (and sometimes "Always Allow").
|
|
3988
|
+
A notification also appears in the Notifications topic with a link to the request.
|
|
3989
|
+
|
|
3990
|
+
### Dangerous mode
|
|
3991
|
+
Auto-approves ALL permission requests \u2014 the agent runs any command without asking.
|
|
3992
|
+
- Enable: \`/enable_dangerous\` or tap the \u2620\uFE0F button in the session
|
|
3993
|
+
- Disable: \`/disable_dangerous\` or tap the \u{1F510} button
|
|
3994
|
+
- \u26A0\uFE0F Use with caution \u2014 the agent can execute anything
|
|
3995
|
+
|
|
3996
|
+
### Session timeout
|
|
3997
|
+
Idle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).
|
|
3998
|
+
Configure via \`security.sessionTimeoutMinutes\` in config.
|
|
3999
|
+
|
|
4000
|
+
---
|
|
4001
|
+
|
|
4002
|
+
## Session Transfer (Handoff)
|
|
4003
|
+
|
|
4004
|
+
### Telegram \u2192 Terminal
|
|
4005
|
+
1. Type \`/handoff\` in a session topic
|
|
4006
|
+
2. You get a command like \`claude --resume <SESSION_ID>\`
|
|
4007
|
+
3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
|
|
4008
|
+
|
|
4009
|
+
### Terminal \u2192 Telegram
|
|
4010
|
+
1. First time: run \`openacp integrate claude\` to install the handoff skill (one-time setup)
|
|
4011
|
+
2. In Claude Code, use the /openacp:handoff slash command
|
|
4012
|
+
3. The session appears as a new topic in Telegram and you can continue chatting there
|
|
4013
|
+
|
|
4014
|
+
### How it works
|
|
4015
|
+
- The agent session ID is shared between platforms
|
|
4016
|
+
- Conversation history is preserved \u2014 pick up where you left off
|
|
4017
|
+
- The agent that supports resume (e.g., Claude with \`--resume\`) handles the actual transfer
|
|
4018
|
+
|
|
4019
|
+
---
|
|
4020
|
+
|
|
4021
|
+
## Managing Sessions
|
|
4022
|
+
|
|
4023
|
+
### Status
|
|
4024
|
+
- \`/status\` \u2014 Shows active sessions count and details
|
|
4025
|
+
- Ask the Assistant: "What sessions are running?"
|
|
4026
|
+
|
|
4027
|
+
### List all sessions
|
|
4028
|
+
- \`/sessions\` \u2014 Shows all sessions with status (active, finished, error)
|
|
4029
|
+
|
|
4030
|
+
### Cancel
|
|
4031
|
+
- \`/cancel\` in a session topic \u2014 cancels that session
|
|
4032
|
+
- Ask the Assistant: "Cancel the stuck session"
|
|
4033
|
+
|
|
4034
|
+
### Cleanup
|
|
4035
|
+
- From \`/sessions\` \u2192 tap cleanup buttons (finished, errors, all)
|
|
4036
|
+
- Ask the Assistant: "Clean up old sessions"
|
|
4037
|
+
|
|
4038
|
+
---
|
|
4039
|
+
|
|
4040
|
+
## Assistant Topic
|
|
4041
|
+
|
|
4042
|
+
The Assistant is an always-on AI helper in its own topic. It can:
|
|
4043
|
+
- Answer questions about OpenACP
|
|
4044
|
+
- Create sessions for you
|
|
4045
|
+
- Check status and health
|
|
4046
|
+
- Cancel sessions
|
|
4047
|
+
- Clean up old sessions
|
|
4048
|
+
- Troubleshoot issues
|
|
4049
|
+
- Manage configuration
|
|
4050
|
+
|
|
4051
|
+
Just chat naturally: "How do I create a session?", "What's the status?", "Something is stuck"
|
|
4052
|
+
|
|
4053
|
+
### Clear history
|
|
4054
|
+
\`/clear\` in the Assistant topic \u2014 resets the conversation
|
|
4055
|
+
|
|
4056
|
+
---
|
|
4057
|
+
|
|
4058
|
+
## System Commands
|
|
4059
|
+
|
|
4060
|
+
| Command | Where | What it does |
|
|
4061
|
+
|---------|-------|-------------|
|
|
4062
|
+
| \`/new [agent] [path]\` | Anywhere | Create new session |
|
|
4063
|
+
| \`/newchat\` | Session topic | New session, same agent + folder |
|
|
4064
|
+
| \`/cancel\` | Session topic | Cancel current session |
|
|
4065
|
+
| \`/status\` | Anywhere | Show status |
|
|
4066
|
+
| \`/sessions\` | Anywhere | List all sessions |
|
|
4067
|
+
| \`/agents\` | Anywhere | List available agents |
|
|
4068
|
+
| \`/enable_dangerous\` | Session topic | Auto-approve all permissions |
|
|
4069
|
+
| \`/disable_dangerous\` | Session topic | Restore permission prompts |
|
|
4070
|
+
| \`/handoff\` | Session topic | Transfer session to terminal |
|
|
4071
|
+
| \`/clear\` | Assistant topic | Clear assistant history |
|
|
4072
|
+
| \`/menu\` | Anywhere | Show action menu |
|
|
4073
|
+
| \`/help\` | Anywhere | Show help |
|
|
4074
|
+
| \`/restart\` | Anywhere | Restart OpenACP |
|
|
4075
|
+
| \`/update\` | Anywhere | Update to latest version |
|
|
4076
|
+
| \`/integrate\` | Anywhere | Manage agent integrations |
|
|
4077
|
+
|
|
4078
|
+
---
|
|
4079
|
+
|
|
4080
|
+
## Menu Buttons
|
|
4081
|
+
|
|
4082
|
+
| Button | Action |
|
|
4083
|
+
|--------|--------|
|
|
4084
|
+
| \u{1F195} New Session | Create new session (interactive) |
|
|
4085
|
+
| \u{1F4CB} Sessions | List all sessions with cleanup options |
|
|
4086
|
+
| \u{1F4CA} Status | Show active/total session count |
|
|
4087
|
+
| \u{1F916} Agents | List available agents |
|
|
4088
|
+
| \u{1F517} Integrate | Manage agent integrations |
|
|
4089
|
+
| \u2753 Help | Show help text |
|
|
4090
|
+
| \u{1F504} Restart | Restart OpenACP |
|
|
4091
|
+
| \u2B06\uFE0F Update | Check and install updates |
|
|
4092
|
+
|
|
4093
|
+
---
|
|
4094
|
+
|
|
4095
|
+
## CLI Commands
|
|
4096
|
+
|
|
4097
|
+
### Server
|
|
4098
|
+
- \`openacp\` \u2014 Start (uses configured mode: foreground or daemon)
|
|
4099
|
+
- \`openacp start\` \u2014 Start as background daemon
|
|
4100
|
+
- \`openacp stop\` \u2014 Stop daemon
|
|
4101
|
+
- \`openacp status\` \u2014 Show daemon status
|
|
4102
|
+
- \`openacp logs\` \u2014 Tail daemon logs
|
|
4103
|
+
- \`openacp --foreground\` \u2014 Force foreground mode (useful for debugging or containers)
|
|
4104
|
+
|
|
4105
|
+
### Auto-start (run on boot)
|
|
4106
|
+
- macOS: installs a LaunchAgent in \`~/Library/LaunchAgents/\`
|
|
4107
|
+
- Linux: installs a systemd user service in \`~/.config/systemd/user/\`
|
|
4108
|
+
- Enabled automatically when you start the daemon. Remove with \`openacp stop\`.
|
|
4109
|
+
|
|
4110
|
+
### Configuration
|
|
4111
|
+
- \`openacp config\` \u2014 Interactive config editor
|
|
4112
|
+
- \`openacp reset\` \u2014 Delete all data and start fresh
|
|
4113
|
+
|
|
4114
|
+
### Plugins
|
|
4115
|
+
- \`openacp install <package>\` \u2014 Install adapter plugin (e.g., \`@openacp/adapter-discord\`)
|
|
4116
|
+
- \`openacp uninstall <package>\` \u2014 Remove adapter plugin
|
|
4117
|
+
- \`openacp plugins\` \u2014 List installed plugins
|
|
4118
|
+
|
|
4119
|
+
### Integration
|
|
4120
|
+
- \`openacp integrate <agent>\` \u2014 Install agent integration (e.g., Claude handoff skill)
|
|
4121
|
+
- \`openacp integrate <agent> --uninstall\` \u2014 Remove integration
|
|
4122
|
+
|
|
4123
|
+
### API (requires running daemon)
|
|
4124
|
+
\`openacp api <command>\` \u2014 Interact with running daemon:
|
|
4125
|
+
|
|
4126
|
+
| Command | Description |
|
|
4127
|
+
|---------|-------------|
|
|
4128
|
+
| \`status\` | List active sessions |
|
|
4129
|
+
| \`session <id>\` | Session details |
|
|
4130
|
+
| \`new <agent> <path>\` | Create session |
|
|
4131
|
+
| \`send <id> "text"\` | Send prompt |
|
|
4132
|
+
| \`cancel <id>\` | Cancel session |
|
|
4133
|
+
| \`dangerous <id> on/off\` | Toggle dangerous mode |
|
|
4134
|
+
| \`topics [--status x,y]\` | List topics |
|
|
4135
|
+
| \`delete-topic <id> [--force]\` | Delete topic |
|
|
4136
|
+
| \`cleanup [--status x,y]\` | Cleanup old topics |
|
|
4137
|
+
| \`agents\` | List agents |
|
|
4138
|
+
| \`health\` | System health |
|
|
4139
|
+
| \`config\` | Show config |
|
|
4140
|
+
| \`config set <key> <value>\` | Update config |
|
|
4141
|
+
| \`adapters\` | List adapters |
|
|
4142
|
+
| \`tunnel\` | Tunnel status |
|
|
4143
|
+
| \`notify "message"\` | Send notification |
|
|
4144
|
+
| \`version\` | Daemon version |
|
|
4145
|
+
| \`restart\` | Restart daemon |
|
|
4146
|
+
|
|
4147
|
+
---
|
|
4148
|
+
|
|
4149
|
+
## File Viewer (Tunnel)
|
|
4150
|
+
|
|
4151
|
+
When tunnel is enabled, file edits and diffs get "View" buttons that open in your browser:
|
|
4152
|
+
- **Monaco Editor** \u2014 Full VS Code editor with syntax highlighting
|
|
4153
|
+
- **Diff viewer** \u2014 Side-by-side or inline comparison
|
|
4154
|
+
- **Line highlighting** \u2014 Click lines to highlight
|
|
4155
|
+
- Dark/light theme toggle
|
|
4156
|
+
|
|
4157
|
+
### Setup
|
|
4158
|
+
Enable in config: set \`tunnel.enabled\` to \`true\`.
|
|
4159
|
+
Providers: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.
|
|
4160
|
+
|
|
4161
|
+
---
|
|
4162
|
+
|
|
4163
|
+
## Configuration
|
|
4164
|
+
|
|
4165
|
+
Config file: \`~/.openacp/config.json\`
|
|
4166
|
+
|
|
4167
|
+
### Telegram
|
|
4168
|
+
- **telegram.botToken** \u2014 Your Telegram bot token
|
|
4169
|
+
- **telegram.chatId** \u2014 Your Telegram supergroup ID
|
|
4170
|
+
|
|
4171
|
+
### Agents
|
|
4172
|
+
- **agents.<name>.command** \u2014 Agent executable path (e.g., \`claude\`, \`codex\`)
|
|
4173
|
+
- **agents.<name>.args** \u2014 Arguments to pass to the agent command
|
|
4174
|
+
- **agents.<name>.env** \u2014 Custom environment variables for the agent subprocess
|
|
4175
|
+
- **defaultAgent** \u2014 Which agent to use by default
|
|
4176
|
+
|
|
4177
|
+
### Workspace
|
|
4178
|
+
- **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
|
|
4179
|
+
|
|
4180
|
+
### Security
|
|
4181
|
+
- **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
|
|
4182
|
+
- **security.maxConcurrentSessions** \u2014 Max parallel sessions (default: 5)
|
|
4183
|
+
- **security.sessionTimeoutMinutes** \u2014 Auto-cancel idle sessions (default: 60)
|
|
4184
|
+
|
|
4185
|
+
### Tunnel / File Viewer
|
|
4186
|
+
- **tunnel.enabled** \u2014 Enable file viewer tunnel
|
|
4187
|
+
- **tunnel.provider** \u2014 Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale
|
|
4188
|
+
- **tunnel.port** \u2014 Local port for tunnel server (default: 3100)
|
|
4189
|
+
- **tunnel.auth.enabled** \u2014 Enable authentication for tunnel URLs
|
|
4190
|
+
- **tunnel.auth.token** \u2014 Auth token for tunnel access
|
|
4191
|
+
- **tunnel.storeTtlMinutes** \u2014 How long viewer links stay cached (default: 60)
|
|
4192
|
+
|
|
4193
|
+
### Logging
|
|
4194
|
+
- **logging.level** \u2014 Log level: silent, debug, info, warn, error, fatal (default: info)
|
|
4195
|
+
- **logging.logDir** \u2014 Log directory (default: \`~/.openacp/logs\`)
|
|
4196
|
+
- **logging.maxFileSize** \u2014 Max log file size before rotation
|
|
4197
|
+
- **logging.maxFiles** \u2014 Max number of rotated log files
|
|
4198
|
+
- **logging.sessionLogRetentionDays** \u2014 Auto-delete old session logs (default: 30)
|
|
4199
|
+
|
|
4200
|
+
### Data Retention
|
|
4201
|
+
- **sessionStore.ttlDays** \u2014 How long session records persist (default: 30). Old records are cleaned up automatically.
|
|
4202
|
+
|
|
4203
|
+
### Environment variables
|
|
4204
|
+
Override config with env vars:
|
|
4205
|
+
- \`OPENACP_TELEGRAM_BOT_TOKEN\`
|
|
4206
|
+
- \`OPENACP_TELEGRAM_CHAT_ID\`
|
|
4207
|
+
- \`OPENACP_DEFAULT_AGENT\`
|
|
4208
|
+
- \`OPENACP_RUN_MODE\` \u2014 foreground or daemon
|
|
4209
|
+
- \`OPENACP_API_PORT\` \u2014 API server port (default: 21420)
|
|
4210
|
+
- \`OPENACP_TUNNEL_ENABLED\`
|
|
4211
|
+
- \`OPENACP_TUNNEL_PORT\`
|
|
4212
|
+
- \`OPENACP_TUNNEL_PROVIDER\`
|
|
4213
|
+
- \`OPENACP_LOG_LEVEL\`
|
|
4214
|
+
- \`OPENACP_LOG_DIR\`
|
|
4215
|
+
- \`OPENACP_DEBUG\` \u2014 Sets log level to debug
|
|
4216
|
+
|
|
4217
|
+
---
|
|
4218
|
+
|
|
4219
|
+
## Troubleshooting
|
|
4220
|
+
|
|
4221
|
+
### Session stuck / not responding
|
|
4222
|
+
- Check status: ask Assistant "Is anything stuck?"
|
|
4223
|
+
- Cancel and create new: \`/cancel\` then \`/new\`
|
|
4224
|
+
- Check system health: Assistant can run health check
|
|
4225
|
+
|
|
4226
|
+
### Agent not found
|
|
4227
|
+
- Check available agents: \`/agents\`
|
|
4228
|
+
- Verify agent command is installed and in PATH
|
|
4229
|
+
- Check config: agent command + args must be correct
|
|
4230
|
+
|
|
4231
|
+
### Permission request not showing
|
|
4232
|
+
- Check Notifications topic for the alert
|
|
4233
|
+
- Try \`/enable_dangerous\` to auto-approve (if you trust the agent)
|
|
4234
|
+
|
|
4235
|
+
### Session disappeared after restart
|
|
4236
|
+
- Sessions persist across restarts
|
|
4237
|
+
- Send a message in the old topic \u2014 it auto-resumes
|
|
4238
|
+
- If topic was deleted, the session record may still exist in status
|
|
4239
|
+
|
|
4240
|
+
### Bot not responding at all
|
|
4241
|
+
- Check daemon: \`openacp status\`
|
|
4242
|
+
- Check logs: \`openacp logs\`
|
|
4243
|
+
- Restart: \`openacp start\` or \`/restart\`
|
|
4244
|
+
|
|
4245
|
+
### Messages going to wrong topic
|
|
4246
|
+
- Each session is bound to a specific Telegram topic
|
|
4247
|
+
- If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session
|
|
4248
|
+
|
|
4249
|
+
### Viewing logs
|
|
4250
|
+
- Session-specific logs: \`~/.openacp/logs/sessions/\`
|
|
4251
|
+
- System logs: \`openacp logs\` to tail live
|
|
4252
|
+
- Set \`OPENACP_DEBUG=true\` for verbose output
|
|
4253
|
+
|
|
4254
|
+
---
|
|
4255
|
+
|
|
4256
|
+
## Data & Storage
|
|
4257
|
+
|
|
4258
|
+
All data is stored in \`~/.openacp/\`:
|
|
4259
|
+
- \`config.json\` \u2014 Configuration
|
|
4260
|
+
- \`sessions/\` \u2014 Session records and state
|
|
4261
|
+
- \`topics/\` \u2014 Topic-to-session mappings
|
|
4262
|
+
- \`logs/\` \u2014 System and session logs
|
|
4263
|
+
- \`plugins/\` \u2014 Installed adapter plugins
|
|
4264
|
+
- \`openacp.pid\` \u2014 Daemon PID file
|
|
4265
|
+
|
|
4266
|
+
Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`).
|
|
4267
|
+
Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`).
|
|
4268
|
+
`;
|
|
4269
|
+
|
|
3571
4270
|
// src/adapters/telegram/assistant.ts
|
|
3572
|
-
var
|
|
4271
|
+
var log12 = createChildLogger({ module: "telegram-assistant" });
|
|
3573
4272
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
3574
4273
|
const config = core.configManager.get();
|
|
3575
|
-
|
|
4274
|
+
log12.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
3576
4275
|
const session = await core.sessionManager.createSession(
|
|
3577
4276
|
"telegram",
|
|
3578
4277
|
config.defaultAgent,
|
|
@@ -3581,7 +4280,7 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
3581
4280
|
);
|
|
3582
4281
|
session.threadId = String(assistantTopicId);
|
|
3583
4282
|
session.name = "Assistant";
|
|
3584
|
-
|
|
4283
|
+
log12.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
3585
4284
|
core.wireSessionEvents(session, adapter);
|
|
3586
4285
|
const allRecords = core.sessionManager.listRecords();
|
|
3587
4286
|
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
@@ -3598,58 +4297,106 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
3598
4297
|
};
|
|
3599
4298
|
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
3600
4299
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
3601
|
-
|
|
4300
|
+
log12.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
3602
4301
|
}).catch((err) => {
|
|
3603
|
-
|
|
4302
|
+
log12.warn({ err }, "Assistant system prompt failed");
|
|
3604
4303
|
});
|
|
3605
4304
|
return { session, ready };
|
|
3606
4305
|
}
|
|
4306
|
+
function buildWelcomeMessage(ctx) {
|
|
4307
|
+
const { activeCount, errorCount, totalCount, agents, defaultAgent } = ctx;
|
|
4308
|
+
const agentList = agents.map((a) => `${a}${a === defaultAgent ? " (default)" : ""}`).join(", ");
|
|
4309
|
+
if (totalCount === 0) {
|
|
4310
|
+
return `\u{1F44B} <b>OpenACP is ready!</b>
|
|
4311
|
+
|
|
4312
|
+
No sessions yet. Tap \u{1F195} New Session to start, or ask me anything!`;
|
|
4313
|
+
}
|
|
4314
|
+
if (errorCount > 0) {
|
|
4315
|
+
return `\u{1F44B} <b>OpenACP is ready!</b>
|
|
4316
|
+
|
|
4317
|
+
\u{1F4CA} ${activeCount} active, ${errorCount} errors / ${totalCount} total
|
|
4318
|
+
\u26A0\uFE0F ${errorCount} session${errorCount > 1 ? "s have" : " has"} errors \u2014 ask me to check if you'd like.
|
|
4319
|
+
|
|
4320
|
+
Agents: ${agentList}`;
|
|
4321
|
+
}
|
|
4322
|
+
return `\u{1F44B} <b>OpenACP is ready!</b>
|
|
4323
|
+
|
|
4324
|
+
\u{1F4CA} ${activeCount} active / ${totalCount} total
|
|
4325
|
+
Agents: ${agentList}`;
|
|
4326
|
+
}
|
|
3607
4327
|
function buildAssistantSystemPrompt(ctx) {
|
|
3608
4328
|
const { config, activeSessionCount, totalSessionCount, topicSummary } = ctx;
|
|
3609
4329
|
const agentNames = Object.keys(config.agents).join(", ");
|
|
3610
4330
|
const topicBreakdown = topicSummary.map((s) => `${s.status}: ${s.count}`).join(", ") || "none";
|
|
3611
|
-
return `You are the OpenACP Assistant
|
|
4331
|
+
return `You are the OpenACP Assistant \u2014 a helpful guide for managing AI coding sessions.
|
|
3612
4332
|
|
|
3613
4333
|
## Current State
|
|
3614
4334
|
- Active sessions: ${activeSessionCount} / ${totalSessionCount} total
|
|
3615
4335
|
- Topics by status: ${topicBreakdown}
|
|
3616
4336
|
- Available agents: ${agentNames}
|
|
3617
4337
|
- Default agent: ${config.defaultAgent}
|
|
3618
|
-
- Workspace base: ${config.workspace.baseDir}
|
|
4338
|
+
- Workspace base directory: ${config.workspace.baseDir}
|
|
4339
|
+
|
|
4340
|
+
## Action Playbook
|
|
3619
4341
|
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
-
|
|
3623
|
-
-
|
|
3624
|
-
-
|
|
3625
|
-
-
|
|
3626
|
-
- /agents \u2014 List agents
|
|
3627
|
-
- /help \u2014 Show help
|
|
4342
|
+
### Create Session
|
|
4343
|
+
- 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\`.
|
|
4344
|
+
- Ask which agent to use (if multiple are configured). Show available: ${agentNames}
|
|
4345
|
+
- Ask which project directory to use as workspace. Suggest \`${config.workspace.baseDir}\` as the base, but explain the user can provide any path.
|
|
4346
|
+
- Confirm before creating: show agent name + full workspace path.
|
|
4347
|
+
- Create via: \`openacp api new <agent> <workspace>\`
|
|
3628
4348
|
|
|
3629
|
-
|
|
3630
|
-
|
|
4349
|
+
### Check Status / List Sessions
|
|
4350
|
+
- Run \`openacp api status\` for active sessions overview
|
|
4351
|
+
- Run \`openacp api topics\` for full list with statuses
|
|
4352
|
+
- Format the output nicely for the user
|
|
3631
4353
|
|
|
3632
|
-
### Session
|
|
4354
|
+
### Cancel Session
|
|
4355
|
+
- Run \`openacp api status\` to see what's active
|
|
4356
|
+
- If 1 active session \u2192 ask user to confirm \u2192 \`openacp api cancel <id>\`
|
|
4357
|
+
- If multiple \u2192 list them, ask user which one to cancel
|
|
4358
|
+
|
|
4359
|
+
### Troubleshoot (Session Stuck, Errors)
|
|
4360
|
+
- Run \`openacp api health\` + \`openacp api status\` to diagnose
|
|
4361
|
+
- Small issue (stuck session) \u2192 suggest cancel + create new
|
|
4362
|
+
- Big issue (system-level) \u2192 suggest restart, ask for confirmation first
|
|
4363
|
+
|
|
4364
|
+
### Cleanup Old Sessions
|
|
4365
|
+
- Run \`openacp api topics --status finished,error\` to see what can be cleaned
|
|
4366
|
+
- Report the count, ask user to confirm
|
|
4367
|
+
- Execute: \`openacp api cleanup --status <statuses>\`
|
|
4368
|
+
|
|
4369
|
+
### Configuration
|
|
4370
|
+
- View: \`openacp api config\`
|
|
4371
|
+
- Update: \`openacp api config set <key> <value>\`
|
|
4372
|
+
|
|
4373
|
+
### Restart / Update
|
|
4374
|
+
- Always ask for confirmation \u2014 these are disruptive actions
|
|
4375
|
+
- Guide user: "Tap \u{1F504} Restart button or type /restart"
|
|
4376
|
+
|
|
4377
|
+
### Toggle Dangerous Mode
|
|
4378
|
+
- Run \`openacp api dangerous <id> on|off\`
|
|
4379
|
+
- Explain: dangerous mode auto-approves all permission requests \u2014 the agent can run any command without asking
|
|
4380
|
+
|
|
4381
|
+
## CLI Commands Reference
|
|
3633
4382
|
\`\`\`bash
|
|
4383
|
+
# Session management
|
|
3634
4384
|
openacp api status # List active sessions
|
|
3635
4385
|
openacp api session <id> # Session detail
|
|
4386
|
+
openacp api new <agent> <workspace> # Create new session
|
|
3636
4387
|
openacp api send <id> "prompt text" # Send prompt to session
|
|
3637
4388
|
openacp api cancel <id> # Cancel session
|
|
3638
4389
|
openacp api dangerous <id> on|off # Toggle dangerous mode
|
|
3639
|
-
\`\`\`
|
|
3640
4390
|
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
openacp api topics # List topics
|
|
4391
|
+
# Topic management
|
|
4392
|
+
openacp api topics # List all topics
|
|
3644
4393
|
openacp api topics --status finished,error
|
|
3645
4394
|
openacp api delete-topic <id> # Delete topic
|
|
3646
4395
|
openacp api delete-topic <id> --force # Force delete active
|
|
3647
4396
|
openacp api cleanup # Cleanup finished topics
|
|
3648
4397
|
openacp api cleanup --status finished,error
|
|
3649
|
-
\`\`\`
|
|
3650
4398
|
|
|
3651
|
-
|
|
3652
|
-
\`\`\`bash
|
|
4399
|
+
# System
|
|
3653
4400
|
openacp api health # System health
|
|
3654
4401
|
openacp api config # Show config
|
|
3655
4402
|
openacp api config set <key> <value> # Update config
|
|
@@ -3661,13 +4408,18 @@ openacp api restart # Restart daemon
|
|
|
3661
4408
|
\`\`\`
|
|
3662
4409
|
|
|
3663
4410
|
## Guidelines
|
|
3664
|
-
-
|
|
3665
|
-
-
|
|
3666
|
-
-
|
|
3667
|
-
-
|
|
3668
|
-
-
|
|
3669
|
-
-
|
|
3670
|
-
-
|
|
4411
|
+
- 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.
|
|
4412
|
+
- 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").
|
|
4413
|
+
- When creating sessions: guide user through agent + workspace choice conversationally, then run the command yourself.
|
|
4414
|
+
- Destructive actions (cancel active session, restart, cleanup) \u2192 always ask user to confirm first in natural language.
|
|
4415
|
+
- Small/obvious issues (clearly stuck session with no activity) \u2192 fix it and report back.
|
|
4416
|
+
- Respond in the same language the user uses.
|
|
4417
|
+
- Format responses for Telegram: use <b>bold</b>, <code>code</code>, keep it concise.
|
|
4418
|
+
- When you don't know something, check with the relevant \`openacp api\` command first before answering.
|
|
4419
|
+
- 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.
|
|
4420
|
+
|
|
4421
|
+
## Product Reference
|
|
4422
|
+
${PRODUCT_GUIDE}`;
|
|
3671
4423
|
}
|
|
3672
4424
|
async function handleAssistantMessage(session, text) {
|
|
3673
4425
|
if (!session) return;
|
|
@@ -3680,7 +4432,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
3680
4432
|
}
|
|
3681
4433
|
|
|
3682
4434
|
// src/adapters/telegram/activity.ts
|
|
3683
|
-
var
|
|
4435
|
+
var log13 = createChildLogger({ module: "telegram:activity" });
|
|
3684
4436
|
var THINKING_REFRESH_MS = 15e3;
|
|
3685
4437
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
3686
4438
|
var ThinkingIndicator = class {
|
|
@@ -3712,7 +4464,7 @@ var ThinkingIndicator = class {
|
|
|
3712
4464
|
this.startRefreshTimer();
|
|
3713
4465
|
}
|
|
3714
4466
|
} catch (err) {
|
|
3715
|
-
|
|
4467
|
+
log13.warn({ err }, "ThinkingIndicator.show() failed");
|
|
3716
4468
|
} finally {
|
|
3717
4469
|
this.sending = false;
|
|
3718
4470
|
}
|
|
@@ -3785,7 +4537,7 @@ var UsageMessage = class {
|
|
|
3785
4537
|
if (result) this.msgId = result.message_id;
|
|
3786
4538
|
}
|
|
3787
4539
|
} catch (err) {
|
|
3788
|
-
|
|
4540
|
+
log13.warn({ err }, "UsageMessage.send() failed");
|
|
3789
4541
|
}
|
|
3790
4542
|
}
|
|
3791
4543
|
getMsgId() {
|
|
@@ -3798,7 +4550,7 @@ var UsageMessage = class {
|
|
|
3798
4550
|
try {
|
|
3799
4551
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
3800
4552
|
} catch (err) {
|
|
3801
|
-
|
|
4553
|
+
log13.warn({ err }, "UsageMessage.delete() failed");
|
|
3802
4554
|
}
|
|
3803
4555
|
}
|
|
3804
4556
|
};
|
|
@@ -3884,7 +4636,7 @@ var PlanCard = class {
|
|
|
3884
4636
|
if (result) this.msgId = result.message_id;
|
|
3885
4637
|
}
|
|
3886
4638
|
} catch (err) {
|
|
3887
|
-
|
|
4639
|
+
log13.warn({ err }, "PlanCard flush failed");
|
|
3888
4640
|
}
|
|
3889
4641
|
}
|
|
3890
4642
|
};
|
|
@@ -3947,7 +4699,7 @@ var ActivityTracker = class {
|
|
|
3947
4699
|
})
|
|
3948
4700
|
);
|
|
3949
4701
|
} catch (err) {
|
|
3950
|
-
|
|
4702
|
+
log13.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
3951
4703
|
}
|
|
3952
4704
|
}
|
|
3953
4705
|
}
|
|
@@ -4030,7 +4782,7 @@ var TelegramSendQueue = class {
|
|
|
4030
4782
|
|
|
4031
4783
|
// src/adapters/telegram/action-detect.ts
|
|
4032
4784
|
import { nanoid as nanoid3 } from "nanoid";
|
|
4033
|
-
import { InlineKeyboard as
|
|
4785
|
+
import { InlineKeyboard as InlineKeyboard7 } from "grammy";
|
|
4034
4786
|
var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
|
|
4035
4787
|
var CMD_CANCEL_RE = /\/cancel\b/;
|
|
4036
4788
|
var KW_NEW_RE = /(?:create|new)\s+session/i;
|
|
@@ -4077,7 +4829,7 @@ function removeAction(id) {
|
|
|
4077
4829
|
actionMap.delete(id);
|
|
4078
4830
|
}
|
|
4079
4831
|
function buildActionKeyboard(actionId, action) {
|
|
4080
|
-
const keyboard = new
|
|
4832
|
+
const keyboard = new InlineKeyboard7();
|
|
4081
4833
|
if (action.action === "new_session") {
|
|
4082
4834
|
keyboard.text("\u2705 Create session", `a:${actionId}`);
|
|
4083
4835
|
keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
|
|
@@ -4109,27 +4861,38 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
4109
4861
|
removeAction(actionId);
|
|
4110
4862
|
try {
|
|
4111
4863
|
if (action.action === "new_session") {
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4864
|
+
if (action.agent && action.workspace) {
|
|
4865
|
+
await ctx.answerCallbackQuery({ text: "\u23F3 Creating session..." });
|
|
4866
|
+
const { threadId, firstMsgId } = await executeNewSession(
|
|
4867
|
+
bot,
|
|
4868
|
+
core,
|
|
4869
|
+
chatId,
|
|
4870
|
+
action.agent,
|
|
4871
|
+
action.workspace
|
|
4872
|
+
);
|
|
4873
|
+
const topicLink = `https://t.me/c/${String(chatId).replace("-100", "")}/${firstMsgId ?? threadId}`;
|
|
4874
|
+
const originalText = ctx.callbackQuery.message?.text ?? "";
|
|
4875
|
+
try {
|
|
4876
|
+
await ctx.editMessageText(
|
|
4877
|
+
originalText + `
|
|
4125
4878
|
|
|
4126
4879
|
\u2705 Session created \u2192 <a href="${topicLink}">Go to topic</a>`,
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4880
|
+
{ parse_mode: "HTML" }
|
|
4881
|
+
);
|
|
4882
|
+
} catch {
|
|
4883
|
+
await ctx.editMessageReplyMarkup({
|
|
4884
|
+
reply_markup: { inline_keyboard: [] }
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4887
|
+
} else {
|
|
4888
|
+
await ctx.answerCallbackQuery();
|
|
4889
|
+
try {
|
|
4890
|
+
await ctx.editMessageReplyMarkup({
|
|
4891
|
+
reply_markup: { inline_keyboard: [] }
|
|
4892
|
+
});
|
|
4893
|
+
} catch {
|
|
4894
|
+
}
|
|
4895
|
+
await startInteractiveNewSession(ctx, core, chatId, action.agent);
|
|
4133
4896
|
}
|
|
4134
4897
|
} else if (action.action === "cancel_session") {
|
|
4135
4898
|
const assistantId = getAssistantSessionId();
|
|
@@ -4174,7 +4937,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
4174
4937
|
}
|
|
4175
4938
|
|
|
4176
4939
|
// src/adapters/telegram/adapter.ts
|
|
4177
|
-
var
|
|
4940
|
+
var log14 = createChildLogger({ module: "telegram" });
|
|
4178
4941
|
function patchedFetch(input, init) {
|
|
4179
4942
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
4180
4943
|
const nativeController = new AbortController();
|
|
@@ -4225,7 +4988,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4225
4988
|
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
|
|
4226
4989
|
this.bot.catch((err) => {
|
|
4227
4990
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
4228
|
-
|
|
4991
|
+
log14.error({ err: rootCause }, "Telegram bot error");
|
|
4229
4992
|
});
|
|
4230
4993
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
4231
4994
|
const maxRetries = 3;
|
|
@@ -4239,7 +5002,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4239
5002
|
if (rateLimitedMethods.includes(method)) {
|
|
4240
5003
|
this.sendQueue.onRateLimited();
|
|
4241
5004
|
}
|
|
4242
|
-
|
|
5005
|
+
log14.warn(
|
|
4243
5006
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
4244
5007
|
"Rate limited by Telegram, retrying"
|
|
4245
5008
|
);
|
|
@@ -4291,7 +5054,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4291
5054
|
() => this.assistantSession?.id
|
|
4292
5055
|
);
|
|
4293
5056
|
setupIntegrateCallbacks(this.bot, this.core);
|
|
4294
|
-
|
|
5057
|
+
setupAllCallbacks(
|
|
4295
5058
|
this.bot,
|
|
4296
5059
|
this.core,
|
|
4297
5060
|
this.telegramConfig.chatId,
|
|
@@ -4303,7 +5066,23 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4303
5066
|
this.telegramConfig.chatId,
|
|
4304
5067
|
{
|
|
4305
5068
|
topicId: this.assistantTopicId,
|
|
4306
|
-
getSession: () => this.assistantSession
|
|
5069
|
+
getSession: () => this.assistantSession,
|
|
5070
|
+
respawn: async () => {
|
|
5071
|
+
if (this.assistantSession) {
|
|
5072
|
+
await this.assistantSession.destroy();
|
|
5073
|
+
this.assistantSession = null;
|
|
5074
|
+
}
|
|
5075
|
+
const { session, ready } = await spawnAssistant(
|
|
5076
|
+
this.core,
|
|
5077
|
+
this,
|
|
5078
|
+
this.assistantTopicId
|
|
5079
|
+
);
|
|
5080
|
+
this.assistantSession = session;
|
|
5081
|
+
this.assistantInitializing = true;
|
|
5082
|
+
ready.then(() => {
|
|
5083
|
+
this.assistantInitializing = false;
|
|
5084
|
+
});
|
|
5085
|
+
}
|
|
4307
5086
|
}
|
|
4308
5087
|
);
|
|
4309
5088
|
this.permissionHandler.setupCallbackHandler();
|
|
@@ -4348,7 +5127,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4348
5127
|
this.setupRoutes();
|
|
4349
5128
|
this.bot.start({
|
|
4350
5129
|
allowed_updates: ["message", "callback_query"],
|
|
4351
|
-
onStart: () =>
|
|
5130
|
+
onStart: () => log14.info(
|
|
4352
5131
|
{ chatId: this.telegramConfig.chatId },
|
|
4353
5132
|
"Telegram bot started"
|
|
4354
5133
|
)
|
|
@@ -4356,27 +5135,24 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4356
5135
|
try {
|
|
4357
5136
|
const config = this.core.configManager.get();
|
|
4358
5137
|
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
5138
|
const allRecords = this.core.sessionManager.listRecords();
|
|
4362
|
-
const
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
<b>Select an action:</b>`;
|
|
5139
|
+
const welcomeText = buildWelcomeMessage({
|
|
5140
|
+
activeCount: allRecords.filter((r) => r.status === "active" || r.status === "initializing").length,
|
|
5141
|
+
errorCount: allRecords.filter((r) => r.status === "error").length,
|
|
5142
|
+
totalCount: allRecords.length,
|
|
5143
|
+
agents: agents.map((a) => a.name),
|
|
5144
|
+
defaultAgent: config.defaultAgent
|
|
5145
|
+
});
|
|
4370
5146
|
await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
|
|
4371
5147
|
message_thread_id: this.assistantTopicId,
|
|
4372
5148
|
parse_mode: "HTML",
|
|
4373
5149
|
reply_markup: buildMenuKeyboard()
|
|
4374
5150
|
});
|
|
4375
5151
|
} catch (err) {
|
|
4376
|
-
|
|
5152
|
+
log14.warn({ err }, "Failed to send welcome message");
|
|
4377
5153
|
}
|
|
4378
5154
|
try {
|
|
4379
|
-
|
|
5155
|
+
log14.info("Spawning assistant session...");
|
|
4380
5156
|
const { session, ready } = await spawnAssistant(
|
|
4381
5157
|
this.core,
|
|
4382
5158
|
this,
|
|
@@ -4384,13 +5160,13 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4384
5160
|
);
|
|
4385
5161
|
this.assistantSession = session;
|
|
4386
5162
|
this.assistantInitializing = true;
|
|
4387
|
-
|
|
5163
|
+
log14.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
4388
5164
|
ready.then(() => {
|
|
4389
5165
|
this.assistantInitializing = false;
|
|
4390
|
-
|
|
5166
|
+
log14.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
4391
5167
|
});
|
|
4392
5168
|
} catch (err) {
|
|
4393
|
-
|
|
5169
|
+
log14.error({ err }, "Failed to spawn assistant");
|
|
4394
5170
|
this.bot.api.sendMessage(
|
|
4395
5171
|
this.telegramConfig.chatId,
|
|
4396
5172
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -4406,11 +5182,14 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4406
5182
|
await this.assistantSession.destroy();
|
|
4407
5183
|
}
|
|
4408
5184
|
await this.bot.stop();
|
|
4409
|
-
|
|
5185
|
+
log14.info("Telegram bot stopped");
|
|
4410
5186
|
}
|
|
4411
5187
|
setupRoutes() {
|
|
4412
5188
|
this.bot.on("message:text", async (ctx) => {
|
|
4413
5189
|
const threadId = ctx.message.message_thread_id;
|
|
5190
|
+
if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
4414
5193
|
if (!threadId) {
|
|
4415
5194
|
const html = redirectToAssistant(
|
|
4416
5195
|
this.telegramConfig.chatId,
|
|
@@ -4429,7 +5208,7 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4429
5208
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
4430
5209
|
});
|
|
4431
5210
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
4432
|
-
(err) =>
|
|
5211
|
+
(err) => log14.error({ err }, "Assistant error")
|
|
4433
5212
|
);
|
|
4434
5213
|
return;
|
|
4435
5214
|
}
|
|
@@ -4446,7 +5225,7 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4446
5225
|
threadId: String(threadId),
|
|
4447
5226
|
userId: String(ctx.from.id),
|
|
4448
5227
|
text: ctx.message.text
|
|
4449
|
-
}).catch((err) =>
|
|
5228
|
+
}).catch((err) => log14.error({ err }, "handleMessage error"));
|
|
4450
5229
|
});
|
|
4451
5230
|
}
|
|
4452
5231
|
// --- ChannelAdapter implementations ---
|
|
@@ -4457,6 +5236,10 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4457
5236
|
);
|
|
4458
5237
|
if (!session) return;
|
|
4459
5238
|
const threadId = Number(session.threadId);
|
|
5239
|
+
if (!threadId || isNaN(threadId)) {
|
|
5240
|
+
log14.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
4460
5243
|
switch (content.type) {
|
|
4461
5244
|
case "thought": {
|
|
4462
5245
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
@@ -4526,16 +5309,16 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4526
5309
|
if (toolState) {
|
|
4527
5310
|
if (meta.viewerLinks) {
|
|
4528
5311
|
toolState.viewerLinks = meta.viewerLinks;
|
|
4529
|
-
|
|
5312
|
+
log14.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
4530
5313
|
}
|
|
4531
5314
|
const viewerFilePath = content.metadata?.viewerFilePath;
|
|
4532
5315
|
if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
|
|
4533
5316
|
if (meta.name) toolState.name = meta.name;
|
|
4534
5317
|
if (meta.kind) toolState.kind = meta.kind;
|
|
4535
5318
|
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
4536
|
-
if (!isTerminal
|
|
5319
|
+
if (!isTerminal) break;
|
|
4537
5320
|
await toolState.ready;
|
|
4538
|
-
|
|
5321
|
+
log14.debug(
|
|
4539
5322
|
{ toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
|
|
4540
5323
|
"Tool completed, preparing edit"
|
|
4541
5324
|
);
|
|
@@ -4557,7 +5340,7 @@ Sessions: ${activeCount} active / ${allRecords.length} total
|
|
|
4557
5340
|
)
|
|
4558
5341
|
);
|
|
4559
5342
|
} catch (err) {
|
|
4560
|
-
|
|
5343
|
+
log14.warn(
|
|
4561
5344
|
{ err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
|
|
4562
5345
|
"Tool update edit failed"
|
|
4563
5346
|
);
|
|
@@ -4652,7 +5435,7 @@ Task completed.
|
|
|
4652
5435
|
}
|
|
4653
5436
|
}
|
|
4654
5437
|
async sendPermissionRequest(sessionId, request) {
|
|
4655
|
-
|
|
5438
|
+
log14.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
4656
5439
|
const session = this.core.sessionManager.getSession(
|
|
4657
5440
|
sessionId
|
|
4658
5441
|
);
|
|
@@ -4660,7 +5443,7 @@ Task completed.
|
|
|
4660
5443
|
if (request.description.includes("openacp")) {
|
|
4661
5444
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
4662
5445
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
4663
|
-
|
|
5446
|
+
log14.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
4664
5447
|
session.permissionGate.resolve(allowOption.id);
|
|
4665
5448
|
}
|
|
4666
5449
|
return;
|
|
@@ -4668,7 +5451,7 @@ Task completed.
|
|
|
4668
5451
|
if (session.dangerousMode) {
|
|
4669
5452
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
4670
5453
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
4671
|
-
|
|
5454
|
+
log14.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
4672
5455
|
session.permissionGate.resolve(allowOption.id);
|
|
4673
5456
|
}
|
|
4674
5457
|
return;
|
|
@@ -4679,7 +5462,7 @@ Task completed.
|
|
|
4679
5462
|
}
|
|
4680
5463
|
async sendNotification(notification) {
|
|
4681
5464
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
4682
|
-
|
|
5465
|
+
log14.info(
|
|
4683
5466
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
4684
5467
|
"Notification sent"
|
|
4685
5468
|
);
|
|
@@ -4715,7 +5498,7 @@ Task completed.
|
|
|
4715
5498
|
);
|
|
4716
5499
|
}
|
|
4717
5500
|
async createSessionThread(sessionId, name) {
|
|
4718
|
-
|
|
5501
|
+
log14.info({ sessionId, name }, "Session topic created");
|
|
4719
5502
|
return String(
|
|
4720
5503
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
4721
5504
|
);
|
|
@@ -4744,7 +5527,7 @@ Task completed.
|
|
|
4744
5527
|
try {
|
|
4745
5528
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
4746
5529
|
} catch (err) {
|
|
4747
|
-
|
|
5530
|
+
log14.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
4748
5531
|
}
|
|
4749
5532
|
}
|
|
4750
5533
|
async sendSkillCommands(sessionId, commands) {
|
|
@@ -4817,7 +5600,7 @@ Task completed.
|
|
|
4817
5600
|
{ disable_notification: true }
|
|
4818
5601
|
);
|
|
4819
5602
|
} catch (err) {
|
|
4820
|
-
|
|
5603
|
+
log14.error({ err, sessionId }, "Failed to send skill commands");
|
|
4821
5604
|
}
|
|
4822
5605
|
}
|
|
4823
5606
|
async cleanupSkillCommands(sessionId) {
|
|
@@ -4888,4 +5671,4 @@ export {
|
|
|
4888
5671
|
TopicManager,
|
|
4889
5672
|
TelegramAdapter
|
|
4890
5673
|
};
|
|
4891
|
-
//# sourceMappingURL=chunk-
|
|
5674
|
+
//# sourceMappingURL=chunk-KPI4HGJC.js.map
|