@grinev/opencode-telegram-bot 0.1.0-rc.4 → 0.1.0-rc.8
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 +2 -0
- package/dist/app/start-bot-app.js +15 -1
- package/dist/bot/commands/agent.js +2 -1
- package/dist/bot/commands/definitions.js +16 -15
- package/dist/bot/commands/help.js +2 -5
- package/dist/bot/commands/model.js +2 -1
- package/dist/bot/commands/models.js +7 -6
- package/dist/bot/commands/new.js +4 -3
- package/dist/bot/commands/opencode-start.js +18 -20
- package/dist/bot/commands/opencode-stop.js +7 -9
- package/dist/bot/commands/projects.js +9 -6
- package/dist/bot/commands/sessions.js +15 -13
- package/dist/bot/commands/start.js +2 -9
- package/dist/bot/commands/status.js +21 -17
- package/dist/bot/commands/stop.js +10 -9
- package/dist/bot/handlers/agent.js +6 -5
- package/dist/bot/handlers/context.js +14 -16
- package/dist/bot/handlers/model.js +7 -6
- package/dist/bot/handlers/permission.js +32 -26
- package/dist/bot/handlers/question.js +39 -32
- package/dist/bot/handlers/variant.js +10 -9
- package/dist/bot/index.js +27 -26
- package/dist/bot/utils/keyboard.js +6 -3
- package/dist/cli/args.js +7 -6
- package/dist/cli.js +7 -17
- package/dist/config.js +15 -0
- package/dist/i18n/en.js +205 -0
- package/dist/i18n/index.js +50 -0
- package/dist/i18n/ru.js +205 -0
- package/dist/keyboard/manager.js +2 -1
- package/dist/opencode/events.js +4 -4
- package/dist/pinned/manager.js +16 -11
- package/dist/question/manager.js +1 -1
- package/dist/runtime/bootstrap.js +15 -11
- package/dist/summary/aggregator.js +2 -2
- package/dist/summary/formatter.js +9 -6
- package/package.json +16 -6
package/.env.example
CHANGED
|
@@ -28,6 +28,8 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
28
28
|
# Bot Configuration (optional)
|
|
29
29
|
# Maximum number of sessions shown in /sessions (default: 10)
|
|
30
30
|
# SESSIONS_LIST_LIMIT=10
|
|
31
|
+
# Bot locale: en or ru (default: en)
|
|
32
|
+
# BOT_LOCALE=en
|
|
31
33
|
|
|
32
34
|
# Code File Settings (optional)
|
|
33
35
|
# Maximum file size in KB to send as document (default: 100)
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { createBot } from "../bot/index.js";
|
|
2
3
|
import { config } from "../config.js";
|
|
3
4
|
import { loadSettings } from "../settings/manager.js";
|
|
4
5
|
import { processManager } from "../process/manager.js";
|
|
5
6
|
import { getRuntimeMode } from "../runtime/mode.js";
|
|
6
7
|
import { logger } from "../utils/logger.js";
|
|
8
|
+
async function getBotVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
11
|
+
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
12
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
13
|
+
return packageJson.version ?? "unknown";
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
logger.warn("[App] Failed to read bot version", error);
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
export async function startBotApp() {
|
|
8
21
|
const mode = getRuntimeMode();
|
|
9
|
-
|
|
22
|
+
const version = await getBotVersion();
|
|
23
|
+
logger.info(`Starting OpenCode Telegram Bot v${version}...`);
|
|
10
24
|
logger.info(`Allowed User ID: ${config.telegram.allowedUserId}`);
|
|
11
25
|
logger.debug(`[Runtime] Application start mode: ${mode}`);
|
|
12
26
|
await loadSettings();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { showAgentSelectionMenu } from "../handlers/agent.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Handler for /agent command
|
|
5
6
|
* Shows inline menu to select agent mode
|
|
@@ -11,6 +12,6 @@ export async function handleAgentCommand(ctx) {
|
|
|
11
12
|
}
|
|
12
13
|
catch (err) {
|
|
13
14
|
logger.error("[AgentCommand] Error showing agent menu:", err);
|
|
14
|
-
await ctx.reply("
|
|
15
|
+
await ctx.reply(t("error.load_agents"));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
* Centralized bot commands definitions
|
|
3
|
-
* Used for both Telegram API setMyCommands and command handler registration
|
|
4
|
-
*/
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
5
2
|
/**
|
|
6
3
|
* List of all bot commands
|
|
7
4
|
* Update this array when adding new commands
|
|
8
5
|
*/
|
|
9
|
-
|
|
10
|
-
{ command: "status",
|
|
11
|
-
{ command: "new",
|
|
12
|
-
{ command: "stop",
|
|
13
|
-
{ command: "sessions",
|
|
14
|
-
{ command: "projects",
|
|
15
|
-
{ command: "model",
|
|
16
|
-
{ command: "agent",
|
|
17
|
-
{ command: "opencode_start",
|
|
18
|
-
{ command: "opencode_stop",
|
|
19
|
-
{ command: "help",
|
|
6
|
+
const COMMAND_DEFINITIONS = [
|
|
7
|
+
{ command: "status", descriptionKey: "cmd.description.status" },
|
|
8
|
+
{ command: "new", descriptionKey: "cmd.description.new" },
|
|
9
|
+
{ command: "stop", descriptionKey: "cmd.description.stop" },
|
|
10
|
+
{ command: "sessions", descriptionKey: "cmd.description.sessions" },
|
|
11
|
+
{ command: "projects", descriptionKey: "cmd.description.projects" },
|
|
12
|
+
{ command: "model", descriptionKey: "cmd.description.model" },
|
|
13
|
+
{ command: "agent", descriptionKey: "cmd.description.agent" },
|
|
14
|
+
{ command: "opencode_start", descriptionKey: "cmd.description.opencode_start" },
|
|
15
|
+
{ command: "opencode_stop", descriptionKey: "cmd.description.opencode_stop" },
|
|
16
|
+
{ command: "help", descriptionKey: "cmd.description.help" },
|
|
20
17
|
];
|
|
18
|
+
export const BOT_COMMANDS = COMMAND_DEFINITIONS.map(({ command, descriptionKey }) => ({
|
|
19
|
+
command,
|
|
20
|
+
description: t(descriptionKey),
|
|
21
|
+
}));
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
1
2
|
export async function helpCommand(ctx) {
|
|
2
|
-
await ctx.reply("
|
|
3
|
-
"/status - Проверить статус сервера\n" +
|
|
4
|
-
"/sessions - Список сессий\n" +
|
|
5
|
-
"/new - Создать новую сессию\n" +
|
|
6
|
-
"/help - Справка", { parse_mode: "Markdown" });
|
|
3
|
+
await ctx.reply(t("help.text"), { parse_mode: "Markdown" });
|
|
7
4
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { showModelSelectionMenu } from "../handlers/model.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Handler for /model command
|
|
5
6
|
* Shows inline menu to select model from favorites
|
|
@@ -11,6 +12,6 @@ export async function handleModelCommand(ctx) {
|
|
|
11
12
|
}
|
|
12
13
|
catch (err) {
|
|
13
14
|
logger.error("[ModelCommand] Error showing model menu:", err);
|
|
14
|
-
await ctx.reply("
|
|
15
|
+
await ctx.reply(t("error.load_models"));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
export async function modelsCommand(ctx) {
|
|
4
5
|
try {
|
|
5
6
|
const { data: providersData, error } = await opencodeClient.config.providers();
|
|
6
7
|
if (error || !providersData) {
|
|
7
|
-
await ctx.reply("
|
|
8
|
+
await ctx.reply(t("legacy.models.fetch_error"));
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
const providers = providersData.providers;
|
|
11
12
|
if (!providers || providers.length === 0) {
|
|
12
|
-
await ctx.reply("
|
|
13
|
+
await ctx.reply(t("legacy.models.empty"));
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
let message = "
|
|
16
|
+
let message = t("legacy.models.header");
|
|
16
17
|
for (const provider of providers) {
|
|
17
18
|
message += `🔹 **\`${provider.id}\`**\n`;
|
|
18
19
|
const models = Object.values(provider.models);
|
|
19
20
|
if (models.length === 0) {
|
|
20
|
-
message += "
|
|
21
|
+
message += t("legacy.models.no_provider_models");
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
24
|
for (const model of models) {
|
|
@@ -26,12 +27,12 @@ export async function modelsCommand(ctx) {
|
|
|
26
27
|
}
|
|
27
28
|
message += "\n";
|
|
28
29
|
}
|
|
29
|
-
message += "
|
|
30
|
+
message += t("legacy.models.env_hint");
|
|
30
31
|
message += "```\nOPENCODE_MODEL_PROVIDER=<provider.id>\nOPENCODE_MODEL_ID=<model.id>\n```";
|
|
31
32
|
await ctx.reply(message, { parse_mode: "Markdown" });
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
35
|
logger.error("[ModelsCommand] Error listing models:", error);
|
|
35
|
-
await ctx.reply("
|
|
36
|
+
await ctx.reply(t("legacy.models.error"));
|
|
36
37
|
}
|
|
37
38
|
}
|
package/dist/bot/commands/new.js
CHANGED
|
@@ -8,11 +8,12 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
8
8
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
9
9
|
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
|
+
import { t } from "../../i18n/index.js";
|
|
11
12
|
export async function newCommand(ctx) {
|
|
12
13
|
try {
|
|
13
14
|
const currentProject = getCurrentProject();
|
|
14
15
|
if (!currentProject) {
|
|
15
|
-
await ctx.reply("
|
|
16
|
+
await ctx.reply(t("new.project_not_selected"));
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
logger.debug("[Bot] Creating new session for directory:", currentProject.worktree);
|
|
@@ -47,12 +48,12 @@ export async function newCommand(ctx) {
|
|
|
47
48
|
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
48
49
|
const variantName = formatVariantForButton(currentModel.variant || "default");
|
|
49
50
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo ?? undefined, variantName);
|
|
50
|
-
await ctx.reply(
|
|
51
|
+
await ctx.reply(t("new.created", { title: session.title }), {
|
|
51
52
|
reply_markup: keyboard,
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
56
|
logger.error("[Bot] Error creating session:", error);
|
|
56
|
-
await ctx.reply("
|
|
57
|
+
await ctx.reply(t("new.create_error"));
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { processManager } from "../../process/manager.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Wait for OpenCode server to become ready by polling health endpoint
|
|
6
7
|
* @param maxWaitMs Maximum time to wait in milliseconds
|
|
@@ -33,18 +34,19 @@ export async function opencodeStartCommand(ctx) {
|
|
|
33
34
|
if (processManager.isRunning()) {
|
|
34
35
|
const uptime = processManager.getUptime();
|
|
35
36
|
const uptimeStr = uptime ? Math.floor(uptime / 1000) : 0;
|
|
36
|
-
await ctx.reply(
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
await ctx.reply(t("opencode_start.already_running_managed", {
|
|
38
|
+
pid: processManager.getPID() ?? "-",
|
|
39
|
+
seconds: uptimeStr,
|
|
40
|
+
}));
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
// 2. Check if server is accessible (external process)
|
|
42
44
|
try {
|
|
43
45
|
const { data, error } = await opencodeClient.global.health();
|
|
44
46
|
if (!error && data?.healthy) {
|
|
45
|
-
await ctx.reply(
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
await ctx.reply(t("opencode_start.already_running_external", {
|
|
48
|
+
version: data.version || t("common.unknown"),
|
|
49
|
+
}));
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -52,36 +54,32 @@ export async function opencodeStartCommand(ctx) {
|
|
|
52
54
|
// Server not accessible, continue with start
|
|
53
55
|
}
|
|
54
56
|
// 3. Notify user that we're starting the server
|
|
55
|
-
const statusMessage = await ctx.reply("
|
|
57
|
+
const statusMessage = await ctx.reply(t("opencode_start.starting"));
|
|
56
58
|
// 4. Start the process
|
|
57
59
|
const { success, error } = await processManager.start();
|
|
58
60
|
if (!success) {
|
|
59
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
60
|
-
`Ошибка: ${error || "неизвестная ошибка"}\n\n` +
|
|
61
|
-
`Проверьте, что OpenCode CLI установлен и доступен в PATH:\n` +
|
|
62
|
-
`\`opencode --version\`\n` +
|
|
63
|
-
`\`npm install -g @opencode-ai/cli\``, { parse_mode: "Markdown" });
|
|
61
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.start_error", { error: error || t("common.unknown_error") }), { parse_mode: "Markdown" });
|
|
64
62
|
return;
|
|
65
63
|
}
|
|
66
64
|
// 5. Wait for server to become ready
|
|
67
65
|
logger.info("[Bot] Waiting for OpenCode server to become ready...");
|
|
68
66
|
const ready = await waitForServerReady(10000);
|
|
69
67
|
if (!ready) {
|
|
70
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.started_not_ready", {
|
|
69
|
+
pid: processManager.getPID() ?? "-",
|
|
70
|
+
}));
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
75
73
|
// 6. Get server version and send success message
|
|
76
74
|
const { data: health } = await opencodeClient.global.health();
|
|
77
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.success", {
|
|
76
|
+
pid: processManager.getPID() ?? "-",
|
|
77
|
+
version: health?.version || t("common.unknown"),
|
|
78
|
+
}));
|
|
80
79
|
logger.info(`[Bot] OpenCode server started successfully, PID=${processManager.getPID()}`);
|
|
81
80
|
}
|
|
82
81
|
catch (err) {
|
|
83
82
|
logger.error("[Bot] Error in /opencode-start command:", err);
|
|
84
|
-
await ctx.reply("
|
|
85
|
-
"Проверьте логи приложения для подробностей.");
|
|
83
|
+
await ctx.reply(t("opencode_start.error"));
|
|
86
84
|
}
|
|
87
85
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { processManager } from "../../process/manager.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Command handler for /opencode-stop
|
|
6
7
|
* Stops the OpenCode server process
|
|
@@ -13,34 +14,31 @@ export async function opencodeStopCommand(ctx) {
|
|
|
13
14
|
try {
|
|
14
15
|
const { data, error } = await opencodeClient.global.health();
|
|
15
16
|
if (!error && data?.healthy) {
|
|
16
|
-
await ctx.reply(
|
|
17
|
-
`Этот сервер не был запущен через /opencode-start.\n` +
|
|
18
|
-
`Остановите его вручную или используйте /status для проверки состояния.`);
|
|
17
|
+
await ctx.reply(t("opencode_stop.external_running"));
|
|
19
18
|
return;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
catch {
|
|
23
22
|
// Server not accessible
|
|
24
23
|
}
|
|
25
|
-
await ctx.reply("
|
|
24
|
+
await ctx.reply(t("opencode_stop.not_running"));
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
27
|
// 2. Notify user that we're stopping the server
|
|
29
28
|
const pid = processManager.getPID();
|
|
30
|
-
const statusMessage = await ctx.reply(
|
|
29
|
+
const statusMessage = await ctx.reply(t("opencode_stop.stopping", { pid: pid ?? "-" }));
|
|
31
30
|
// 3. Stop the process
|
|
32
31
|
const { success, error } = await processManager.stop(5000);
|
|
33
32
|
if (!success) {
|
|
34
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
33
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_stop.stop_error", { error: error || t("common.unknown_error") }));
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
36
|
// 4. Success - process has been stopped
|
|
38
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
37
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_stop.success"));
|
|
39
38
|
logger.info("[Bot] OpenCode server stopped successfully");
|
|
40
39
|
}
|
|
41
40
|
catch (err) {
|
|
42
41
|
logger.error("[Bot] Error in /opencode-stop command:", err);
|
|
43
|
-
await ctx.reply("
|
|
44
|
-
"Проверьте логи приложения для подробностей.");
|
|
42
|
+
await ctx.reply(t("opencode_stop.error"));
|
|
45
43
|
}
|
|
46
44
|
}
|
|
@@ -10,6 +10,7 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
10
10
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
11
11
|
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
12
12
|
import { logger } from "../../utils/logger.js";
|
|
13
|
+
import { t } from "../../i18n/index.js";
|
|
13
14
|
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
14
15
|
function formatProjectButtonLabel(label, isActive) {
|
|
15
16
|
const prefix = isActive ? "✅ " : "";
|
|
@@ -23,7 +24,7 @@ export async function projectsCommand(ctx) {
|
|
|
23
24
|
try {
|
|
24
25
|
const projects = await getProjects();
|
|
25
26
|
if (projects.length === 0) {
|
|
26
|
-
await ctx.reply("
|
|
27
|
+
await ctx.reply(t("projects.empty"));
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
const keyboard = new InlineKeyboard();
|
|
@@ -39,15 +40,17 @@ export async function projectsCommand(ctx) {
|
|
|
39
40
|
});
|
|
40
41
|
if (currentProject) {
|
|
41
42
|
const projectName = currentProject.name || currentProject.worktree;
|
|
42
|
-
await ctx.reply(
|
|
43
|
+
await ctx.reply(t("projects.select_with_current", { project: projectName }), {
|
|
44
|
+
reply_markup: keyboard,
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
|
-
await ctx.reply("
|
|
48
|
+
await ctx.reply(t("projects.select"), { reply_markup: keyboard });
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
catch (error) {
|
|
49
52
|
logger.error("[Bot] Error fetching projects:", error);
|
|
50
|
-
await ctx.reply("
|
|
53
|
+
await ctx.reply(t("projects.fetch_error"));
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
export async function handleProjectSelect(ctx) {
|
|
@@ -90,7 +93,7 @@ export async function handleProjectSelect(ctx) {
|
|
|
90
93
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo, variantName);
|
|
91
94
|
const projectName = selectedProject.name || selectedProject.worktree;
|
|
92
95
|
await ctx.answerCallbackQuery();
|
|
93
|
-
await ctx.reply(
|
|
96
|
+
await ctx.reply(t("projects.selected", { project: projectName }), {
|
|
94
97
|
reply_markup: keyboard,
|
|
95
98
|
});
|
|
96
99
|
await ctx.deleteMessage();
|
|
@@ -98,7 +101,7 @@ export async function handleProjectSelect(ctx) {
|
|
|
98
101
|
catch (error) {
|
|
99
102
|
logger.error("[Bot] Error selecting project:", error);
|
|
100
103
|
await ctx.answerCallbackQuery();
|
|
101
|
-
await ctx.reply("
|
|
104
|
+
await ctx.reply(t("projects.select_error"));
|
|
102
105
|
}
|
|
103
106
|
return true;
|
|
104
107
|
}
|
|
@@ -7,12 +7,13 @@ import { keyboardManager } from "../../keyboard/manager.js";
|
|
|
7
7
|
import { logger } from "../../utils/logger.js";
|
|
8
8
|
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
9
9
|
import { config } from "../../config.js";
|
|
10
|
+
import { getLocale, t } from "../../i18n/index.js";
|
|
10
11
|
export async function sessionsCommand(ctx) {
|
|
11
12
|
try {
|
|
12
13
|
const maxSessions = config.bot.sessionsListLimit;
|
|
13
14
|
const currentProject = getCurrentProject();
|
|
14
15
|
if (!currentProject) {
|
|
15
|
-
await ctx.reply("
|
|
16
|
+
await ctx.reply(t("sessions.project_not_selected"));
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
logger.debug(`[Sessions] Fetching sessions for directory: ${currentProject.worktree}`);
|
|
@@ -28,22 +29,23 @@ export async function sessionsCommand(ctx) {
|
|
|
28
29
|
logger.debug(`[Sessions] Session: ${session.title} | ${session.directory}`);
|
|
29
30
|
});
|
|
30
31
|
if (sessions.length === 0) {
|
|
31
|
-
await ctx.reply("
|
|
32
|
+
await ctx.reply(t("sessions.empty"));
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
const keyboard = new InlineKeyboard();
|
|
36
|
+
const localeForDate = getLocale() === "ru" ? "ru-RU" : "en-US";
|
|
35
37
|
sessions.forEach((session, index) => {
|
|
36
|
-
const date = new Date(session.time.created).toLocaleDateString(
|
|
38
|
+
const date = new Date(session.time.created).toLocaleDateString(localeForDate);
|
|
37
39
|
const label = `${index + 1}. ${session.title} (${date})`;
|
|
38
40
|
keyboard.text(label, `session:${session.id}`).row();
|
|
39
41
|
});
|
|
40
|
-
await ctx.reply("
|
|
42
|
+
await ctx.reply(t("sessions.select"), {
|
|
41
43
|
reply_markup: keyboard,
|
|
42
44
|
});
|
|
43
45
|
}
|
|
44
46
|
catch (error) {
|
|
45
47
|
logger.error("[Sessions] Error fetching sessions:", error);
|
|
46
|
-
await ctx.reply("
|
|
48
|
+
await ctx.reply(t("sessions.fetch_error"));
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
export async function handleSessionSelect(ctx) {
|
|
@@ -56,7 +58,7 @@ export async function handleSessionSelect(ctx) {
|
|
|
56
58
|
const currentProject = getCurrentProject();
|
|
57
59
|
if (!currentProject) {
|
|
58
60
|
await ctx.answerCallbackQuery();
|
|
59
|
-
await ctx.reply("
|
|
61
|
+
await ctx.reply(t("sessions.select_project_first"));
|
|
60
62
|
return true;
|
|
61
63
|
}
|
|
62
64
|
const { data: session, error } = await opencodeClient.session.get({
|
|
@@ -77,7 +79,7 @@ export async function handleSessionSelect(ctx) {
|
|
|
77
79
|
let loadingMessageId = null;
|
|
78
80
|
if (ctx.chat) {
|
|
79
81
|
try {
|
|
80
|
-
const loadingMessage = await ctx.api.sendMessage(ctx.chat.id,
|
|
82
|
+
const loadingMessage = await ctx.api.sendMessage(ctx.chat.id, t("sessions.loading_context"));
|
|
81
83
|
loadingMessageId = loadingMessage.message_id;
|
|
82
84
|
}
|
|
83
85
|
catch (err) {
|
|
@@ -121,7 +123,7 @@ export async function handleSessionSelect(ctx) {
|
|
|
121
123
|
// Send session selection confirmation with updated keyboard
|
|
122
124
|
const keyboard = keyboardManager.getKeyboard();
|
|
123
125
|
try {
|
|
124
|
-
await ctx.api.sendMessage(chatId,
|
|
126
|
+
await ctx.api.sendMessage(chatId, t("sessions.selected", { title: session.title }), {
|
|
125
127
|
reply_markup: keyboard,
|
|
126
128
|
});
|
|
127
129
|
}
|
|
@@ -139,7 +141,7 @@ export async function handleSessionSelect(ctx) {
|
|
|
139
141
|
catch (error) {
|
|
140
142
|
logger.error("[Sessions] Error selecting session:", error);
|
|
141
143
|
await ctx.answerCallbackQuery();
|
|
142
|
-
await ctx.reply("
|
|
144
|
+
await ctx.reply(t("sessions.select_error"));
|
|
143
145
|
}
|
|
144
146
|
return true;
|
|
145
147
|
}
|
|
@@ -202,15 +204,15 @@ async function loadSessionPreview(sessionId, directory) {
|
|
|
202
204
|
return [];
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
|
-
function formatSessionPreview(
|
|
207
|
+
function formatSessionPreview(_sessionTitle, items) {
|
|
206
208
|
const lines = [];
|
|
207
209
|
if (items.length === 0) {
|
|
208
|
-
lines.push("
|
|
210
|
+
lines.push(t("sessions.preview.empty"));
|
|
209
211
|
return lines.join("\n");
|
|
210
212
|
}
|
|
211
|
-
lines.push("
|
|
213
|
+
lines.push(t("sessions.preview.title"));
|
|
212
214
|
items.forEach((item, index) => {
|
|
213
|
-
const label = item.role === "user" ? "
|
|
215
|
+
const label = item.role === "user" ? t("sessions.preview.you") : t("sessions.preview.agent");
|
|
214
216
|
lines.push(`${label} ${item.text}`);
|
|
215
217
|
if (index < items.length - 1) {
|
|
216
218
|
lines.push("");
|
|
@@ -4,6 +4,7 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
4
4
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
5
5
|
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
6
6
|
import { keyboardManager } from "../../keyboard/manager.js";
|
|
7
|
+
import { t } from "../../i18n/index.js";
|
|
7
8
|
export async function startCommand(ctx) {
|
|
8
9
|
if (ctx.chat) {
|
|
9
10
|
if (!pinnedMessageManager.isInitialized()) {
|
|
@@ -28,13 +29,5 @@ export async function startCommand(ctx) {
|
|
|
28
29
|
keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
|
|
29
30
|
}
|
|
30
31
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo ?? undefined, variantName);
|
|
31
|
-
await ctx.reply("
|
|
32
|
-
"Используйте команды:\n" +
|
|
33
|
-
"/projects — выбрать проект\n" +
|
|
34
|
-
"/sessions — список сессий\n" +
|
|
35
|
-
"/new — новая сессия\n" +
|
|
36
|
-
"/agent — сменить режим\n" +
|
|
37
|
-
"/model — выбрать модель\n" +
|
|
38
|
-
"/status — статус\n" +
|
|
39
|
-
"/help — справка", { reply_markup: keyboard });
|
|
32
|
+
await ctx.reply(t("start.welcome"), { reply_markup: keyboard });
|
|
40
33
|
}
|
|
@@ -7,57 +7,61 @@ import { fetchCurrentModel } from "../../model/manager.js";
|
|
|
7
7
|
import { formatModelForDisplay } from "../../model/types.js";
|
|
8
8
|
import { processManager } from "../../process/manager.js";
|
|
9
9
|
import { logger } from "../../utils/logger.js";
|
|
10
|
+
import { t } from "../../i18n/index.js";
|
|
10
11
|
export async function statusCommand(ctx) {
|
|
11
12
|
try {
|
|
12
13
|
const { data, error } = await opencodeClient.global.health();
|
|
13
14
|
if (error || !data) {
|
|
14
15
|
throw error || new Error("No data received from server");
|
|
15
16
|
}
|
|
16
|
-
let message = "
|
|
17
|
-
|
|
17
|
+
let message = `${t("status.header_running")}\n\n`;
|
|
18
|
+
const healthLabel = data.healthy ? t("status.health.healthy") : t("status.health.unhealthy");
|
|
19
|
+
message += `${t("status.line.health", { health: healthLabel })}\n`;
|
|
18
20
|
if (data.version) {
|
|
19
|
-
message +=
|
|
21
|
+
message += `${t("status.line.version", { version: data.version })}\n`;
|
|
20
22
|
}
|
|
21
23
|
// Add process management information
|
|
22
24
|
if (processManager.isRunning()) {
|
|
23
25
|
const uptime = processManager.getUptime();
|
|
24
26
|
const uptimeStr = uptime ? Math.floor(uptime / 1000) : 0;
|
|
25
|
-
message +=
|
|
26
|
-
message +=
|
|
27
|
-
message +=
|
|
27
|
+
message += `${t("status.line.managed_yes")}\n`;
|
|
28
|
+
message += `${t("status.line.pid", { pid: processManager.getPID() ?? "-" })}\n`;
|
|
29
|
+
message += `${t("status.line.uptime_sec", { seconds: uptimeStr })}\n`;
|
|
28
30
|
}
|
|
29
31
|
else {
|
|
30
|
-
message +=
|
|
32
|
+
message += `${t("status.line.managed_no")}\n`;
|
|
31
33
|
}
|
|
32
34
|
// Add agent mode information
|
|
33
35
|
const currentAgent = await fetchCurrentAgent();
|
|
34
|
-
const agentDisplay = currentAgent
|
|
35
|
-
|
|
36
|
+
const agentDisplay = currentAgent
|
|
37
|
+
? getAgentDisplayName(currentAgent)
|
|
38
|
+
: t("status.agent_not_set");
|
|
39
|
+
message += `${t("status.line.mode", { mode: agentDisplay })}\n`;
|
|
36
40
|
// Add model information
|
|
37
41
|
const currentModel = fetchCurrentModel();
|
|
38
42
|
const modelDisplay = formatModelForDisplay(currentModel.providerID, currentModel.modelID);
|
|
39
|
-
message +=
|
|
43
|
+
message += `${t("status.line.model", { model: modelDisplay })}\n`;
|
|
40
44
|
const currentProject = getCurrentProject();
|
|
41
45
|
if (currentProject) {
|
|
42
46
|
const projectName = currentProject.name || currentProject.worktree;
|
|
43
|
-
message += `\n
|
|
47
|
+
message += `\n${t("status.project_selected", { project: projectName })}\n`;
|
|
44
48
|
}
|
|
45
49
|
else {
|
|
46
|
-
message += "\n
|
|
47
|
-
message += "
|
|
50
|
+
message += `\n${t("status.project_not_selected")}\n`;
|
|
51
|
+
message += t("status.project_hint");
|
|
48
52
|
}
|
|
49
53
|
const currentSession = getCurrentSession();
|
|
50
54
|
if (currentSession) {
|
|
51
|
-
message += `\n
|
|
55
|
+
message += `\n${t("status.session_selected", { title: currentSession.title })}\n`;
|
|
52
56
|
}
|
|
53
57
|
else {
|
|
54
|
-
message += "\n
|
|
55
|
-
message += "
|
|
58
|
+
message += `\n${t("status.session_not_selected")}\n`;
|
|
59
|
+
message += t("status.session_hint");
|
|
56
60
|
}
|
|
57
61
|
await ctx.reply(message, { parse_mode: "Markdown" });
|
|
58
62
|
}
|
|
59
63
|
catch (error) {
|
|
60
64
|
logger.error("[Bot] Error checking server status:", error);
|
|
61
|
-
await ctx.reply("
|
|
65
|
+
await ctx.reply(t("status.server_unavailable"));
|
|
62
66
|
}
|
|
63
67
|
}
|