@grinev/opencode-telegram-bot 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/app/start-bot-app.js +5 -0
- package/dist/bot/commands/{stop.js → abort.js} +44 -16
- package/dist/bot/commands/definitions.js +1 -1
- package/dist/bot/commands/start.js +8 -0
- package/dist/bot/commands/status.js +1 -2
- package/dist/bot/index.js +5 -5
- package/dist/config.js +1 -1
- package/dist/i18n/de.js +13 -11
- package/dist/i18n/en.js +13 -11
- package/dist/i18n/es.js +13 -11
- package/dist/i18n/ru.js +20 -18
- package/dist/i18n/zh.js +13 -11
- package/dist/interaction/guard.js +3 -0
- package/dist/interaction/manager.js +1 -1
- package/dist/model/manager.js +110 -4
- package/dist/pinned/manager.js +1 -1
- package/dist/runtime/bootstrap.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ Languages: English (`en`), Deutsch (`de`), Español (`es`), Русский (`ru`
|
|
|
35
35
|
- **Security** — strict user ID whitelist; no one else can access your bot, even if they find it
|
|
36
36
|
- **Localization** — UI localization is supported for multiple languages (`BOT_LOCALE`)
|
|
37
37
|
|
|
38
|
+
Planned features currently in development are listed in [Current Task List](PRODUCT.md#current-task-list).
|
|
39
|
+
|
|
38
40
|
## Prerequisites
|
|
39
41
|
|
|
40
42
|
- **Node.js 20+** — [download](https://nodejs.org)
|
|
@@ -71,7 +73,7 @@ npx @grinev/opencode-telegram-bot
|
|
|
71
73
|
|
|
72
74
|
> Quick start is for npm usage. You do not need to clone this repository. If you run this command from the source directory (repository root), it may fail with `opencode-telegram: not found`. To run from sources, use the [Development](#development) section.
|
|
73
75
|
|
|
74
|
-
On first launch, an interactive wizard will guide you through the configuration — it asks for interface language first, then your bot token, user ID, and OpenCode
|
|
76
|
+
On first launch, an interactive wizard will guide you through the configuration — it asks for interface language first, then your bot token, user ID, OpenCode API URL, and optional OpenCode server credentials (username/password). After that, you're ready to go. Open your bot in Telegram and start sending tasks.
|
|
75
77
|
|
|
76
78
|
#### Alternative: Global Install
|
|
77
79
|
|
|
@@ -100,7 +102,7 @@ opencode-telegram config
|
|
|
100
102
|
| ----------------- | ------------------------------------------------------- |
|
|
101
103
|
| `/status` | Server health, current project, session, and model info |
|
|
102
104
|
| `/new` | Create a new session |
|
|
103
|
-
| `/
|
|
105
|
+
| `/abort` | Abort the current task |
|
|
104
106
|
| `/sessions` | Browse and switch between recent sessions |
|
|
105
107
|
| `/projects` | Switch between OpenCode projects |
|
|
106
108
|
| `/rename` | Rename the current session |
|
|
@@ -4,7 +4,9 @@ import { config } from "../config.js";
|
|
|
4
4
|
import { loadSettings } from "../settings/manager.js";
|
|
5
5
|
import { processManager } from "../process/manager.js";
|
|
6
6
|
import { warmupSessionDirectoryCache } from "../session/cache-manager.js";
|
|
7
|
+
import { reconcileStoredModelSelection } from "../model/manager.js";
|
|
7
8
|
import { getRuntimeMode } from "../runtime/mode.js";
|
|
9
|
+
import { getRuntimePaths } from "../runtime/paths.js";
|
|
8
10
|
import { logger } from "../utils/logger.js";
|
|
9
11
|
async function getBotVersion() {
|
|
10
12
|
try {
|
|
@@ -20,12 +22,15 @@ async function getBotVersion() {
|
|
|
20
22
|
}
|
|
21
23
|
export async function startBotApp() {
|
|
22
24
|
const mode = getRuntimeMode();
|
|
25
|
+
const runtimePaths = getRuntimePaths();
|
|
23
26
|
const version = await getBotVersion();
|
|
24
27
|
logger.info(`Starting OpenCode Telegram Bot v${version}...`);
|
|
28
|
+
logger.info(`Config loaded from ${runtimePaths.envFilePath}`);
|
|
25
29
|
logger.info(`Allowed User ID: ${config.telegram.allowedUserId}`);
|
|
26
30
|
logger.debug(`[Runtime] Application start mode: ${mode}`);
|
|
27
31
|
await loadSettings();
|
|
28
32
|
await processManager.initialize();
|
|
33
|
+
await reconcileStoredModelSelection();
|
|
29
34
|
await warmupSessionDirectoryCache();
|
|
30
35
|
const bot = createBot();
|
|
31
36
|
const webhookInfo = await bot.api.getWebhookInfo();
|
|
@@ -6,10 +6,10 @@ import { summaryAggregator } from "../../summary/aggregator.js";
|
|
|
6
6
|
import { logger } from "../../utils/logger.js";
|
|
7
7
|
import { t } from "../../i18n/index.js";
|
|
8
8
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
-
function
|
|
9
|
+
function abortLocalStreaming() {
|
|
10
10
|
stopEventListening();
|
|
11
11
|
summaryAggregator.clear();
|
|
12
|
-
clearAllInteractionState("
|
|
12
|
+
clearAllInteractionState("abort_command");
|
|
13
13
|
}
|
|
14
14
|
async function pollSessionStatus(sessionId, directory, maxWaitMs = 5000) {
|
|
15
15
|
const startedAt = Date.now();
|
|
@@ -33,21 +33,34 @@ async function pollSessionStatus(sessionId, directory, maxWaitMs = 5000) {
|
|
|
33
33
|
await sleep(pollIntervalMs);
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
-
logger.warn("[
|
|
36
|
+
logger.warn("[Abort] Failed to poll session status:", error);
|
|
37
37
|
break;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
return "busy";
|
|
41
41
|
}
|
|
42
|
-
export async function
|
|
42
|
+
export async function abortCurrentOperation(ctx, options = {}) {
|
|
43
|
+
const notifyUser = options.notifyUser ?? true;
|
|
43
44
|
try {
|
|
44
|
-
|
|
45
|
+
abortLocalStreaming();
|
|
45
46
|
const currentSession = getCurrentSession();
|
|
46
47
|
if (!currentSession) {
|
|
47
|
-
|
|
48
|
+
if (notifyUser) {
|
|
49
|
+
await ctx.reply(t("stop.no_active_session"));
|
|
50
|
+
}
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
50
|
-
|
|
53
|
+
let waitingMessageId = null;
|
|
54
|
+
let chatId = null;
|
|
55
|
+
if (notifyUser) {
|
|
56
|
+
const waitingMessage = await ctx.reply(t("stop.in_progress"));
|
|
57
|
+
waitingMessageId = waitingMessage.message_id;
|
|
58
|
+
chatId = ctx.chat?.id ?? null;
|
|
59
|
+
if (!chatId) {
|
|
60
|
+
logger.warn("[Abort] Chat context is missing while aborting active session");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
const controller = new AbortController();
|
|
52
65
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
53
66
|
try {
|
|
@@ -57,35 +70,50 @@ export async function stopCommand(ctx) {
|
|
|
57
70
|
}, { signal: controller.signal });
|
|
58
71
|
clearTimeout(timeoutId);
|
|
59
72
|
if (abortError) {
|
|
60
|
-
logger.warn("[
|
|
61
|
-
|
|
73
|
+
logger.warn("[Abort] Abort request failed:", abortError);
|
|
74
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
75
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.warn_unconfirmed"));
|
|
76
|
+
}
|
|
62
77
|
return;
|
|
63
78
|
}
|
|
64
79
|
if (abortResult !== true) {
|
|
65
|
-
|
|
80
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
81
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.warn_maybe_finished"));
|
|
82
|
+
}
|
|
66
83
|
return;
|
|
67
84
|
}
|
|
68
85
|
const finalStatus = await pollSessionStatus(currentSession.id, currentSession.directory, 5000);
|
|
69
86
|
if (finalStatus === "idle" || finalStatus === "not-found") {
|
|
70
|
-
|
|
87
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
88
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.success"));
|
|
89
|
+
}
|
|
71
90
|
}
|
|
72
91
|
else {
|
|
73
|
-
|
|
92
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
93
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.warn_still_busy"));
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
catch (error) {
|
|
77
98
|
clearTimeout(timeoutId);
|
|
78
99
|
if (error instanceof Error && error.name === "AbortError") {
|
|
79
|
-
|
|
100
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
101
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.warn_timeout"));
|
|
102
|
+
}
|
|
80
103
|
}
|
|
81
104
|
else {
|
|
82
|
-
logger.error("[
|
|
83
|
-
|
|
105
|
+
logger.error("[Abort] Error while aborting session:", error);
|
|
106
|
+
if (notifyUser && chatId !== null && waitingMessageId !== null) {
|
|
107
|
+
await ctx.api.editMessageText(chatId, waitingMessageId, t("stop.warn_local_only"));
|
|
108
|
+
}
|
|
84
109
|
}
|
|
85
110
|
}
|
|
86
111
|
}
|
|
87
112
|
catch (error) {
|
|
88
|
-
logger.error("[
|
|
113
|
+
logger.error("[Abort] Unexpected error:", error);
|
|
89
114
|
await ctx.reply(t("stop.error"));
|
|
90
115
|
}
|
|
91
116
|
}
|
|
117
|
+
export async function abortCommand(ctx) {
|
|
118
|
+
await abortCurrentOperation(ctx);
|
|
119
|
+
}
|
|
@@ -6,7 +6,7 @@ import { t } from "../../i18n/index.js";
|
|
|
6
6
|
const COMMAND_DEFINITIONS = [
|
|
7
7
|
{ command: "status", descriptionKey: "cmd.description.status" },
|
|
8
8
|
{ command: "new", descriptionKey: "cmd.description.new" },
|
|
9
|
-
{ command: "
|
|
9
|
+
{ command: "abort", descriptionKey: "cmd.description.stop" },
|
|
10
10
|
{ command: "sessions", descriptionKey: "cmd.description.sessions" },
|
|
11
11
|
{ command: "projects", descriptionKey: "cmd.description.projects" },
|
|
12
12
|
{ command: "rename", descriptionKey: "cmd.description.rename" },
|
|
@@ -4,6 +4,9 @@ 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 { clearSession } from "../../session/manager.js";
|
|
8
|
+
import { clearProject } from "../../settings/manager.js";
|
|
9
|
+
import { abortCurrentOperation } from "./abort.js";
|
|
7
10
|
import { t } from "../../i18n/index.js";
|
|
8
11
|
export async function startCommand(ctx) {
|
|
9
12
|
if (ctx.chat) {
|
|
@@ -12,6 +15,11 @@ export async function startCommand(ctx) {
|
|
|
12
15
|
}
|
|
13
16
|
keyboardManager.initialize(ctx.api, ctx.chat.id);
|
|
14
17
|
}
|
|
18
|
+
await abortCurrentOperation(ctx, { notifyUser: false });
|
|
19
|
+
clearSession();
|
|
20
|
+
clearProject();
|
|
21
|
+
keyboardManager.clearContext();
|
|
22
|
+
await pinnedMessageManager.clear();
|
|
15
23
|
if (pinnedMessageManager.getContextLimit() === 0) {
|
|
16
24
|
await pinnedMessageManager.refreshContextLimit();
|
|
17
25
|
}
|
|
@@ -4,7 +4,6 @@ import { getCurrentProject } from "../../settings/manager.js";
|
|
|
4
4
|
import { fetchCurrentAgent } from "../../agent/manager.js";
|
|
5
5
|
import { getAgentDisplayName } from "../../agent/types.js";
|
|
6
6
|
import { fetchCurrentModel } from "../../model/manager.js";
|
|
7
|
-
import { formatModelForDisplay } from "../../model/types.js";
|
|
8
7
|
import { processManager } from "../../process/manager.js";
|
|
9
8
|
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
9
|
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
@@ -42,7 +41,7 @@ export async function statusCommand(ctx) {
|
|
|
42
41
|
message += `${t("status.line.mode", { mode: agentDisplay })}\n`;
|
|
43
42
|
// Add model information
|
|
44
43
|
const currentModel = fetchCurrentModel();
|
|
45
|
-
const modelDisplay =
|
|
44
|
+
const modelDisplay = `🤖 ${currentModel.providerID}/${currentModel.modelID}`;
|
|
46
45
|
message += `${t("status.line.model", { model: modelDisplay })}\n`;
|
|
47
46
|
const currentProject = getCurrentProject();
|
|
48
47
|
if (currentProject) {
|
package/dist/bot/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { AGENT_MODE_BUTTON_TEXT_PATTERN, MODEL_BUTTON_TEXT_PATTERN, VARIANT_BUTT
|
|
|
16
16
|
import { sessionsCommand, handleSessionSelect } from "./commands/sessions.js";
|
|
17
17
|
import { newCommand } from "./commands/new.js";
|
|
18
18
|
import { projectsCommand, handleProjectSelect } from "./commands/projects.js";
|
|
19
|
-
import {
|
|
19
|
+
import { abortCommand } from "./commands/abort.js";
|
|
20
20
|
import { opencodeStartCommand } from "./commands/opencode-start.js";
|
|
21
21
|
import { opencodeStopCommand } from "./commands/opencode-stop.js";
|
|
22
22
|
import { renameCommand, handleRenameCancel, handleRenameTextAnswer } from "./commands/rename.js";
|
|
@@ -122,7 +122,7 @@ async function ensureCommandsInitialized(ctx, next) {
|
|
|
122
122
|
},
|
|
123
123
|
});
|
|
124
124
|
commandsInitialized = true;
|
|
125
|
-
logger.
|
|
125
|
+
logger.debug(`[Bot] Commands initialized for authorized user (chat_id=${ctx.chat.id})`);
|
|
126
126
|
}
|
|
127
127
|
catch (err) {
|
|
128
128
|
logger.error("[Bot] Failed to set commands:", err);
|
|
@@ -385,7 +385,7 @@ async function ensureEventSubscription(directory) {
|
|
|
385
385
|
export function createBot() {
|
|
386
386
|
clearAllInteractionState("bot_startup");
|
|
387
387
|
toolMessageBatcher.setIntervalSeconds(config.bot.serviceMessagesIntervalSec);
|
|
388
|
-
logger.
|
|
388
|
+
logger.debug(`[ToolBatcher] Service messages interval: ${config.bot.serviceMessagesIntervalSec}s`);
|
|
389
389
|
const botOptions = {};
|
|
390
390
|
if (config.telegram.proxyUrl) {
|
|
391
391
|
const proxyUrl = config.telegram.proxyUrl;
|
|
@@ -456,7 +456,7 @@ export function createBot() {
|
|
|
456
456
|
bot.command("projects", projectsCommand);
|
|
457
457
|
bot.command("sessions", sessionsCommand);
|
|
458
458
|
bot.command("new", newCommand);
|
|
459
|
-
bot.command("
|
|
459
|
+
bot.command("abort", abortCommand);
|
|
460
460
|
bot.command("rename", renameCommand);
|
|
461
461
|
bot.command("commands", commandsCommand);
|
|
462
462
|
bot.on("message:text", unknownCommandMiddleware);
|
|
@@ -584,7 +584,7 @@ export function createBot() {
|
|
|
584
584
|
},
|
|
585
585
|
onSuccess: (result) => {
|
|
586
586
|
if (result.success) {
|
|
587
|
-
logger.
|
|
587
|
+
logger.debug("[Bot] Cleared global commands (default and all_private_chats scopes)");
|
|
588
588
|
return;
|
|
589
589
|
}
|
|
590
590
|
logger.warn("[Bot] Could not clear global commands:", result.error);
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import dotenv from "dotenv";
|
|
|
2
2
|
import { getRuntimePaths } from "./runtime/paths.js";
|
|
3
3
|
import { normalizeLocale } from "./i18n/index.js";
|
|
4
4
|
const runtimePaths = getRuntimePaths();
|
|
5
|
-
dotenv.config({ path: runtimePaths.envFilePath });
|
|
5
|
+
dotenv.config({ path: runtimePaths.envFilePath, quiet: true });
|
|
6
6
|
function getEnvVar(key, required = true) {
|
|
7
7
|
const value = process.env[key];
|
|
8
8
|
if (required && !value) {
|
package/dist/i18n/de.js
CHANGED
|
@@ -38,11 +38,11 @@ export const de = {
|
|
|
38
38
|
"bot.creating_session": "🔄 Erstelle eine neue Sitzung...",
|
|
39
39
|
"bot.create_session_error": "🔴 Sitzung konnte nicht erstellt werden. Versuche /new oder prüfe den Serverstatus mit /status.",
|
|
40
40
|
"bot.session_created": "✅ Sitzung erstellt: {title}",
|
|
41
|
-
"bot.session_busy": "⏳ Agent führt bereits eine Aufgabe aus. Warte auf Abschluss oder nutze /
|
|
41
|
+
"bot.session_busy": "⏳ Agent führt bereits eine Aufgabe aus. Warte auf Abschluss oder nutze /abort, um den aktuellen Lauf zu unterbrechen.",
|
|
42
42
|
"bot.session_reset_project_mismatch": "⚠️ Die aktive Sitzung passt nicht zum ausgewählten Projekt und wurde daher zurückgesetzt. Nutze /sessions zur Auswahl oder /new, um eine neue Sitzung zu erstellen.",
|
|
43
43
|
"bot.prompt_send_error": "Anfrage konnte nicht an OpenCode gesendet werden.",
|
|
44
44
|
"bot.session_error": "🔴 OpenCode meldete einen Fehler: {message}",
|
|
45
|
-
"bot.session_retry": "🔁 {message}\n\nDer Provider liefert bei wiederholten Versuchen immer wieder denselben Fehler. Mit /
|
|
45
|
+
"bot.session_retry": "🔁 {message}\n\nDer Provider liefert bei wiederholten Versuchen immer wieder denselben Fehler. Mit /abort abbrechen.",
|
|
46
46
|
"bot.unknown_command": "⚠️ Unbekannter Befehl: {command}. Nutze /help, um verfügbare Befehle zu sehen.",
|
|
47
47
|
"bot.photo_downloading": "⏳ Lade Foto herunter...",
|
|
48
48
|
"bot.photo_too_large": "⚠️ Foto ist zu groß (max. {maxSizeMb}MB)",
|
|
@@ -59,18 +59,18 @@ export const de = {
|
|
|
59
59
|
"status.health.unhealthy": "Nicht OK",
|
|
60
60
|
"status.line.health": "Status: {health}",
|
|
61
61
|
"status.line.version": "Version: {version}",
|
|
62
|
-
"status.line.managed_yes": "Vom Bot
|
|
63
|
-
"status.line.managed_no": "Vom Bot
|
|
62
|
+
"status.line.managed_yes": "Vom Bot gestartet: Ja",
|
|
63
|
+
"status.line.managed_no": "Vom Bot gestartet: Nein",
|
|
64
64
|
"status.line.pid": "PID: {pid}",
|
|
65
65
|
"status.line.uptime_sec": "Betriebszeit: {seconds} s",
|
|
66
66
|
"status.line.mode": "Modus: {mode}",
|
|
67
67
|
"status.line.model": "Modell: {model}",
|
|
68
68
|
"status.agent_not_set": "nicht gesetzt",
|
|
69
|
-
"status.project_selected": "
|
|
70
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Projekt: {project}",
|
|
70
|
+
"status.project_not_selected": "Projekt: nicht ausgewählt",
|
|
71
71
|
"status.project_hint": "Nutze /projects, um ein Projekt auszuwahlen",
|
|
72
|
-
"status.session_selected": "
|
|
73
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Aktuelle Sitzung: {title}",
|
|
73
|
+
"status.session_not_selected": "Aktuelle Sitzung: nicht ausgewählt",
|
|
74
74
|
"status.session_hint": "Nutze /sessions zur Auswahl oder /new zum Erstellen",
|
|
75
75
|
"status.server_unavailable": "🔴 OpenCode-Server ist nicht verfügbar\n\nNutze /opencode_start, um den Server zu starten.",
|
|
76
76
|
"projects.empty": "📭 Keine Projekte gefunden.\n\nÖffne ein Verzeichnis in OpenCode und erstelle mindestens eine Sitzung, dann erscheint es hier.",
|
|
@@ -105,13 +105,13 @@ export const de = {
|
|
|
105
105
|
"new.create_error": "🔴 OpenCode-Server ist nicht verfügbar oder beim Erstellen der Sitzung ist ein Fehler aufgetreten.",
|
|
106
106
|
"stop.no_active_session": "🛑 Agent wurde nicht gestartet\n\nErstelle eine Sitzung mit /new oder wähle eine über /sessions aus.",
|
|
107
107
|
"stop.in_progress": "🛑 Event-Stream gestoppt, sende Abbruchsignal...\n\nWarte darauf, dass der Agent stoppt.",
|
|
108
|
-
"stop.warn_unconfirmed": "⚠️ Event-Stream gestoppt, aber der Server hat den Abbruch nicht bestätigt.\n\nPrüfe /status und versuche /
|
|
108
|
+
"stop.warn_unconfirmed": "⚠️ Event-Stream gestoppt, aber der Server hat den Abbruch nicht bestätigt.\n\nPrüfe /status und versuche /abort in ein paar Sekunden erneut.",
|
|
109
109
|
"stop.warn_maybe_finished": "⚠️ Event-Stream gestoppt, aber der Agent konnte bereits fertig sein.",
|
|
110
110
|
"stop.success": "✅ Agent-Aktion unterbrochen. Von diesem Lauf werden keine weiteren Nachrichten gesendet.",
|
|
111
111
|
"stop.warn_still_busy": "⚠️ Signal gesendet, aber der Agent ist noch beschäftigt.\n\nDer Event-Stream ist bereits deaktiviert, daher werden keine Zwischenmeldungen gesendet.",
|
|
112
|
-
"stop.warn_timeout": "⚠️ Timeout beim Abbruch.\n\nDer Event-Stream ist bereits deaktiviert, versuche /
|
|
112
|
+
"stop.warn_timeout": "⚠️ Timeout beim Abbruch.\n\nDer Event-Stream ist bereits deaktiviert, versuche /abort in ein paar Sekunden erneut.",
|
|
113
113
|
"stop.warn_local_only": "⚠️ Event-Stream lokal gestoppt, aber serverseitiger Abbruch ist fehlgeschlagen.",
|
|
114
|
-
"stop.error": "🔴 Aktion konnte nicht gestoppt werden.\n\nEvent-Stream ist gestoppt, versuche /
|
|
114
|
+
"stop.error": "🔴 Aktion konnte nicht gestoppt werden.\n\nEvent-Stream ist gestoppt, versuche /abort erneut.",
|
|
115
115
|
"opencode_start.already_running_managed": "⚠️ OpenCode-Server läuft bereits\n\nPID: {pid}\nBetriebszeit: {seconds} Sekunden",
|
|
116
116
|
"opencode_start.already_running_external": "✅ OpenCode-Server läuft bereits als externer Prozess\n\nVersion: {version}\n\nDieser Server wurde nicht vom Bot gestartet, daher kann /opencode-stop ihn nicht stoppen.",
|
|
117
117
|
"opencode_start.starting": "🔄 Starte OpenCode-Server...",
|
|
@@ -228,6 +228,8 @@ export const de = {
|
|
|
228
228
|
"runtime.wizard.ask_user_id": "Gib deine Telegram User ID ein (du bekommst sie bei @userinfobot).\n> ",
|
|
229
229
|
"runtime.wizard.user_id_invalid": "Gib eine positive ganze Zahl ein (> 0).\n",
|
|
230
230
|
"runtime.wizard.ask_api_url": "OpenCode API URL eingeben (optional).\nEnter drücken für Standard: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "OpenCode-Server-Benutzername eingeben (optional).\nEnter drücken für Standard: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "OpenCode-Server-Passwort eingeben (optional).\nEnter drücken, um es leer zu lassen.\n> ",
|
|
231
233
|
"runtime.wizard.api_url_invalid": "Gib eine gültige URL (http/https) ein oder drücke Enter für Standard.\n",
|
|
232
234
|
"runtime.wizard.start": "OpenCode Telegram Bot Einrichtung.\n",
|
|
233
235
|
"runtime.wizard.saved": "Konfiguration gespeichert:\n- {envPath}\n- {settingsPath}\n",
|
package/dist/i18n/en.js
CHANGED
|
@@ -38,11 +38,11 @@ export const en = {
|
|
|
38
38
|
"bot.creating_session": "🔄 Creating a new session...",
|
|
39
39
|
"bot.create_session_error": "🔴 Failed to create session. Try /new or check server status with /status.",
|
|
40
40
|
"bot.session_created": "✅ Session created: {title}",
|
|
41
|
-
"bot.session_busy": "⏳ Agent is already running a task. Wait for completion or use /
|
|
41
|
+
"bot.session_busy": "⏳ Agent is already running a task. Wait for completion or use /abort to interrupt current run.",
|
|
42
42
|
"bot.session_reset_project_mismatch": "⚠️ Active session does not match the selected project, so it was reset. Use /sessions to pick one or /new to create a new session.",
|
|
43
43
|
"bot.prompt_send_error": "Failed to send request to OpenCode.",
|
|
44
44
|
"bot.session_error": "🔴 OpenCode returned an error: {message}",
|
|
45
|
-
"bot.session_retry": "🔁 {message}\n\nProvider keeps returning the same error on repeated retries. Use /
|
|
45
|
+
"bot.session_retry": "🔁 {message}\n\nProvider keeps returning the same error on repeated retries. Use /abort to abort.",
|
|
46
46
|
"bot.unknown_command": "⚠️ Unknown command: {command}. Use /help to see available commands.",
|
|
47
47
|
"bot.photo_downloading": "⏳ Downloading photo...",
|
|
48
48
|
"bot.photo_too_large": "⚠️ Photo is too large (max {maxSizeMb}MB)",
|
|
@@ -59,18 +59,18 @@ export const en = {
|
|
|
59
59
|
"status.health.unhealthy": "Unhealthy",
|
|
60
60
|
"status.line.health": "Status: {health}",
|
|
61
61
|
"status.line.version": "Version: {version}",
|
|
62
|
-
"status.line.managed_yes": "
|
|
63
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Started by bot: Yes",
|
|
63
|
+
"status.line.managed_no": "Started by bot: No",
|
|
64
64
|
"status.line.pid": "PID: {pid}",
|
|
65
65
|
"status.line.uptime_sec": "Uptime: {seconds} sec",
|
|
66
66
|
"status.line.mode": "Mode: {mode}",
|
|
67
67
|
"status.line.model": "Model: {model}",
|
|
68
68
|
"status.agent_not_set": "not set",
|
|
69
|
-
"status.project_selected": "
|
|
70
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Project: {project}",
|
|
70
|
+
"status.project_not_selected": "Project: not selected",
|
|
71
71
|
"status.project_hint": "Use /projects to select a project",
|
|
72
|
-
"status.session_selected": "
|
|
73
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Current session: {title}",
|
|
73
|
+
"status.session_not_selected": "Current session: not selected",
|
|
74
74
|
"status.session_hint": "Use /sessions to select one or /new to create one",
|
|
75
75
|
"status.server_unavailable": "🔴 OpenCode Server is unavailable\n\nUse /opencode_start to start the server.",
|
|
76
76
|
"projects.empty": "📭 No projects found.\n\nOpen a directory in OpenCode and create at least one session, then it will appear here.",
|
|
@@ -105,13 +105,13 @@ export const en = {
|
|
|
105
105
|
"new.create_error": "🔴 OpenCode Server is unavailable or an error occurred while creating session.",
|
|
106
106
|
"stop.no_active_session": "🛑 Agent was not started\n\nCreate a session with /new or select one via /sessions.",
|
|
107
107
|
"stop.in_progress": "🛑 Event stream stopped, sending abort signal...\n\nWaiting for agent to stop.",
|
|
108
|
-
"stop.warn_unconfirmed": "⚠️ Event stream stopped, but server did not confirm abort.\n\nCheck /status and retry /
|
|
108
|
+
"stop.warn_unconfirmed": "⚠️ Event stream stopped, but server did not confirm abort.\n\nCheck /status and retry /abort in a few seconds.",
|
|
109
109
|
"stop.warn_maybe_finished": "⚠️ Event stream stopped, but the agent may have already finished.",
|
|
110
110
|
"stop.success": "✅ Agent action interrupted. No more messages from this run will be sent.",
|
|
111
111
|
"stop.warn_still_busy": "⚠️ Signal sent, but agent is still busy.\n\nEvent stream is already disabled, so no intermediate messages will be sent.",
|
|
112
|
-
"stop.warn_timeout": "⚠️ Abort request timeout.\n\nEvent stream is already disabled, retry /
|
|
112
|
+
"stop.warn_timeout": "⚠️ Abort request timeout.\n\nEvent stream is already disabled, retry /abort in a few seconds.",
|
|
113
113
|
"stop.warn_local_only": "⚠️ Event stream stopped locally, but server-side abort failed.",
|
|
114
|
-
"stop.error": "🔴 Failed to stop action.\n\nEvent stream is stopped, try /
|
|
114
|
+
"stop.error": "🔴 Failed to stop action.\n\nEvent stream is stopped, try /abort again.",
|
|
115
115
|
"opencode_start.already_running_managed": "⚠️ OpenCode Server is already running\n\nPID: {pid}\nUptime: {seconds} seconds",
|
|
116
116
|
"opencode_start.already_running_external": "✅ OpenCode Server is already running as an external process\n\nVersion: {version}\n\nThis server was not started by bot, so /opencode-stop cannot stop it.",
|
|
117
117
|
"opencode_start.starting": "🔄 Starting OpenCode Server...",
|
|
@@ -228,6 +228,8 @@ export const en = {
|
|
|
228
228
|
"runtime.wizard.ask_user_id": "Enter your Telegram User ID (you can get it from @userinfobot).\n> ",
|
|
229
229
|
"runtime.wizard.user_id_invalid": "Enter a positive integer (> 0).\n",
|
|
230
230
|
"runtime.wizard.ask_api_url": "Enter OpenCode API URL (optional).\nPress Enter to use default: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Enter OpenCode server username (optional).\nPress Enter to use default: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Enter OpenCode server password (optional).\nPress Enter to keep it empty.\n> ",
|
|
231
233
|
"runtime.wizard.api_url_invalid": "Enter a valid URL (http/https) or press Enter for default.\n",
|
|
232
234
|
"runtime.wizard.start": "OpenCode Telegram Bot setup.\n",
|
|
233
235
|
"runtime.wizard.saved": "Configuration saved:\n- {envPath}\n- {settingsPath}\n",
|
package/dist/i18n/es.js
CHANGED
|
@@ -38,11 +38,11 @@ export const es = {
|
|
|
38
38
|
"bot.creating_session": "🔄 Creando una sesión nueva...",
|
|
39
39
|
"bot.create_session_error": "🔴 No se pudo crear la sesión. Prueba /new o revisa el estado del servidor con /status.",
|
|
40
40
|
"bot.session_created": "✅ Sesión creada: {title}",
|
|
41
|
-
"bot.session_busy": "⏳ El agente ya está ejecutando una tarea. Espera a que termine o usa /
|
|
41
|
+
"bot.session_busy": "⏳ El agente ya está ejecutando una tarea. Espera a que termine o usa /abort para interrumpir la ejecución actual.",
|
|
42
42
|
"bot.session_reset_project_mismatch": "⚠️ La sesión activa no coincide con el proyecto seleccionado, así que se reinició. Usa /sessions para elegir una o /new para crear una nueva.",
|
|
43
43
|
"bot.prompt_send_error": "No se pudo enviar la solicitud a OpenCode.",
|
|
44
44
|
"bot.session_error": "🔴 OpenCode devolvió un error: {message}",
|
|
45
|
-
"bot.session_retry": "🔁 {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /
|
|
45
|
+
"bot.session_retry": "🔁 {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /abort para detenerlo.",
|
|
46
46
|
"bot.unknown_command": "⚠️ Comando desconocido: {command}. Usa /help para ver los comandos disponibles.",
|
|
47
47
|
"bot.photo_downloading": "⏳ Descargando foto...",
|
|
48
48
|
"bot.photo_too_large": "⚠️ La foto es demasiado grande (max {maxSizeMb}MB)",
|
|
@@ -59,18 +59,18 @@ export const es = {
|
|
|
59
59
|
"status.health.unhealthy": "No saludable",
|
|
60
60
|
"status.line.health": "Estado: {health}",
|
|
61
61
|
"status.line.version": "Versión: {version}",
|
|
62
|
-
"status.line.managed_yes": "
|
|
63
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Iniciado por el bot: Sí",
|
|
63
|
+
"status.line.managed_no": "Iniciado por el bot: No",
|
|
64
64
|
"status.line.pid": "PID: {pid}",
|
|
65
65
|
"status.line.uptime_sec": "Tiempo activo: {seconds} s",
|
|
66
66
|
"status.line.mode": "Modo: {mode}",
|
|
67
67
|
"status.line.model": "Modelo: {model}",
|
|
68
68
|
"status.agent_not_set": "no configurado",
|
|
69
|
-
"status.project_selected": "
|
|
70
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Proyecto: {project}",
|
|
70
|
+
"status.project_not_selected": "Proyecto: no seleccionado",
|
|
71
71
|
"status.project_hint": "Usa /projects para seleccionar un proyecto",
|
|
72
|
-
"status.session_selected": "
|
|
73
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Sesión actual: {title}",
|
|
73
|
+
"status.session_not_selected": "Sesión actual: no seleccionada",
|
|
74
74
|
"status.session_hint": "Usa /sessions para elegir una o /new para crear una",
|
|
75
75
|
"status.server_unavailable": "🔴 OpenCode Server no está disponible\n\nUsa /opencode_start para iniciar el servidor.",
|
|
76
76
|
"projects.empty": "📭 No se encontraron proyectos.\n\nAbre un directorio en OpenCode y crea al menos una sesión; entonces aparecerá aquí.",
|
|
@@ -105,13 +105,13 @@ export const es = {
|
|
|
105
105
|
"new.create_error": "🔴 OpenCode Server no está disponible u ocurrió un error al crear la sesión.",
|
|
106
106
|
"stop.no_active_session": "🛑 El agente no se inició\n\nCrea una sesión con /new o selecciona una con /sessions.",
|
|
107
107
|
"stop.in_progress": "🛑 Flujo de eventos detenido; enviando señal de aborto...\n\nEsperando a que el agente se detenga.",
|
|
108
|
-
"stop.warn_unconfirmed": "⚠️ Flujo de eventos detenido, pero el servidor no confirmó el aborto.\n\nRevisa /status y vuelve a intentar /
|
|
108
|
+
"stop.warn_unconfirmed": "⚠️ Flujo de eventos detenido, pero el servidor no confirmó el aborto.\n\nRevisa /status y vuelve a intentar /abort en unos segundos.",
|
|
109
109
|
"stop.warn_maybe_finished": "⚠️ Flujo de eventos detenido, pero el agente podría haber terminado ya.",
|
|
110
110
|
"stop.success": "✅ Acción del agente interrumpida. No se enviarán más mensajes de esta ejecución.",
|
|
111
111
|
"stop.warn_still_busy": "⚠️ Señal enviada, pero el agente sigue ocupado.\n\nEl flujo de eventos ya está deshabilitado, así que no se enviarán mensajes intermedios.",
|
|
112
|
-
"stop.warn_timeout": "⚠️ Tiempo de espera agotado al solicitar el aborto.\n\nEl flujo de eventos ya está deshabilitado; vuelve a intentar /
|
|
112
|
+
"stop.warn_timeout": "⚠️ Tiempo de espera agotado al solicitar el aborto.\n\nEl flujo de eventos ya está deshabilitado; vuelve a intentar /abort en unos segundos.",
|
|
113
113
|
"stop.warn_local_only": "⚠️ Flujo de eventos detenido localmente, pero el aborto en el servidor falló.",
|
|
114
|
-
"stop.error": "🔴 No se pudo detener la acción.\n\nEl flujo de eventos está detenido; prueba /
|
|
114
|
+
"stop.error": "🔴 No se pudo detener la acción.\n\nEl flujo de eventos está detenido; prueba /abort otra vez.",
|
|
115
115
|
"opencode_start.already_running_managed": "⚠️ OpenCode Server ya está en ejecución\n\nPID: {pid}\nTiempo activo: {seconds} segundos",
|
|
116
116
|
"opencode_start.already_running_external": "✅ OpenCode Server ya está en ejecución como un proceso externo\n\nVersión: {version}\n\nEste servidor no fue iniciado por el bot, por lo que /opencode-stop no puede detenerlo.",
|
|
117
117
|
"opencode_start.starting": "🔄 Iniciando OpenCode Server...",
|
|
@@ -228,6 +228,8 @@ export const es = {
|
|
|
228
228
|
"runtime.wizard.ask_user_id": "Introduce tu Telegram User ID (puedes obtenerlo de @userinfobot).\n> ",
|
|
229
229
|
"runtime.wizard.user_id_invalid": "Introduce un entero positivo (> 0).\n",
|
|
230
230
|
"runtime.wizard.ask_api_url": "Introduce la URL de la API de OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Introduce el nombre de usuario del servidor OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Introduce la contrasena del servidor OpenCode (opcional).\nPulsa Enter para dejarla vacia.\n> ",
|
|
231
233
|
"runtime.wizard.api_url_invalid": "Introduce una URL válida (http/https) o pulsa Enter para usar el valor por defecto.\n",
|
|
232
234
|
"runtime.wizard.start": "Configuración de OpenCode Telegram Bot.\n",
|
|
233
235
|
"runtime.wizard.saved": "Configuración guardada:\n- {envPath}\n- {settingsPath}\n",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -38,11 +38,11 @@ export const ru = {
|
|
|
38
38
|
"bot.creating_session": "🔄 Создаю новую сессию...",
|
|
39
39
|
"bot.create_session_error": "🔴 Не удалось создать сессию. Попробуйте команду /new или проверьте статус сервера /status.",
|
|
40
40
|
"bot.session_created": "✅ Сессия создана: {title}",
|
|
41
|
-
"bot.session_busy": "⏳ Агент уже выполняет задачу. Дождитесь завершения или используйте /
|
|
41
|
+
"bot.session_busy": "⏳ Агент уже выполняет задачу. Дождитесь завершения или используйте /abort, чтобы прервать текущий запуск.",
|
|
42
42
|
"bot.session_reset_project_mismatch": "⚠️ Активная сессия не соответствует выбранному проекту, поэтому была сброшена. Используйте /sessions для выбора или /new для создания новой сессии.",
|
|
43
43
|
"bot.prompt_send_error": "Не удалось отправить запрос в OpenCode.",
|
|
44
44
|
"bot.session_error": "🔴 OpenCode вернул ошибку: {message}",
|
|
45
|
-
"bot.session_retry": "🔁 {message}\n\nПровайдер возвращает одну и ту же ошибку при повторных запросах. Используйте /
|
|
45
|
+
"bot.session_retry": "🔁 {message}\n\nПровайдер возвращает одну и ту же ошибку при повторных запросах. Используйте /abort для остановки.",
|
|
46
46
|
"bot.unknown_command": "⚠️ Неизвестная команда: {command}. Используйте /help для списка команд.",
|
|
47
47
|
"bot.photo_downloading": "⏳ Скачиваю фото...",
|
|
48
48
|
"bot.photo_too_large": "⚠️ Фото слишком большое (макс. {maxSizeMb}МБ)",
|
|
@@ -59,18 +59,18 @@ export const ru = {
|
|
|
59
59
|
"status.health.unhealthy": "Unhealthy",
|
|
60
60
|
"status.line.health": "Статус: {health}",
|
|
61
61
|
"status.line.version": "Версия: {version}",
|
|
62
|
-
"status.line.managed_yes": "
|
|
63
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Запущен ботом: Да",
|
|
63
|
+
"status.line.managed_no": "Запущен ботом: Нет",
|
|
64
64
|
"status.line.pid": "PID: {pid}",
|
|
65
65
|
"status.line.uptime_sec": "Uptime: {seconds} сек",
|
|
66
66
|
"status.line.mode": "Режим: {mode}",
|
|
67
67
|
"status.line.model": "Модель: {model}",
|
|
68
68
|
"status.agent_not_set": "не установлен",
|
|
69
|
-
"status.project_selected": "
|
|
70
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Проект: {project}",
|
|
70
|
+
"status.project_not_selected": "Проект: не выбран",
|
|
71
71
|
"status.project_hint": "Используйте /projects для выбора проекта",
|
|
72
|
-
"status.session_selected": "
|
|
73
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Текущая сессия: {title}",
|
|
73
|
+
"status.session_not_selected": "Текущая сессия: не выбрана",
|
|
74
74
|
"status.session_hint": "Используйте /sessions для выбора или /new для создания",
|
|
75
75
|
"status.server_unavailable": "🔴 OpenCode Server недоступен\n\nИспользуйте /opencode_start для запуска сервера.",
|
|
76
76
|
"projects.empty": "📭 Проектов нет.\n\nОткройте директорию в OpenCode и создайте хотя бы одну сессию, после этого она появится здесь.",
|
|
@@ -105,13 +105,13 @@ export const ru = {
|
|
|
105
105
|
"new.create_error": "🔴 OpenCode Server недоступен или произошла ошибка при создании сессии.",
|
|
106
106
|
"stop.no_active_session": "🛑 Агент не был запущен\n\nСначала создайте сессию командой /new или выберите существующую через /sessions.",
|
|
107
107
|
"stop.in_progress": "🛑 Отключил поток событий и отправляю сигнал прерывания...\n\nОжидание остановки агента.",
|
|
108
|
-
"stop.warn_unconfirmed": "⚠️ Поток событий остановлен, но сервер не подтвердил прерывание.\n\nПроверьте /status и повторите /
|
|
108
|
+
"stop.warn_unconfirmed": "⚠️ Поток событий остановлен, но сервер не подтвердил прерывание.\n\nПроверьте /status и повторите /abort через пару секунд.",
|
|
109
109
|
"stop.warn_maybe_finished": "⚠️ Поток событий остановлен, но агент мог уже завершиться к моменту запроса.",
|
|
110
110
|
"stop.success": "✅ Действие агента прервано. Новые сообщения от текущего запуска больше не придут.",
|
|
111
111
|
"stop.warn_still_busy": "⚠️ Сигнал отправлен, но агент еще busy.\n\nПоток событий уже отключен, поэтому бот не будет присылать промежуточные сообщения.",
|
|
112
|
-
"stop.warn_timeout": "⚠️ Таймаут запроса на прерывание.\n\nПоток событий уже отключен, повторите /
|
|
112
|
+
"stop.warn_timeout": "⚠️ Таймаут запроса на прерывание.\n\nПоток событий уже отключен, повторите /abort через пару секунд.",
|
|
113
113
|
"stop.warn_local_only": "⚠️ Поток событий остановлен локально, но при прерывании на сервере произошла ошибка.",
|
|
114
|
-
"stop.error": "🔴 Ошибка при прерывании действия.\n\nПоток событий остановлен, попробуйте /
|
|
114
|
+
"stop.error": "🔴 Ошибка при прерывании действия.\n\nПоток событий остановлен, попробуйте /abort еще раз.",
|
|
115
115
|
"opencode_start.already_running_managed": "⚠️ OpenCode Server уже запущен\n\nPID: {pid}\nUptime: {seconds} секунд",
|
|
116
116
|
"opencode_start.already_running_external": "✅ OpenCode Server уже запущен внешним процессом\n\nВерсия: {version}\n\nЭтот сервер не был запущен через бота, поэтому команда /opencode-stop не сможет его остановить.",
|
|
117
117
|
"opencode_start.starting": "🔄 Запускаю OpenCode Server...",
|
|
@@ -208,14 +208,14 @@ export const ru = {
|
|
|
208
208
|
"keyboard.variant": "💭 {name}",
|
|
209
209
|
"keyboard.variant_default": "💡 Default",
|
|
210
210
|
"keyboard.updated": "⌨️ Клавиатура обновлена",
|
|
211
|
-
"pinned.default_session_title": "
|
|
212
|
-
"pinned.unknown": "
|
|
213
|
-
"pinned.line.project": "
|
|
214
|
-
"pinned.line.model": "
|
|
215
|
-
"pinned.line.context": "
|
|
216
|
-
"pinned.files.title": "
|
|
211
|
+
"pinned.default_session_title": "новая сессия",
|
|
212
|
+
"pinned.unknown": "Неизвестно",
|
|
213
|
+
"pinned.line.project": "Проект: {project}",
|
|
214
|
+
"pinned.line.model": "Модель: {model}",
|
|
215
|
+
"pinned.line.context": "Контекст: {used} / {limit} ({percent}%)",
|
|
216
|
+
"pinned.files.title": "Файлы ({count}):",
|
|
217
217
|
"pinned.files.item": " {path}{diff}",
|
|
218
|
-
"pinned.files.more": " ...
|
|
218
|
+
"pinned.files.more": " ... и еще {count}",
|
|
219
219
|
"tool.todo.overflow": "*(ещё {count} задач)*",
|
|
220
220
|
"tool.file_header.write": "Write File/Path: {path}\n============================================================\n\n",
|
|
221
221
|
"tool.file_header.edit": "Edit File/Path: {path}\n============================================================\n\n",
|
|
@@ -228,6 +228,8 @@ export const ru = {
|
|
|
228
228
|
"runtime.wizard.ask_user_id": "Введите ваш Telegram User ID (можно узнать у @userinfobot).\n> ",
|
|
229
229
|
"runtime.wizard.user_id_invalid": "Введите положительное целое число (> 0).\n",
|
|
230
230
|
"runtime.wizard.ask_api_url": "Введите URL OpenCode API (опционально).\nНажмите Enter для значения по умолчанию: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Введите логин OpenCode сервера (опционально).\nНажмите Enter для значения по умолчанию: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Введите пароль OpenCode сервера (опционально).\nНажмите Enter, чтобы оставить пустым.\n> ",
|
|
231
233
|
"runtime.wizard.api_url_invalid": "Введите корректный URL (http/https) или нажмите Enter для значения по умолчанию.\n",
|
|
232
234
|
"runtime.wizard.start": "Настройка OpenCode Telegram Bot.\n",
|
|
233
235
|
"runtime.wizard.saved": "Конфигурация сохранена:\n- {envPath}\n- {settingsPath}\n",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -38,11 +38,11 @@ export const zh = {
|
|
|
38
38
|
"bot.creating_session": "🔄 正在创建新会话...",
|
|
39
39
|
"bot.create_session_error": "🔴 创建会话失败。请重试 /new,或使用 /status 检查服务器状态。",
|
|
40
40
|
"bot.session_created": "✅ 会话已创建:{title}",
|
|
41
|
-
"bot.session_busy": "⏳ 代理正在执行任务。请等待完成,或使用 /
|
|
41
|
+
"bot.session_busy": "⏳ 代理正在执行任务。请等待完成,或使用 /abort 中断当前运行。",
|
|
42
42
|
"bot.session_reset_project_mismatch": "⚠️ 活动会话与所选项目不匹配,因此已重置。使用 /sessions 选择一个会话,或 /new 创建新会话。",
|
|
43
43
|
"bot.prompt_send_error": "向 OpenCode 发送请求失败。",
|
|
44
44
|
"bot.session_error": "🔴 OpenCode 返回错误:{message}",
|
|
45
|
-
"bot.session_retry": "🔁 {message}\n\n提供方在重复重试时持续返回同一错误。使用 /
|
|
45
|
+
"bot.session_retry": "🔁 {message}\n\n提供方在重复重试时持续返回同一错误。使用 /abort 可停止。",
|
|
46
46
|
"bot.unknown_command": "⚠️ 未知命令:{command}。使用 /help 查看可用命令。",
|
|
47
47
|
"bot.photo_downloading": "⏳ 正在下载照片...",
|
|
48
48
|
"bot.photo_too_large": "⚠️ 照片过大(最大 {maxSizeMb}MB)",
|
|
@@ -59,18 +59,18 @@ export const zh = {
|
|
|
59
59
|
"status.health.unhealthy": "不健康",
|
|
60
60
|
"status.line.health": "状态:{health}",
|
|
61
61
|
"status.line.version": "版本:{version}",
|
|
62
|
-
"status.line.managed_yes": "
|
|
63
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "由机器人启动:是",
|
|
63
|
+
"status.line.managed_no": "由机器人启动:否",
|
|
64
64
|
"status.line.pid": "PID:{pid}",
|
|
65
65
|
"status.line.uptime_sec": "运行时间:{seconds} 秒",
|
|
66
66
|
"status.line.mode": "模式:{mode}",
|
|
67
67
|
"status.line.model": "模型:{model}",
|
|
68
68
|
"status.agent_not_set": "未设置",
|
|
69
|
-
"status.project_selected": "
|
|
70
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "项目:{project}",
|
|
70
|
+
"status.project_not_selected": "项目:未选择",
|
|
71
71
|
"status.project_hint": "使用 /projects 选择项目",
|
|
72
|
-
"status.session_selected": "
|
|
73
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "当前会话:{title}",
|
|
73
|
+
"status.session_not_selected": "当前会话:未选择",
|
|
74
74
|
"status.session_hint": "使用 /sessions 选择一个会话,或 /new 创建",
|
|
75
75
|
"status.server_unavailable": "🔴 OpenCode 服务器不可用\n\n使用 /opencode_start 启动服务器。",
|
|
76
76
|
"projects.empty": "📭 未找到项目。\n\n在 OpenCode 中打开一个目录并至少创建一个会话,然后它会出现在这里。",
|
|
@@ -105,13 +105,13 @@ export const zh = {
|
|
|
105
105
|
"new.create_error": "🔴 OpenCode 服务器不可用,或创建会话时发生错误。",
|
|
106
106
|
"stop.no_active_session": "🛑 代理尚未启动\n\n使用 /new 创建会话,或通过 /sessions 选择一个。",
|
|
107
107
|
"stop.in_progress": "🛑 已停止事件流,正在发送中止信号...\n\n等待代理停止。",
|
|
108
|
-
"stop.warn_unconfirmed": "⚠️ 事件流已停止,但服务器未确认中止。\n\n检查 /status,并在几秒后重试 /
|
|
108
|
+
"stop.warn_unconfirmed": "⚠️ 事件流已停止,但服务器未确认中止。\n\n检查 /status,并在几秒后重试 /abort。",
|
|
109
109
|
"stop.warn_maybe_finished": "⚠️ 事件流已停止,但代理可能已完成。",
|
|
110
110
|
"stop.success": "✅ 代理操作已中断。本次运行的后续消息将不再发送。",
|
|
111
111
|
"stop.warn_still_busy": "⚠️ 信号已发送,但代理仍在忙。\n\n事件流已禁用,因此不会发送中间消息。",
|
|
112
|
-
"stop.warn_timeout": "⚠️ 中止请求超时。\n\n事件流已禁用,请在几秒后重试 /
|
|
112
|
+
"stop.warn_timeout": "⚠️ 中止请求超时。\n\n事件流已禁用,请在几秒后重试 /abort。",
|
|
113
113
|
"stop.warn_local_only": "⚠️ 已在本地停止事件流,但服务器端中止失败。",
|
|
114
|
-
"stop.error": "🔴 停止操作失败。\n\n事件流已停止,请再次尝试 /
|
|
114
|
+
"stop.error": "🔴 停止操作失败。\n\n事件流已停止,请再次尝试 /abort。",
|
|
115
115
|
"opencode_start.already_running_managed": "⚠️ OpenCode 服务器已在运行\n\nPID:{pid}\n运行时间:{seconds} 秒",
|
|
116
116
|
"opencode_start.already_running_external": "✅ OpenCode 服务器正作为外部进程运行\n\n版本:{version}\n\n该服务器不是由机器人启动,因此 /opencode-stop 无法停止它。",
|
|
117
117
|
"opencode_start.starting": "🔄 正在启动 OpenCode 服务器...",
|
|
@@ -228,6 +228,8 @@ export const zh = {
|
|
|
228
228
|
"runtime.wizard.ask_user_id": "请输入你的 Telegram User ID(可从 @userinfobot 获取)。\n> ",
|
|
229
229
|
"runtime.wizard.user_id_invalid": "请输入一个正整数(> 0)。\n",
|
|
230
230
|
"runtime.wizard.ask_api_url": "请输入 OpenCode API URL(可选)。\n按 Enter 使用默认值:{defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "请输入 OpenCode 服务器用户名(可选)。\n按 Enter 使用默认值:{defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "请输入 OpenCode 服务器密码(可选)。\n按 Enter 保持为空。\n> ",
|
|
231
233
|
"runtime.wizard.api_url_invalid": "请输入有效 URL(http/https),或按 Enter 使用默认值。\n",
|
|
232
234
|
"runtime.wizard.start": "OpenCode Telegram Bot 设置。\n",
|
|
233
235
|
"runtime.wizard.saved": "配置已保存:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -73,6 +73,9 @@ export function resolveInteractionGuardDecision(ctx) {
|
|
|
73
73
|
return createBlockDecision(inputType, state, "expired", command);
|
|
74
74
|
}
|
|
75
75
|
if (inputType === "command") {
|
|
76
|
+
if (command === "/start") {
|
|
77
|
+
return createAllowDecision(inputType, state, command);
|
|
78
|
+
}
|
|
76
79
|
if (command && state.allowedCommands.includes(command)) {
|
|
77
80
|
return createAllowDecision(inputType, state, command);
|
|
78
81
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.js";
|
|
2
|
-
export const DEFAULT_ALLOWED_INTERACTION_COMMANDS = ["/help", "/status", "/
|
|
2
|
+
export const DEFAULT_ALLOWED_INTERACTION_COMMANDS = ["/help", "/status", "/abort"];
|
|
3
3
|
function normalizeCommand(command) {
|
|
4
4
|
const trimmed = command.trim().toLowerCase();
|
|
5
5
|
if (!trimmed) {
|
package/dist/model/manager.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { getCurrentModel, setCurrentModel } from "../settings/manager.js";
|
|
2
2
|
import { config } from "../config.js";
|
|
3
|
+
import { opencodeClient } from "../opencode/client.js";
|
|
3
4
|
import { logger } from "../utils/logger.js";
|
|
4
5
|
import path from "node:path";
|
|
6
|
+
const MODEL_CATALOG_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
7
|
+
let cachedValidModelKeys = null;
|
|
8
|
+
let modelCatalogCacheExpiresAt = 0;
|
|
9
|
+
let modelCatalogFetchInFlight = null;
|
|
10
|
+
function getModelKey(providerID, modelID) {
|
|
11
|
+
return `${providerID}/${modelID}`;
|
|
12
|
+
}
|
|
5
13
|
function getEnvDefaultModel() {
|
|
6
14
|
const providerID = config.opencode.model.provider;
|
|
7
15
|
const modelID = config.opencode.model.modelId;
|
|
@@ -20,6 +28,58 @@ function dedupeModels(models) {
|
|
|
20
28
|
}
|
|
21
29
|
return Array.from(unique.values());
|
|
22
30
|
}
|
|
31
|
+
function filterModelsByCatalog(models, validModelKeys) {
|
|
32
|
+
if (!validModelKeys) {
|
|
33
|
+
return models;
|
|
34
|
+
}
|
|
35
|
+
return models.filter((model) => validModelKeys.has(getModelKey(model.providerID, model.modelID)));
|
|
36
|
+
}
|
|
37
|
+
async function getValidModelKeys() {
|
|
38
|
+
if (cachedValidModelKeys && Date.now() < modelCatalogCacheExpiresAt) {
|
|
39
|
+
logger.debug(`[ModelManager] Model catalog cache hit: models=${cachedValidModelKeys.size}, ttlMs=${modelCatalogCacheExpiresAt - Date.now()}`);
|
|
40
|
+
return cachedValidModelKeys;
|
|
41
|
+
}
|
|
42
|
+
if (modelCatalogFetchInFlight) {
|
|
43
|
+
logger.debug("[ModelManager] Awaiting in-flight model catalog refresh");
|
|
44
|
+
return modelCatalogFetchInFlight;
|
|
45
|
+
}
|
|
46
|
+
modelCatalogFetchInFlight = (async () => {
|
|
47
|
+
try {
|
|
48
|
+
logger.debug("[ModelManager] Refreshing model catalog from OpenCode API");
|
|
49
|
+
const response = await opencodeClient.config.providers();
|
|
50
|
+
if (response.error || !response.data) {
|
|
51
|
+
logger.warn("[ModelManager] Failed to refresh model catalog:", response.error);
|
|
52
|
+
if (cachedValidModelKeys) {
|
|
53
|
+
logger.warn("[ModelManager] Using stale model catalog cache after refresh failure");
|
|
54
|
+
return cachedValidModelKeys;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const validModelKeys = new Set();
|
|
59
|
+
for (const provider of response.data.providers) {
|
|
60
|
+
for (const modelID of Object.keys(provider.models)) {
|
|
61
|
+
validModelKeys.add(getModelKey(provider.id, modelID));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
cachedValidModelKeys = validModelKeys;
|
|
65
|
+
modelCatalogCacheExpiresAt = Date.now() + MODEL_CATALOG_CACHE_TTL_MS;
|
|
66
|
+
logger.debug(`[ModelManager] Model catalog refreshed: providers=${response.data.providers.length}, models=${validModelKeys.size}`);
|
|
67
|
+
return cachedValidModelKeys;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
logger.warn("[ModelManager] Error refreshing model catalog:", err);
|
|
71
|
+
if (cachedValidModelKeys) {
|
|
72
|
+
logger.warn("[ModelManager] Using stale model catalog cache after refresh exception");
|
|
73
|
+
return cachedValidModelKeys;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
modelCatalogFetchInFlight = null;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
return modelCatalogFetchInFlight;
|
|
82
|
+
}
|
|
23
83
|
function normalizeFavoriteModels(state) {
|
|
24
84
|
if (!Array.isArray(state.favorite)) {
|
|
25
85
|
return [];
|
|
@@ -68,17 +128,27 @@ export async function getModelSelectionLists() {
|
|
|
68
128
|
const content = await fs.readFile(stateFilePath, "utf-8");
|
|
69
129
|
const state = JSON.parse(content);
|
|
70
130
|
const rawFavorites = normalizeFavoriteModels(state);
|
|
131
|
+
const rawRecent = normalizeRecentModels(state);
|
|
132
|
+
const shouldValidateWithCatalog = rawFavorites.length > 0 || rawRecent.length > 0;
|
|
133
|
+
const validModelKeys = shouldValidateWithCatalog ? await getValidModelKeys() : null;
|
|
134
|
+
const validatedFavorites = filterModelsByCatalog(rawFavorites, validModelKeys);
|
|
135
|
+
const validatedRecent = filterModelsByCatalog(rawRecent, validModelKeys);
|
|
71
136
|
const favorites = envDefaultModel
|
|
72
|
-
? dedupeModels([...
|
|
73
|
-
:
|
|
137
|
+
? dedupeModels([...validatedFavorites, envDefaultModel])
|
|
138
|
+
: validatedFavorites;
|
|
74
139
|
if (rawFavorites.length === 0 && envDefaultModel) {
|
|
75
140
|
logger.info(`[ModelManager] No favorites in ${stateFilePath}, using config model as favorite`);
|
|
76
141
|
}
|
|
77
142
|
if (favorites.length === 0) {
|
|
78
143
|
logger.warn(`[ModelManager] No favorites in ${stateFilePath}`);
|
|
79
144
|
}
|
|
80
|
-
const
|
|
81
|
-
const
|
|
145
|
+
const filteredOutFavorites = rawFavorites.length - validatedFavorites.length;
|
|
146
|
+
const filteredOutRecent = rawRecent.length - validatedRecent.length;
|
|
147
|
+
if (filteredOutFavorites > 0 || filteredOutRecent > 0) {
|
|
148
|
+
logger.info(`[ModelManager] Filtered unavailable models from OpenCode state: favoritesRemoved=${filteredOutFavorites}, recentRemoved=${filteredOutRecent}`);
|
|
149
|
+
}
|
|
150
|
+
const favoriteKeys = new Set(favorites.map((model) => getModelKey(model.providerID, model.modelID)));
|
|
151
|
+
const recent = dedupeModels(validatedRecent).filter((model) => !favoriteKeys.has(getModelKey(model.providerID, model.modelID)));
|
|
82
152
|
logger.debug(`[ModelManager] Loaded model selection lists from ${stateFilePath}: favorites=${favorites.length}, recent=${recent.length}`);
|
|
83
153
|
return { favorites, recent };
|
|
84
154
|
}
|
|
@@ -97,6 +167,42 @@ export async function getModelSelectionLists() {
|
|
|
97
167
|
};
|
|
98
168
|
}
|
|
99
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate stored selected model against OpenCode providers catalog.
|
|
172
|
+
* If selected model is unavailable, fallback to env default model.
|
|
173
|
+
*/
|
|
174
|
+
export async function reconcileStoredModelSelection() {
|
|
175
|
+
const currentModel = getCurrentModel();
|
|
176
|
+
if (!currentModel?.providerID || !currentModel.modelID) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const validModelKeys = await getValidModelKeys();
|
|
180
|
+
if (!validModelKeys) {
|
|
181
|
+
logger.warn("[ModelManager] Skipping stored model validation: model catalog unavailable");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const currentModelKey = getModelKey(currentModel.providerID, currentModel.modelID);
|
|
185
|
+
if (validModelKeys.has(currentModelKey)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const envDefaultModel = getEnvDefaultModel();
|
|
189
|
+
if (!envDefaultModel) {
|
|
190
|
+
logger.warn(`[ModelManager] Stored model ${currentModelKey} is unavailable and env default model is missing`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const fallbackKey = getModelKey(envDefaultModel.providerID, envDefaultModel.modelID);
|
|
194
|
+
logger.warn(`[ModelManager] Stored model ${currentModelKey} is unavailable, falling back to ${fallbackKey}`);
|
|
195
|
+
setCurrentModel({
|
|
196
|
+
providerID: envDefaultModel.providerID,
|
|
197
|
+
modelID: envDefaultModel.modelID,
|
|
198
|
+
variant: "default",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export function __resetModelCatalogCacheForTests() {
|
|
202
|
+
cachedValidModelKeys = null;
|
|
203
|
+
modelCatalogCacheExpiresAt = 0;
|
|
204
|
+
modelCatalogFetchInFlight = null;
|
|
205
|
+
}
|
|
100
206
|
/**
|
|
101
207
|
* Get list of favorite models from OpenCode local state file
|
|
102
208
|
* Falls back to env default model if file is unavailable or empty
|
package/dist/pinned/manager.js
CHANGED
|
@@ -128,7 +128,7 @@ class PinnedMessageManager {
|
|
|
128
128
|
*/
|
|
129
129
|
async onMessageComplete(tokens) {
|
|
130
130
|
// Ensure context limit is available even if session was restored
|
|
131
|
-
// without a fresh onSessionChange call (for example after /
|
|
131
|
+
// without a fresh onSessionChange call (for example after /abort + continue).
|
|
132
132
|
if (this.getContextLimit() === 0) {
|
|
133
133
|
await this.fetchContextLimit();
|
|
134
134
|
}
|
|
@@ -7,6 +7,7 @@ import dotenv from "dotenv";
|
|
|
7
7
|
import { getRuntimePaths } from "./paths.js";
|
|
8
8
|
import { getLocale, getLocaleOptions, resolveSupportedLocale, setRuntimeLocale, t, } from "../i18n/index.js";
|
|
9
9
|
const DEFAULT_API_URL = "http://localhost:4096";
|
|
10
|
+
const DEFAULT_SERVER_USERNAME = "opencode";
|
|
10
11
|
const FALLBACK_MODEL_PROVIDER = "opencode";
|
|
11
12
|
const FALLBACK_MODEL_ID = "big-pickle";
|
|
12
13
|
function isPositiveInteger(value) {
|
|
@@ -64,6 +65,8 @@ export function buildEnvFileContent(existingContent, values) {
|
|
|
64
65
|
["TELEGRAM_BOT_TOKEN", values.TELEGRAM_BOT_TOKEN],
|
|
65
66
|
["TELEGRAM_ALLOWED_USER_ID", values.TELEGRAM_ALLOWED_USER_ID],
|
|
66
67
|
["OPENCODE_API_URL", values.OPENCODE_API_URL],
|
|
68
|
+
["OPENCODE_SERVER_USERNAME", values.OPENCODE_SERVER_USERNAME],
|
|
69
|
+
["OPENCODE_SERVER_PASSWORD", values.OPENCODE_SERVER_PASSWORD],
|
|
67
70
|
["OPENCODE_MODEL_PROVIDER", values.OPENCODE_MODEL_PROVIDER],
|
|
68
71
|
["OPENCODE_MODEL_ID", values.OPENCODE_MODEL_ID],
|
|
69
72
|
];
|
|
@@ -242,6 +245,23 @@ async function askApiUrl() {
|
|
|
242
245
|
return apiUrl;
|
|
243
246
|
}
|
|
244
247
|
}
|
|
248
|
+
async function askServerUsername() {
|
|
249
|
+
const prompt = t("runtime.wizard.ask_server_username", {
|
|
250
|
+
defaultUsername: DEFAULT_SERVER_USERNAME,
|
|
251
|
+
});
|
|
252
|
+
const username = await askVisible(prompt);
|
|
253
|
+
if (!username) {
|
|
254
|
+
return DEFAULT_SERVER_USERNAME;
|
|
255
|
+
}
|
|
256
|
+
return username;
|
|
257
|
+
}
|
|
258
|
+
async function askServerPassword() {
|
|
259
|
+
const password = await askHidden(t("runtime.wizard.ask_server_password"));
|
|
260
|
+
if (!password) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return password;
|
|
264
|
+
}
|
|
245
265
|
async function collectWizardValues() {
|
|
246
266
|
const locale = await askLocale();
|
|
247
267
|
setRuntimeLocale(locale);
|
|
@@ -258,12 +278,16 @@ async function collectWizardValues() {
|
|
|
258
278
|
const token = await askToken();
|
|
259
279
|
const allowedUserId = await askAllowedUserId();
|
|
260
280
|
const apiUrl = await askApiUrl();
|
|
281
|
+
const serverUsername = await askServerUsername();
|
|
282
|
+
const serverPassword = await askServerPassword();
|
|
261
283
|
process.stdout.write("\n");
|
|
262
284
|
return {
|
|
263
285
|
locale,
|
|
264
286
|
token,
|
|
265
287
|
allowedUserId,
|
|
266
288
|
apiUrl,
|
|
289
|
+
serverUsername,
|
|
290
|
+
serverPassword,
|
|
267
291
|
};
|
|
268
292
|
}
|
|
269
293
|
function ensureInteractiveTty() {
|
|
@@ -294,6 +318,8 @@ async function runWizardAndPersist(runtimePaths) {
|
|
|
294
318
|
TELEGRAM_BOT_TOKEN: wizardValues.token,
|
|
295
319
|
TELEGRAM_ALLOWED_USER_ID: wizardValues.allowedUserId,
|
|
296
320
|
OPENCODE_API_URL: wizardValues.apiUrl,
|
|
321
|
+
OPENCODE_SERVER_USERNAME: wizardValues.serverUsername,
|
|
322
|
+
OPENCODE_SERVER_PASSWORD: wizardValues.serverPassword,
|
|
297
323
|
OPENCODE_MODEL_PROVIDER: provider,
|
|
298
324
|
OPENCODE_MODEL_ID: modelId,
|
|
299
325
|
};
|