@grinev/opencode-telegram-bot 0.10.0 → 0.10.1
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 +14 -8
- package/dist/agent/manager.js +10 -14
- package/dist/bot/commands/projects.js +102 -18
- package/dist/bot/commands/status.js +20 -1
- package/dist/bot/handlers/agent.js +20 -10
- package/dist/bot/message-patterns.js +1 -1
- package/dist/i18n/de.js +6 -0
- package/dist/i18n/en.js +6 -0
- package/dist/i18n/es.js +6 -0
- package/dist/i18n/ru.js +6 -0
- package/dist/i18n/zh.js +6 -0
- package/dist/model/types.js +4 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ Run AI coding tasks, monitor progress, switch models, and manage sessions from y
|
|
|
11
11
|
|
|
12
12
|
No open ports, no exposed APIs. The bot communicates with your local OpenCode server and the Telegram Bot API only.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Platforms: macOS, Windows, Linux
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Languages: English (`en`), Deutsch (`de`), Español (`es`), Русский (`ru`), 简体中文 (`zh`)
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
<img src="assets/screencast.gif" width="45%" alt="OpenCode Telegram Bot screencast" />
|
|
@@ -68,6 +68,8 @@ The fastest way — run directly with `npx`:
|
|
|
68
68
|
npx @grinev/opencode-telegram-bot
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
> 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.
|
|
72
|
+
|
|
71
73
|
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 API URL. After that, you're ready to go. Open your bot in Telegram and start sending tasks.
|
|
72
74
|
|
|
73
75
|
#### Alternative: Global Install
|
|
@@ -85,11 +87,11 @@ opencode-telegram config
|
|
|
85
87
|
|
|
86
88
|
## Supported Platforms
|
|
87
89
|
|
|
88
|
-
| Platform | Status
|
|
89
|
-
| -------- |
|
|
90
|
-
| macOS | Fully supported
|
|
91
|
-
| Windows | Fully supported
|
|
92
|
-
| Linux |
|
|
90
|
+
| Platform | Status |
|
|
91
|
+
| -------- | -------------------------------------------- |
|
|
92
|
+
| macOS | Fully supported |
|
|
93
|
+
| Windows | Fully supported |
|
|
94
|
+
| Linux | Fully supported (tested on Ubuntu 24.04 LTS) |
|
|
93
95
|
|
|
94
96
|
## Bot Commands
|
|
95
97
|
|
|
@@ -137,7 +139,7 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
137
139
|
| `OPENCODE_MODEL_ID` | Default model ID | Yes | `big-pickle` |
|
|
138
140
|
| `BOT_LOCALE` | Bot UI language (supported locale code, e.g. `en`, `de`, `es`, `ru`, `zh`) | No | `en` |
|
|
139
141
|
| `SESSIONS_LIST_LIMIT` | Sessions per page in `/sessions` | No | `10` |
|
|
140
|
-
| `PROJECTS_LIST_LIMIT` |
|
|
142
|
+
| `PROJECTS_LIST_LIMIT` | Projects per page in `/projects` | No | `10` |
|
|
141
143
|
| `SERVICE_MESSAGES_INTERVAL_SEC` | Service messages interval (thinking + tool calls); keep `>=2` to avoid Telegram rate limits, `0` = immediate | No | `5` |
|
|
142
144
|
| `HIDE_THINKING_MESSAGES` | Hide `💭 Thinking...` service messages | No | `false` |
|
|
143
145
|
| `HIDE_TOOL_CALL_MESSAGES` | Hide tool-call service messages (`💻 bash ...`, `📖 read ...`, etc.) | No | `false` |
|
|
@@ -250,6 +252,10 @@ npm run dev
|
|
|
250
252
|
|
|
251
253
|
Please follow commit and release note conventions in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
252
254
|
|
|
255
|
+
## Community
|
|
256
|
+
|
|
257
|
+
Have questions, want to share your experience using the bot, or have an idea for a feature? Join the conversation in [GitHub Discussions](https://github.com/grinev/opencode-telegram-bot/discussions).
|
|
258
|
+
|
|
253
259
|
## License
|
|
254
260
|
|
|
255
261
|
[MIT](LICENSE) © Ruslan Grinev
|
package/dist/agent/manager.js
CHANGED
|
@@ -8,15 +8,9 @@ import { logger } from "../utils/logger.js";
|
|
|
8
8
|
* @returns Array of available agents (filtered by mode and hidden flag)
|
|
9
9
|
*/
|
|
10
10
|
export async function getAvailableAgents() {
|
|
11
|
-
const project = getCurrentProject();
|
|
12
|
-
if (!project) {
|
|
13
|
-
logger.warn("[AgentManager] Cannot get agents: no project selected");
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
11
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
});
|
|
12
|
+
const project = getCurrentProject();
|
|
13
|
+
const { data: agents, error } = await opencodeClient.app.agents(project ? { directory: project.worktree } : undefined);
|
|
20
14
|
if (error) {
|
|
21
15
|
logger.error("[AgentManager] Failed to fetch agents:", error);
|
|
22
16
|
return [];
|
|
@@ -34,9 +28,11 @@ export async function getAvailableAgents() {
|
|
|
34
28
|
return [];
|
|
35
29
|
}
|
|
36
30
|
}
|
|
31
|
+
const DEFAULT_AGENT = "build";
|
|
37
32
|
/**
|
|
38
|
-
* Get current agent from last session message or settings
|
|
39
|
-
*
|
|
33
|
+
* Get current agent from last session message or settings.
|
|
34
|
+
* Falls back to "build" if nothing is stored.
|
|
35
|
+
* @returns Current agent name
|
|
40
36
|
*/
|
|
41
37
|
export async function fetchCurrentAgent() {
|
|
42
38
|
const storedAgent = getCurrentAgent();
|
|
@@ -44,7 +40,7 @@ export async function fetchCurrentAgent() {
|
|
|
44
40
|
const project = getCurrentProject();
|
|
45
41
|
if (!session || !project) {
|
|
46
42
|
// No active session, return stored agent from settings
|
|
47
|
-
return storedAgent;
|
|
43
|
+
return storedAgent ?? DEFAULT_AGENT;
|
|
48
44
|
}
|
|
49
45
|
try {
|
|
50
46
|
const { data: messages, error } = await opencodeClient.session.messages({
|
|
@@ -54,7 +50,7 @@ export async function fetchCurrentAgent() {
|
|
|
54
50
|
});
|
|
55
51
|
if (error || !messages || messages.length === 0) {
|
|
56
52
|
logger.debug("[AgentManager] No messages found, using stored agent");
|
|
57
|
-
return storedAgent;
|
|
53
|
+
return storedAgent ?? DEFAULT_AGENT;
|
|
58
54
|
}
|
|
59
55
|
const lastAgent = messages[0].info.agent;
|
|
60
56
|
logger.debug(`[AgentManager] Current agent from session: ${lastAgent}`);
|
|
@@ -68,11 +64,11 @@ export async function fetchCurrentAgent() {
|
|
|
68
64
|
if (lastAgent && lastAgent !== storedAgent) {
|
|
69
65
|
setCurrentAgent(lastAgent);
|
|
70
66
|
}
|
|
71
|
-
return lastAgent || storedAgent;
|
|
67
|
+
return lastAgent || storedAgent || DEFAULT_AGENT;
|
|
72
68
|
}
|
|
73
69
|
catch (err) {
|
|
74
70
|
logger.error("[AgentManager] Error fetching current agent:", err);
|
|
75
|
-
return storedAgent;
|
|
71
|
+
return storedAgent ?? DEFAULT_AGENT;
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
/**
|
|
@@ -11,11 +11,12 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
11
11
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
12
12
|
import { clearAllInteractionState } from "../../interaction/cleanup.js";
|
|
13
13
|
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
14
|
-
import { ensureActiveInlineMenu, replyWithInlineMenu } from "../handlers/inline-menu.js";
|
|
14
|
+
import { appendInlineMenuCancelButton, ensureActiveInlineMenu, replyWithInlineMenu, } from "../handlers/inline-menu.js";
|
|
15
15
|
import { logger } from "../../utils/logger.js";
|
|
16
16
|
import { t } from "../../i18n/index.js";
|
|
17
17
|
import { config } from "../../config.js";
|
|
18
18
|
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
19
|
+
const PROJECT_PAGE_CALLBACK_PREFIX = "projects:page:";
|
|
19
20
|
function formatProjectButtonLabel(label, isActive) {
|
|
20
21
|
const prefix = isActive ? "✅ " : "";
|
|
21
22
|
const availableLength = MAX_INLINE_BUTTON_LABEL_LENGTH - prefix.length;
|
|
@@ -36,29 +37,84 @@ export function buildProjectButtonLabel(index, worktree) {
|
|
|
36
37
|
const folderName = getProjectFolderName(worktree);
|
|
37
38
|
return `${index + 1}. ${folderName} [${worktree}]`;
|
|
38
39
|
}
|
|
40
|
+
export function parseProjectPageCallback(data) {
|
|
41
|
+
if (!data.startsWith(PROJECT_PAGE_CALLBACK_PREFIX)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const rawPage = data.slice(PROJECT_PAGE_CALLBACK_PREFIX.length);
|
|
45
|
+
if (!/^\d+$/.test(rawPage)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return Number.parseInt(rawPage, 10);
|
|
49
|
+
}
|
|
50
|
+
export function calculateProjectsPaginationRange(totalProjects, page, pageSize) {
|
|
51
|
+
const safePageSize = Math.max(1, pageSize);
|
|
52
|
+
const totalPages = Math.max(1, Math.ceil(totalProjects / safePageSize));
|
|
53
|
+
const normalizedPage = Math.min(Math.max(0, page), totalPages - 1);
|
|
54
|
+
const startIndex = normalizedPage * safePageSize;
|
|
55
|
+
const endIndex = Math.min(startIndex + safePageSize, totalProjects);
|
|
56
|
+
return {
|
|
57
|
+
page: normalizedPage,
|
|
58
|
+
totalPages,
|
|
59
|
+
startIndex,
|
|
60
|
+
endIndex,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function buildProjectsMenuText(currentProjectName, page, totalPages) {
|
|
64
|
+
const baseText = currentProjectName
|
|
65
|
+
? t("projects.select_with_current", {
|
|
66
|
+
project: currentProjectName,
|
|
67
|
+
})
|
|
68
|
+
: t("projects.select");
|
|
69
|
+
if (totalPages <= 1) {
|
|
70
|
+
return baseText;
|
|
71
|
+
}
|
|
72
|
+
return `${baseText}\n\n${t("projects.page_indicator", {
|
|
73
|
+
current: String(page + 1),
|
|
74
|
+
total: String(totalPages),
|
|
75
|
+
})}`;
|
|
76
|
+
}
|
|
77
|
+
function buildProjectsKeyboard(projects, page) {
|
|
78
|
+
const keyboard = new InlineKeyboard();
|
|
79
|
+
const currentProject = getCurrentProject();
|
|
80
|
+
const pageSize = config.bot.projectsListLimit;
|
|
81
|
+
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateProjectsPaginationRange(projects.length, page, pageSize);
|
|
82
|
+
projects.slice(startIndex, endIndex).forEach((project, index) => {
|
|
83
|
+
const isActive = currentProject &&
|
|
84
|
+
(project.id === currentProject.id || project.worktree === currentProject.worktree);
|
|
85
|
+
const label = buildProjectButtonLabel(startIndex + index, project.worktree);
|
|
86
|
+
const labelWithCheck = formatProjectButtonLabel(label, Boolean(isActive));
|
|
87
|
+
keyboard.text(labelWithCheck, `project:${project.id}`).row();
|
|
88
|
+
});
|
|
89
|
+
if (totalPages > 1) {
|
|
90
|
+
if (normalizedPage > 0) {
|
|
91
|
+
keyboard.text(t("projects.prev_page"), `${PROJECT_PAGE_CALLBACK_PREFIX}${normalizedPage - 1}`);
|
|
92
|
+
}
|
|
93
|
+
if (normalizedPage < totalPages - 1) {
|
|
94
|
+
keyboard.text(t("projects.next_page"), `${PROJECT_PAGE_CALLBACK_PREFIX}${normalizedPage + 1}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return keyboard;
|
|
98
|
+
}
|
|
99
|
+
function buildProjectsMenuView(projects, page) {
|
|
100
|
+
const currentProject = getCurrentProject();
|
|
101
|
+
const pageSize = config.bot.projectsListLimit;
|
|
102
|
+
const { page: normalizedPage, totalPages } = calculateProjectsPaginationRange(projects.length, page, pageSize);
|
|
103
|
+
const currentProjectName = currentProject?.name || currentProject?.worktree || null;
|
|
104
|
+
return {
|
|
105
|
+
text: buildProjectsMenuText(currentProjectName, normalizedPage, totalPages),
|
|
106
|
+
keyboard: buildProjectsKeyboard(projects, normalizedPage),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
39
109
|
export async function projectsCommand(ctx) {
|
|
40
110
|
try {
|
|
41
111
|
await syncSessionDirectoryCache();
|
|
42
112
|
const projects = await getProjects();
|
|
43
|
-
|
|
44
|
-
if (projectsToShow.length === 0) {
|
|
113
|
+
if (projects.length === 0) {
|
|
45
114
|
await ctx.reply(t("projects.empty"));
|
|
46
115
|
return;
|
|
47
116
|
}
|
|
48
|
-
const keyboard =
|
|
49
|
-
const currentProject = getCurrentProject();
|
|
50
|
-
projectsToShow.forEach((project, index) => {
|
|
51
|
-
const isActive = currentProject &&
|
|
52
|
-
(project.id === currentProject.id || project.worktree === currentProject.worktree);
|
|
53
|
-
const label = buildProjectButtonLabel(index, project.worktree);
|
|
54
|
-
const labelWithCheck = formatProjectButtonLabel(label, Boolean(isActive));
|
|
55
|
-
keyboard.text(labelWithCheck, `project:${project.id}`).row();
|
|
56
|
-
});
|
|
57
|
-
const text = currentProject
|
|
58
|
-
? t("projects.select_with_current", {
|
|
59
|
-
project: currentProject.name || currentProject.worktree,
|
|
60
|
-
})
|
|
61
|
-
: t("projects.select");
|
|
117
|
+
const { text, keyboard } = buildProjectsMenuView(projects, 0);
|
|
62
118
|
await replyWithInlineMenu(ctx, {
|
|
63
119
|
menuKind: "project",
|
|
64
120
|
text,
|
|
@@ -72,7 +128,35 @@ export async function projectsCommand(ctx) {
|
|
|
72
128
|
}
|
|
73
129
|
export async function handleProjectSelect(ctx) {
|
|
74
130
|
const callbackQuery = ctx.callbackQuery;
|
|
75
|
-
if (!callbackQuery?.data
|
|
131
|
+
if (!callbackQuery?.data) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const page = parseProjectPageCallback(callbackQuery.data);
|
|
135
|
+
if (page !== null) {
|
|
136
|
+
const isActiveMenu = await ensureActiveInlineMenu(ctx, "project");
|
|
137
|
+
if (!isActiveMenu) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const projects = await getProjects();
|
|
142
|
+
if (projects.length === 0) {
|
|
143
|
+
await ctx.answerCallbackQuery();
|
|
144
|
+
await ctx.reply(t("projects.empty"));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const { text, keyboard } = buildProjectsMenuView(projects, page);
|
|
148
|
+
await ctx.answerCallbackQuery();
|
|
149
|
+
await ctx.editMessageText(text, {
|
|
150
|
+
reply_markup: appendInlineMenuCancelButton(keyboard, "project"),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
logger.error("[Bot] Error switching projects page:", error);
|
|
155
|
+
await ctx.answerCallbackQuery({ text: t("projects.page_load_error") });
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (!callbackQuery.data.startsWith("project:")) {
|
|
76
160
|
return false;
|
|
77
161
|
}
|
|
78
162
|
const projectId = callbackQuery.data.replace("project:", "");
|
|
@@ -6,6 +6,8 @@ import { getAgentDisplayName } from "../../agent/types.js";
|
|
|
6
6
|
import { fetchCurrentModel } from "../../model/manager.js";
|
|
7
7
|
import { formatModelForDisplay } from "../../model/types.js";
|
|
8
8
|
import { processManager } from "../../process/manager.js";
|
|
9
|
+
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
|
+
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
9
11
|
import { logger } from "../../utils/logger.js";
|
|
10
12
|
import { t } from "../../i18n/index.js";
|
|
11
13
|
import { sendMessageWithMarkdownFallback } from "../utils/send-with-markdown-fallback.js";
|
|
@@ -59,16 +61,33 @@ export async function statusCommand(ctx) {
|
|
|
59
61
|
message += `\n${t("status.session_not_selected")}\n`;
|
|
60
62
|
message += t("status.session_hint");
|
|
61
63
|
}
|
|
64
|
+
if (ctx.chat) {
|
|
65
|
+
if (!pinnedMessageManager.isInitialized()) {
|
|
66
|
+
pinnedMessageManager.initialize(ctx.api, ctx.chat.id);
|
|
67
|
+
}
|
|
68
|
+
// Fetch context limit if not yet loaded (e.g. fresh bot start)
|
|
69
|
+
if (pinnedMessageManager.getContextLimit() === 0) {
|
|
70
|
+
await pinnedMessageManager.refreshContextLimit();
|
|
71
|
+
}
|
|
72
|
+
keyboardManager.initialize(ctx.api, ctx.chat.id);
|
|
73
|
+
}
|
|
74
|
+
// Sync current context (tokens used + limit) into keyboard state
|
|
75
|
+
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
76
|
+
if (contextInfo) {
|
|
77
|
+
keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
|
|
78
|
+
}
|
|
79
|
+
const keyboard = keyboardManager.getKeyboard();
|
|
62
80
|
if (ctx.chat) {
|
|
63
81
|
await sendMessageWithMarkdownFallback({
|
|
64
82
|
api: ctx.api,
|
|
65
83
|
chatId: ctx.chat.id,
|
|
66
84
|
text: message,
|
|
85
|
+
options: { reply_markup: keyboard },
|
|
67
86
|
parseMode: "Markdown",
|
|
68
87
|
});
|
|
69
88
|
}
|
|
70
89
|
else {
|
|
71
|
-
await ctx.reply(message);
|
|
90
|
+
await ctx.reply(message, { reply_markup: keyboard });
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
catch (error) {
|
|
@@ -95,14 +95,24 @@ export async function buildAgentSelectionMenu(currentAgent) {
|
|
|
95
95
|
* @param ctx grammY context
|
|
96
96
|
*/
|
|
97
97
|
export async function showAgentSelectionMenu(ctx) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
text
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
try {
|
|
99
|
+
const currentAgent = await fetchCurrentAgent();
|
|
100
|
+
const keyboard = await buildAgentSelectionMenu(currentAgent);
|
|
101
|
+
if (keyboard.inline_keyboard.length === 0) {
|
|
102
|
+
await ctx.reply(t("agent.menu.empty"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const text = currentAgent
|
|
106
|
+
? t("agent.menu.current", { name: getAgentDisplayName(currentAgent) })
|
|
107
|
+
: t("agent.menu.select");
|
|
108
|
+
await replyWithInlineMenu(ctx, {
|
|
109
|
+
menuKind: "agent",
|
|
110
|
+
text,
|
|
111
|
+
keyboard,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
logger.error("[AgentHandler] Error showing agent menu:", err);
|
|
116
|
+
await ctx.reply(t("agent.menu.error"));
|
|
117
|
+
}
|
|
108
118
|
}
|
package/dist/i18n/de.js
CHANGED
|
@@ -70,7 +70,11 @@ export const de = {
|
|
|
70
70
|
"projects.empty": "📭 Keine Projekte gefunden.\n\nÖffne ein Verzeichnis in OpenCode und erstelle mindestens eine Sitzung, dann erscheint es hier.",
|
|
71
71
|
"projects.select": "Projekt auswählen:",
|
|
72
72
|
"projects.select_with_current": "Projekt auswählen:\n\nAktuell: 🏗 {project}",
|
|
73
|
+
"projects.page_indicator": "Seite {current}/{total}",
|
|
74
|
+
"projects.prev_page": "⬅️ Zurück",
|
|
75
|
+
"projects.next_page": "Weiter ➡️",
|
|
73
76
|
"projects.fetch_error": "🔴 OpenCode-Server ist nicht verfügbar oder beim Laden der Projekte ist ein Fehler aufgetreten.",
|
|
77
|
+
"projects.page_load_error": "Diese Seite konnte nicht geladen werden. Bitte versuche es erneut.",
|
|
74
78
|
"projects.selected": "✅ Projekt ausgewählt: {project}\n\n📋 Sitzung wurde zurückgesetzt. Nutze /sessions oder /new für dieses Projekt.",
|
|
75
79
|
"projects.select_error": "🔴 Projekt konnte nicht ausgewählt werden.",
|
|
76
80
|
"sessions.project_not_selected": "🏗 Projekt ist nicht ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
|
|
@@ -120,6 +124,8 @@ export const de = {
|
|
|
120
124
|
"agent.change_error_callback": "Modus konnte nicht geändert werden",
|
|
121
125
|
"agent.menu.current": "Aktueller Modus: {name}\n\nModus auswählen:",
|
|
122
126
|
"agent.menu.select": "Arbeitsmodus auswählen:",
|
|
127
|
+
"agent.menu.empty": "⚠️ Keine verfügbaren Agenten",
|
|
128
|
+
"agent.menu.error": "🔴 Agentenliste konnte nicht geladen werden",
|
|
123
129
|
"model.changed_callback": "Modell geändert: {name}",
|
|
124
130
|
"model.changed_message": "✅ Modell geändert zu: {name}",
|
|
125
131
|
"model.change_error_callback": "Modell konnte nicht geändert werden",
|
package/dist/i18n/en.js
CHANGED
|
@@ -70,7 +70,11 @@ export const en = {
|
|
|
70
70
|
"projects.empty": "📭 No projects found.\n\nOpen a directory in OpenCode and create at least one session, then it will appear here.",
|
|
71
71
|
"projects.select": "Select a project:",
|
|
72
72
|
"projects.select_with_current": "Select a project:\n\nCurrent: 🏗 {project}",
|
|
73
|
+
"projects.page_indicator": "Page {current}/{total}",
|
|
74
|
+
"projects.prev_page": "⬅️ Previous",
|
|
75
|
+
"projects.next_page": "Next ➡️",
|
|
73
76
|
"projects.fetch_error": "🔴 OpenCode Server is unavailable or an error occurred while loading projects.",
|
|
77
|
+
"projects.page_load_error": "Cannot load this page. Please try again.",
|
|
74
78
|
"projects.selected": "✅ Project selected: {project}\n\n📋 Session was reset. Use /sessions or /new for this project.",
|
|
75
79
|
"projects.select_error": "🔴 Failed to select project.",
|
|
76
80
|
"sessions.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
|
|
@@ -120,6 +124,8 @@ export const en = {
|
|
|
120
124
|
"agent.change_error_callback": "Failed to change mode",
|
|
121
125
|
"agent.menu.current": "Current mode: {name}\n\nSelect mode:",
|
|
122
126
|
"agent.menu.select": "Select work mode:",
|
|
127
|
+
"agent.menu.empty": "⚠️ No available agents",
|
|
128
|
+
"agent.menu.error": "🔴 Failed to get agents list",
|
|
123
129
|
"model.changed_callback": "Model changed: {name}",
|
|
124
130
|
"model.changed_message": "✅ Model changed to: {name}",
|
|
125
131
|
"model.change_error_callback": "Failed to change model",
|
package/dist/i18n/es.js
CHANGED
|
@@ -70,7 +70,11 @@ export const es = {
|
|
|
70
70
|
"projects.empty": "📭 No se encontraron proyectos.\n\nAbre un directorio en OpenCode y crea al menos una sesión; entonces aparecerá aquí.",
|
|
71
71
|
"projects.select": "Selecciona un proyecto:",
|
|
72
72
|
"projects.select_with_current": "Selecciona un proyecto:\n\nActual: 🏗 {project}",
|
|
73
|
+
"projects.page_indicator": "Página {current}/{total}",
|
|
74
|
+
"projects.prev_page": "⬅️ Anterior",
|
|
75
|
+
"projects.next_page": "Siguiente ➡️",
|
|
73
76
|
"projects.fetch_error": "🔴 OpenCode Server no está disponible u ocurrió un error al cargar los proyectos.",
|
|
77
|
+
"projects.page_load_error": "No se puede cargar esta página. Inténtalo de nuevo.",
|
|
74
78
|
"projects.selected": "✅ Proyecto seleccionado: {project}\n\n📋 La sesión se reinició. Usa /sessions o /new para este proyecto.",
|
|
75
79
|
"projects.select_error": "🔴 No se pudo seleccionar el proyecto.",
|
|
76
80
|
"sessions.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
|
|
@@ -120,6 +124,8 @@ export const es = {
|
|
|
120
124
|
"agent.change_error_callback": "No se pudo cambiar el modo",
|
|
121
125
|
"agent.menu.current": "Modo actual: {name}\n\nSelecciona el modo:",
|
|
122
126
|
"agent.menu.select": "Selecciona el modo de trabajo:",
|
|
127
|
+
"agent.menu.empty": "⚠️ No hay agentes disponibles",
|
|
128
|
+
"agent.menu.error": "🔴 No se pudo obtener la lista de agentes",
|
|
123
129
|
"model.changed_callback": "Modelo cambiado: {name}",
|
|
124
130
|
"model.changed_message": "✅ Modelo cambiado a: {name}",
|
|
125
131
|
"model.change_error_callback": "No se pudo cambiar el modelo",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -70,7 +70,11 @@ export const ru = {
|
|
|
70
70
|
"projects.empty": "📭 Проектов нет.\n\nОткройте директорию в OpenCode и создайте хотя бы одну сессию, после этого она появится здесь.",
|
|
71
71
|
"projects.select": "Выберите проект:",
|
|
72
72
|
"projects.select_with_current": "Выберите проект:\n\nТекущий: 🏗 {project}",
|
|
73
|
+
"projects.page_indicator": "Страница {current}/{total}",
|
|
74
|
+
"projects.prev_page": "⬅️ Назад",
|
|
75
|
+
"projects.next_page": "Вперёд ➡️",
|
|
73
76
|
"projects.fetch_error": "🔴 OpenCode Server недоступен или произошла ошибка при получении списка проектов.",
|
|
77
|
+
"projects.page_load_error": "Не удалось загрузить эту страницу. Попробуйте снова.",
|
|
74
78
|
"projects.selected": "✅ Проект выбран: {project}\n\n📋 Сессия сброшена. Используйте /sessions или /new для работы с этим проектом.",
|
|
75
79
|
"projects.select_error": "🔴 Ошибка при выборе проекта.",
|
|
76
80
|
"sessions.project_not_selected": "🏗 Проект не выбран.\n\nСначала выберите проект командой /projects.",
|
|
@@ -120,6 +124,8 @@ export const ru = {
|
|
|
120
124
|
"agent.change_error_callback": "Ошибка при смене режима",
|
|
121
125
|
"agent.menu.current": "Текущий режим: {name}\n\nВыберите режим:",
|
|
122
126
|
"agent.menu.select": "Выберите режим работы:",
|
|
127
|
+
"agent.menu.empty": "⚠️ Нет доступных агентов",
|
|
128
|
+
"agent.menu.error": "🔴 Не удалось получить список агентов",
|
|
123
129
|
"model.changed_callback": "Модель изменена: {name}",
|
|
124
130
|
"model.changed_message": "✅ Модель изменена на: {name}",
|
|
125
131
|
"model.change_error_callback": "Ошибка при смене модели",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -70,7 +70,11 @@ export const zh = {
|
|
|
70
70
|
"projects.empty": "📭 未找到项目。\n\n在 OpenCode 中打开一个目录并至少创建一个会话,然后它会出现在这里。",
|
|
71
71
|
"projects.select": "请选择一个项目:",
|
|
72
72
|
"projects.select_with_current": "请选择一个项目:\n\n当前:🏗 {project}",
|
|
73
|
+
"projects.page_indicator": "第 {current}/{total} 页",
|
|
74
|
+
"projects.prev_page": "⬅️ 上一页",
|
|
75
|
+
"projects.next_page": "下一页 ➡️",
|
|
73
76
|
"projects.fetch_error": "🔴 OpenCode 服务器不可用,或加载项目时发生错误。",
|
|
77
|
+
"projects.page_load_error": "无法加载此页面。请重试。",
|
|
74
78
|
"projects.selected": "✅ 已选择项目:{project}\n\n📋 会话已重置。请在此项目中使用 /sessions 或 /new。",
|
|
75
79
|
"projects.select_error": "🔴 选择项目失败。",
|
|
76
80
|
"sessions.project_not_selected": "🏗 未选择项目。\n\n请先使用 /projects 选择一个项目。",
|
|
@@ -120,6 +124,8 @@ export const zh = {
|
|
|
120
124
|
"agent.change_error_callback": "切换模式失败",
|
|
121
125
|
"agent.menu.current": "当前模式:{name}\n\n请选择模式:",
|
|
122
126
|
"agent.menu.select": "请选择工作模式:",
|
|
127
|
+
"agent.menu.empty": "⚠️ 没有可用的代理",
|
|
128
|
+
"agent.menu.error": "🔴 获取代理列表失败",
|
|
123
129
|
"model.changed_callback": "模型已更改:{name}",
|
|
124
130
|
"model.changed_message": "✅ 模型已切换为:{name}",
|
|
125
131
|
"model.change_error_callback": "切换模型失败",
|
package/dist/model/types.js
CHANGED
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
* @returns Formatted string "providerID/modelID"
|
|
9
9
|
*/
|
|
10
10
|
export function formatModelForButton(providerID, modelID) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return `🤖 ${formatted}`;
|
|
11
|
+
// If model name is too long, we only truncate the model part
|
|
12
|
+
const displayModelId = modelID.length > 20 ? `${modelID.substring(0, 17)}...` : modelID;
|
|
13
|
+
const displayProviderId = providerID.length > 15 ? `${providerID.substring(0, 12)}...` : providerID;
|
|
14
|
+
return `🤖 ${displayProviderId}\n${displayModelId}`;
|
|
17
15
|
}
|
|
18
16
|
/**
|
|
19
17
|
* Format model for display in messages (full format)
|