@grinev/opencode-telegram-bot 0.16.1 → 0.18.0
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/.env.example +5 -0
- package/README.md +72 -43
- package/dist/app/start-bot-app.js +0 -2
- package/dist/attach/manager.js +66 -0
- package/dist/attach/service.js +166 -0
- package/dist/bot/commands/abort.js +0 -4
- package/dist/bot/commands/commands.js +13 -3
- package/dist/bot/commands/definitions.js +2 -0
- package/dist/bot/commands/new.js +10 -18
- package/dist/bot/commands/opencode-start.js +27 -24
- package/dist/bot/commands/opencode-stop.js +51 -23
- package/dist/bot/commands/projects.js +30 -6
- package/dist/bot/commands/sessions.js +19 -20
- package/dist/bot/commands/skills.js +376 -0
- package/dist/bot/commands/start.js +2 -0
- package/dist/bot/commands/status.js +21 -14
- package/dist/bot/commands/worktree.js +196 -0
- package/dist/bot/handlers/inline-menu.js +1 -0
- package/dist/bot/handlers/prompt.js +26 -31
- package/dist/bot/handlers/voice.js +8 -1
- package/dist/bot/index.js +90 -10
- package/dist/bot/middleware/interaction-guard.js +1 -1
- package/dist/bot/utils/busy-guard.js +3 -2
- package/dist/bot/utils/external-user-input.js +46 -0
- package/dist/bot/utils/switch-project.js +2 -0
- package/dist/config.js +2 -0
- package/dist/external-input/suppression.js +54 -0
- package/dist/git/worktree.js +158 -0
- package/dist/i18n/de.js +54 -1
- package/dist/i18n/en.js +54 -1
- package/dist/i18n/es.js +54 -1
- package/dist/i18n/fr.js +54 -1
- package/dist/i18n/ru.js +54 -1
- package/dist/i18n/zh.js +54 -1
- package/dist/interaction/guard.js +2 -1
- package/dist/opencode/events.js +13 -1
- package/dist/opencode/process.js +182 -0
- package/dist/pinned/manager.js +46 -61
- package/dist/project/manager.js +47 -32
- package/dist/runtime/bootstrap.js +119 -7
- package/dist/scheduled-task/executor.js +137 -7
- package/dist/settings/manager.js +9 -12
- package/dist/summary/aggregator.js +54 -9
- package/package.json +1 -1
- package/dist/process/manager.js +0 -273
- package/dist/process/types.js +0 -1
package/dist/bot/commands/new.js
CHANGED
|
@@ -3,8 +3,6 @@ import { setCurrentSession } from "../../session/manager.js";
|
|
|
3
3
|
import { ingestSessionInfoForCache } from "../../session/cache-manager.js";
|
|
4
4
|
import { getCurrentProject } from "../../settings/manager.js";
|
|
5
5
|
import { clearAllInteractionState } from "../../interaction/cleanup.js";
|
|
6
|
-
import { summaryAggregator } from "../../summary/aggregator.js";
|
|
7
|
-
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
8
6
|
import { keyboardManager } from "../../keyboard/manager.js";
|
|
9
7
|
import { getStoredAgent, resolveProjectAgent } from "../../agent/manager.js";
|
|
10
8
|
import { getStoredModel } from "../../model/manager.js";
|
|
@@ -13,7 +11,8 @@ import { createMainKeyboard } from "../utils/keyboard.js";
|
|
|
13
11
|
import { isForegroundBusy, replyBusyBlocked } from "../utils/busy-guard.js";
|
|
14
12
|
import { logger } from "../../utils/logger.js";
|
|
15
13
|
import { t } from "../../i18n/index.js";
|
|
16
|
-
|
|
14
|
+
import { attachToSession } from "../../attach/service.js";
|
|
15
|
+
export async function newCommand(ctx, deps) {
|
|
17
16
|
try {
|
|
18
17
|
if (isForegroundBusy()) {
|
|
19
18
|
await replyBusyBlocked(ctx);
|
|
@@ -38,27 +37,20 @@ export async function newCommand(ctx) {
|
|
|
38
37
|
directory: currentProject.worktree,
|
|
39
38
|
};
|
|
40
39
|
setCurrentSession(sessionInfo);
|
|
41
|
-
summaryAggregator.clear();
|
|
42
40
|
clearAllInteractionState("session_created");
|
|
43
41
|
await ingestSessionInfoForCache(session);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
await pinnedMessageManager.onSessionChange(session.id, session.title);
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
logger.error("[Bot] Error creating pinned message:", err);
|
|
55
|
-
}
|
|
42
|
+
await attachToSession({
|
|
43
|
+
bot: deps.bot,
|
|
44
|
+
chatId: ctx.chat.id,
|
|
45
|
+
session: sessionInfo,
|
|
46
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
47
|
+
});
|
|
56
48
|
// Get current state for keyboard
|
|
57
49
|
const currentAgent = await resolveProjectAgent(getStoredAgent());
|
|
58
50
|
const currentModel = getStoredModel();
|
|
59
|
-
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
60
|
-
const variantName = formatVariantForButton(currentModel.variant || "default");
|
|
61
51
|
keyboardManager.updateAgent(currentAgent);
|
|
52
|
+
const contextInfo = keyboardManager.getContextInfo();
|
|
53
|
+
const variantName = formatVariantForButton(currentModel.variant || "default");
|
|
62
54
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo ?? undefined, variantName);
|
|
63
55
|
await ctx.reply(t("new.created", { title: session.title }), {
|
|
64
56
|
reply_markup: keyboard,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { config } from "../../config.js";
|
|
1
3
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
-
import {
|
|
4
|
+
import { resolveLocalOpencodeTarget } from "../../opencode/process.js";
|
|
3
5
|
import { logger } from "../../utils/logger.js";
|
|
4
6
|
import { t } from "../../i18n/index.js";
|
|
5
7
|
import { editBotText } from "../utils/telegram-text.js";
|
|
@@ -31,43 +33,45 @@ async function waitForServerReady(maxWaitMs = 10000) {
|
|
|
31
33
|
*/
|
|
32
34
|
export async function opencodeStartCommand(ctx) {
|
|
33
35
|
try {
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const uptimeStr = uptime ? Math.floor(uptime / 1000) : 0;
|
|
38
|
-
await ctx.reply(t("opencode_start.already_running_managed", {
|
|
39
|
-
pid: processManager.getPID() ?? "-",
|
|
40
|
-
seconds: uptimeStr,
|
|
41
|
-
}));
|
|
36
|
+
const localTarget = resolveLocalOpencodeTarget(config.opencode.apiUrl);
|
|
37
|
+
if (!localTarget) {
|
|
38
|
+
await ctx.reply(t("opencode_start.remote_configured"));
|
|
42
39
|
return;
|
|
43
40
|
}
|
|
44
|
-
//
|
|
41
|
+
// Check if server is already accessible.
|
|
45
42
|
try {
|
|
46
43
|
const { data, error } = await opencodeClient.global.health();
|
|
47
44
|
if (!error && data?.healthy) {
|
|
48
|
-
await ctx.reply(t("opencode_start.
|
|
49
|
-
version: data.version || t("common.unknown"),
|
|
50
|
-
}));
|
|
45
|
+
await ctx.reply(t("opencode_start.already_running", { version: data.version || t("common.unknown") }));
|
|
51
46
|
return;
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
catch {
|
|
55
|
-
// Server not accessible, continue with start
|
|
50
|
+
// Server not accessible, continue with start.
|
|
56
51
|
}
|
|
57
|
-
// 3. Notify user that we're starting the server
|
|
58
52
|
const statusMessage = await ctx.reply(t("opencode_start.starting"));
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
53
|
+
const isWindows = process.platform === "win32";
|
|
54
|
+
const command = isWindows ? "cmd.exe" : "opencode";
|
|
55
|
+
const args = isWindows ? ["/c", "opencode", "serve"] : ["serve"];
|
|
56
|
+
const childProcess = spawn(command, args, {
|
|
57
|
+
detached: true,
|
|
58
|
+
stdio: "ignore",
|
|
59
|
+
windowsHide: isWindows,
|
|
60
|
+
});
|
|
61
|
+
childProcess.once("error", (error) => {
|
|
62
|
+
logger.error("[Bot] OpenCode server process failed to start", error);
|
|
63
|
+
});
|
|
64
|
+
const pid = childProcess.pid;
|
|
65
|
+
if (!pid) {
|
|
62
66
|
await editBotText({
|
|
63
67
|
api: ctx.api,
|
|
64
68
|
chatId: ctx.chat.id,
|
|
65
69
|
messageId: statusMessage.message_id,
|
|
66
|
-
text: t("opencode_start.start_error", { error:
|
|
70
|
+
text: t("opencode_start.start_error", { error: t("common.unknown_error") }),
|
|
67
71
|
});
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
70
|
-
|
|
74
|
+
childProcess.unref();
|
|
71
75
|
logger.info("[Bot] Waiting for OpenCode server to become ready...");
|
|
72
76
|
const ready = await waitForServerReady(10000);
|
|
73
77
|
if (!ready) {
|
|
@@ -76,23 +80,22 @@ export async function opencodeStartCommand(ctx) {
|
|
|
76
80
|
chatId: ctx.chat.id,
|
|
77
81
|
messageId: statusMessage.message_id,
|
|
78
82
|
text: t("opencode_start.started_not_ready", {
|
|
79
|
-
pid
|
|
83
|
+
pid,
|
|
80
84
|
}),
|
|
81
85
|
});
|
|
82
86
|
return;
|
|
83
87
|
}
|
|
84
|
-
// 6. Get server version and send success message
|
|
85
88
|
const { data: health } = await opencodeClient.global.health();
|
|
86
89
|
await editBotText({
|
|
87
90
|
api: ctx.api,
|
|
88
91
|
chatId: ctx.chat.id,
|
|
89
92
|
messageId: statusMessage.message_id,
|
|
90
93
|
text: t("opencode_start.success", {
|
|
91
|
-
pid
|
|
94
|
+
pid,
|
|
92
95
|
version: health?.version || t("common.unknown"),
|
|
93
96
|
}),
|
|
94
97
|
});
|
|
95
|
-
logger.info(`[Bot] OpenCode server started successfully, PID=${
|
|
98
|
+
logger.info(`[Bot] OpenCode server started successfully, PID=${pid}, port=${localTarget.port}`);
|
|
96
99
|
}
|
|
97
100
|
catch (err) {
|
|
98
101
|
logger.error("[Bot] Error in /opencode-start command:", err);
|
|
@@ -1,41 +1,69 @@
|
|
|
1
|
+
import { config } from "../../config.js";
|
|
1
2
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
-
import {
|
|
3
|
+
import { findServerPid, killServerProcess, resolveLocalOpencodeTarget, } from "../../opencode/process.js";
|
|
3
4
|
import { logger } from "../../utils/logger.js";
|
|
4
5
|
import { t } from "../../i18n/index.js";
|
|
6
|
+
import { editBotText } from "../utils/telegram-text.js";
|
|
5
7
|
/**
|
|
6
8
|
* Command handler for /opencode-stop
|
|
7
9
|
* Stops the OpenCode server process
|
|
8
10
|
*/
|
|
9
11
|
export async function opencodeStopCommand(ctx) {
|
|
10
12
|
try {
|
|
11
|
-
|
|
12
|
-
if (!
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
catch {
|
|
22
|
-
// Server not accessible
|
|
13
|
+
const localTarget = resolveLocalOpencodeTarget(config.opencode.apiUrl);
|
|
14
|
+
if (!localTarget) {
|
|
15
|
+
await ctx.reply(t("opencode_stop.remote_configured"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const { data, error } = await opencodeClient.global.health();
|
|
20
|
+
if (error || !data?.healthy) {
|
|
21
|
+
await ctx.reply(t("opencode_stop.not_running"));
|
|
22
|
+
return;
|
|
23
23
|
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
24
26
|
await ctx.reply(t("opencode_stop.not_running"));
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// 3. Stop the process
|
|
31
|
-
const { success, error } = await processManager.stop(5000);
|
|
32
|
-
if (!success) {
|
|
33
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_stop.stop_error", { error: error || t("common.unknown_error") }));
|
|
29
|
+
const pid = await findServerPid(localTarget.port);
|
|
30
|
+
if (!pid) {
|
|
31
|
+
await ctx.reply(t("opencode_stop.pid_not_found", { port: localTarget.port }));
|
|
34
32
|
return;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
await
|
|
38
|
-
|
|
34
|
+
const statusMessage = await ctx.reply(t("opencode_stop.stopping", { pid }));
|
|
35
|
+
const stopped = await killServerProcess(pid, 5000);
|
|
36
|
+
if (!stopped) {
|
|
37
|
+
await editBotText({
|
|
38
|
+
api: ctx.api,
|
|
39
|
+
chatId: ctx.chat.id,
|
|
40
|
+
messageId: statusMessage.message_id,
|
|
41
|
+
text: t("opencode_stop.stop_error", { error: t("common.unknown_error") }),
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const { data, error } = await opencodeClient.global.health();
|
|
47
|
+
if (!error && data?.healthy) {
|
|
48
|
+
await editBotText({
|
|
49
|
+
api: ctx.api,
|
|
50
|
+
chatId: ctx.chat.id,
|
|
51
|
+
messageId: statusMessage.message_id,
|
|
52
|
+
text: t("opencode_stop.stop_error", { error: t("opencode_stop.still_running") }),
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Health check failure after stop is expected.
|
|
59
|
+
}
|
|
60
|
+
await editBotText({
|
|
61
|
+
api: ctx.api,
|
|
62
|
+
chatId: ctx.chat.id,
|
|
63
|
+
messageId: statusMessage.message_id,
|
|
64
|
+
text: t("opencode_stop.success"),
|
|
65
|
+
});
|
|
66
|
+
logger.info(`[Bot] OpenCode server stopped successfully, PID=${pid}, port=${localTarget.port}`);
|
|
39
67
|
}
|
|
40
68
|
catch (err) {
|
|
41
69
|
logger.error("[Bot] Error in /opencode-stop command:", err);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InlineKeyboard } from "grammy";
|
|
2
2
|
import { getCurrentProject } from "../../settings/manager.js";
|
|
3
|
+
import { getGitWorktreeContext } from "../../git/worktree.js";
|
|
3
4
|
import { getProjects } from "../../project/manager.js";
|
|
4
5
|
import { syncSessionDirectoryCache } from "../../session/cache-manager.js";
|
|
5
6
|
import { appendInlineMenuCancelButton, ensureActiveInlineMenu, replyWithInlineMenu, } from "../handlers/inline-menu.js";
|
|
@@ -68,14 +69,37 @@ function buildProjectsMenuText(currentProjectName, page, totalPages) {
|
|
|
68
69
|
total: String(totalPages),
|
|
69
70
|
})}`;
|
|
70
71
|
}
|
|
71
|
-
function
|
|
72
|
+
function worktreeKey(worktree) {
|
|
73
|
+
return process.platform === "win32" ? worktree.toLowerCase() : worktree;
|
|
74
|
+
}
|
|
75
|
+
async function getActiveProjectWorktree() {
|
|
76
|
+
const currentProject = getCurrentProject();
|
|
77
|
+
if (!currentProject) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const worktreeContext = await getGitWorktreeContext(currentProject.worktree);
|
|
82
|
+
if (worktreeContext) {
|
|
83
|
+
return worktreeContext.mainProjectPath;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logger.debug("[Projects] Could not resolve active git worktree metadata:", error);
|
|
88
|
+
}
|
|
89
|
+
return currentProject.worktree;
|
|
90
|
+
}
|
|
91
|
+
async function buildProjectsKeyboard(projects, page) {
|
|
72
92
|
const keyboard = new InlineKeyboard();
|
|
73
93
|
const currentProject = getCurrentProject();
|
|
94
|
+
const activeProjectWorktree = await getActiveProjectWorktree();
|
|
74
95
|
const pageSize = config.bot.projectsListLimit;
|
|
75
96
|
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateProjectsPaginationRange(projects.length, page, pageSize);
|
|
76
97
|
projects.slice(startIndex, endIndex).forEach((project, index) => {
|
|
77
98
|
const isActive = currentProject &&
|
|
78
|
-
(project.id === currentProject.id ||
|
|
99
|
+
(project.id === currentProject.id ||
|
|
100
|
+
project.worktree === currentProject.worktree ||
|
|
101
|
+
(activeProjectWorktree !== null &&
|
|
102
|
+
worktreeKey(project.worktree) === worktreeKey(activeProjectWorktree)));
|
|
79
103
|
const label = buildProjectButtonLabel(startIndex + index, project.worktree);
|
|
80
104
|
const labelWithCheck = formatProjectButtonLabel(label, Boolean(isActive));
|
|
81
105
|
keyboard.text(labelWithCheck, `project:${project.id}`).row();
|
|
@@ -90,14 +114,14 @@ function buildProjectsKeyboard(projects, page) {
|
|
|
90
114
|
}
|
|
91
115
|
return keyboard;
|
|
92
116
|
}
|
|
93
|
-
function buildProjectsMenuView(projects, page) {
|
|
117
|
+
async function buildProjectsMenuView(projects, page) {
|
|
94
118
|
const currentProject = getCurrentProject();
|
|
95
119
|
const pageSize = config.bot.projectsListLimit;
|
|
96
120
|
const { page: normalizedPage, totalPages } = calculateProjectsPaginationRange(projects.length, page, pageSize);
|
|
97
121
|
const currentProjectName = currentProject?.name || currentProject?.worktree || null;
|
|
98
122
|
return {
|
|
99
123
|
text: buildProjectsMenuText(currentProjectName, normalizedPage, totalPages),
|
|
100
|
-
keyboard: buildProjectsKeyboard(projects, normalizedPage),
|
|
124
|
+
keyboard: await buildProjectsKeyboard(projects, normalizedPage),
|
|
101
125
|
};
|
|
102
126
|
}
|
|
103
127
|
export async function projectsCommand(ctx) {
|
|
@@ -112,7 +136,7 @@ export async function projectsCommand(ctx) {
|
|
|
112
136
|
await ctx.reply(t("projects.empty"));
|
|
113
137
|
return;
|
|
114
138
|
}
|
|
115
|
-
const { text, keyboard } = buildProjectsMenuView(projects, 0);
|
|
139
|
+
const { text, keyboard } = await buildProjectsMenuView(projects, 0);
|
|
116
140
|
await replyWithInlineMenu(ctx, {
|
|
117
141
|
menuKind: "project",
|
|
118
142
|
text,
|
|
@@ -146,7 +170,7 @@ export async function handleProjectSelect(ctx) {
|
|
|
146
170
|
await ctx.reply(t("projects.empty"));
|
|
147
171
|
return true;
|
|
148
172
|
}
|
|
149
|
-
const { text, keyboard } = buildProjectsMenuView(projects, page);
|
|
173
|
+
const { text, keyboard } = await buildProjectsMenuView(projects, page);
|
|
150
174
|
await ctx.answerCallbackQuery();
|
|
151
175
|
await ctx.editMessageText(text, {
|
|
152
176
|
reply_markup: appendInlineMenuCancelButton(keyboard, "project"),
|
|
@@ -4,8 +4,6 @@ import { resolveProjectAgent } from "../../agent/manager.js";
|
|
|
4
4
|
import { setCurrentSession } from "../../session/manager.js";
|
|
5
5
|
import { getCurrentProject } from "../../settings/manager.js";
|
|
6
6
|
import { clearAllInteractionState } from "../../interaction/cleanup.js";
|
|
7
|
-
import { summaryAggregator } from "../../summary/aggregator.js";
|
|
8
|
-
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
9
7
|
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
8
|
import { appendInlineMenuCancelButton, ensureActiveInlineMenu, replyWithInlineMenu, } from "../handlers/inline-menu.js";
|
|
11
9
|
import { isForegroundBusy, replyBusyBlocked } from "../utils/busy-guard.js";
|
|
@@ -13,6 +11,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
13
11
|
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
14
12
|
import { config } from "../../config.js";
|
|
15
13
|
import { getDateLocale, t } from "../../i18n/index.js";
|
|
14
|
+
import { attachToSession } from "../../attach/service.js";
|
|
16
15
|
const SESSION_CALLBACK_PREFIX = "session:";
|
|
17
16
|
const SESSION_PAGE_CALLBACK_PREFIX = "session:page:";
|
|
18
17
|
const SESSION_FETCH_EXTRA_COUNT = 1;
|
|
@@ -120,7 +119,7 @@ export async function sessionsCommand(ctx) {
|
|
|
120
119
|
await ctx.reply(t("sessions.fetch_error"));
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
|
-
export async function handleSessionSelect(ctx) {
|
|
122
|
+
export async function handleSessionSelect(ctx, deps) {
|
|
124
123
|
const callbackQuery = ctx.callbackQuery;
|
|
125
124
|
if (!callbackQuery?.data || !callbackQuery.data.startsWith(SESSION_CALLBACK_PREFIX)) {
|
|
126
125
|
return false;
|
|
@@ -182,7 +181,6 @@ export async function handleSessionSelect(ctx) {
|
|
|
182
181
|
directory: currentProject.worktree,
|
|
183
182
|
};
|
|
184
183
|
setCurrentSession(sessionInfo);
|
|
185
|
-
summaryAggregator.clear();
|
|
186
184
|
clearAllInteractionState("session_switched");
|
|
187
185
|
await ctx.answerCallbackQuery();
|
|
188
186
|
let loadingMessageId = null;
|
|
@@ -195,30 +193,31 @@ export async function handleSessionSelect(ctx) {
|
|
|
195
193
|
logger.error("[Sessions] Failed to send loading message:", err);
|
|
196
194
|
}
|
|
197
195
|
}
|
|
198
|
-
// Initialize pinned message manager if not already
|
|
199
|
-
if (!pinnedMessageManager.isInitialized() && ctx.chat) {
|
|
200
|
-
pinnedMessageManager.initialize(ctx.api, ctx.chat.id);
|
|
201
|
-
}
|
|
202
|
-
// Initialize keyboard manager if not already
|
|
203
|
-
if (ctx.chat) {
|
|
204
|
-
keyboardManager.initialize(ctx.api, ctx.chat.id);
|
|
205
|
-
}
|
|
206
196
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
197
|
+
await attachToSession({
|
|
198
|
+
bot: deps.bot,
|
|
199
|
+
chatId: ctx.chat.id,
|
|
200
|
+
session: sessionInfo,
|
|
201
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
202
|
+
});
|
|
212
203
|
}
|
|
213
204
|
catch (err) {
|
|
214
|
-
|
|
205
|
+
if (loadingMessageId) {
|
|
206
|
+
try {
|
|
207
|
+
await ctx.api.deleteMessage(ctx.chat.id, loadingMessageId);
|
|
208
|
+
}
|
|
209
|
+
catch (deleteError) {
|
|
210
|
+
logger.debug("[Sessions] Failed to delete loading message after follow error:", deleteError);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
logger.error("[Sessions] Error following selected session:", err);
|
|
214
|
+
throw err;
|
|
215
215
|
}
|
|
216
216
|
if (ctx.chat) {
|
|
217
217
|
const chatId = ctx.chat.id;
|
|
218
218
|
const currentAgent = await resolveProjectAgent();
|
|
219
219
|
keyboardManager.updateAgent(currentAgent);
|
|
220
|
-
|
|
221
|
-
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
220
|
+
const contextInfo = keyboardManager.getContextInfo();
|
|
222
221
|
if (contextInfo) {
|
|
223
222
|
keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
|
|
224
223
|
}
|