@grinev/opencode-telegram-bot 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -15
- package/README.md +33 -13
- package/dist/app/{start-bot-app.js → bootstrap/start-bot-app.js} +16 -15
- package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js} +1 -1
- package/dist/{summary → app/formatters}/subagent-formatter.js +8 -3
- package/dist/{summary/formatter.js → app/formatters/summary-formatter.js} +20 -132
- package/dist/{summary → app/formatters}/tool-message-batcher.js +1 -1
- package/dist/{bot/assistant-run-state.js → app/managers/assistant-run-state-manager.js} +1 -1
- package/dist/{attach/manager.js → app/managers/attach-manager.js} +1 -1
- package/dist/{background-session/tracker.js → app/managers/background-session-manager.js} +2 -2
- package/dist/{scheduled-task/foreground-state.js → app/managers/foreground-session-state-manager.js} +1 -1
- package/dist/{interaction/manager.js → app/managers/interaction-manager.js} +31 -1
- package/dist/{permission/manager.js → app/managers/permission-manager.js} +1 -1
- package/dist/{question/manager.js → app/managers/question-manager.js} +1 -1
- package/dist/{rename/manager.js → app/managers/rename-manager.js} +1 -1
- package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js} +2 -2
- package/dist/{summary/aggregator.js → app/managers/summary-aggregation-manager.js} +177 -13
- package/dist/{agent/manager.js → app/services/agent-selection-service.js} +4 -5
- package/dist/{attach/service.js → app/services/attach-service.js} +23 -44
- package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js} +12 -7
- package/dist/app/services/command-catalog-service.js +21 -0
- package/dist/{bot/utils/external-user-input.js → app/services/external-user-input-service.js} +4 -19
- package/dist/app/services/file-browser-service.js +265 -0
- package/dist/{bot/utils/file-download.js → app/services/file-download-service.js} +14 -28
- package/dist/app/services/mcp-catalog-service.js +79 -0
- package/dist/app/services/message-history-service.js +81 -0
- package/dist/{model/capabilities.js → app/services/model-capabilities-service.js} +2 -2
- package/dist/{model/context-limit.js → app/services/model-context-limit-service.js} +4 -4
- package/dist/{model/manager.js → app/services/model-selection-service.js} +4 -4
- package/dist/{project/manager.js → app/services/project-service.js} +3 -3
- package/dist/app/services/project-switch-service.js +39 -0
- package/dist/{bot/utils/busy-guard.js → app/services/run-control-service.js} +3 -14
- package/dist/{scheduled-task/executor.js → app/services/scheduled-task-executor-service.js} +27 -12
- package/dist/{scheduled-task/runtime.js → app/services/scheduled-task-runtime-service.js} +17 -56
- package/dist/{scheduled-task/schedule-parser.js → app/services/scheduled-task-schedule-parser-service.js} +3 -3
- package/dist/{scheduled-task/session-ignore.js → app/services/scheduled-task-session-ignore-service.js} +1 -1
- package/dist/{session/cache-manager.js → app/services/session-cache-service.js} +3 -3
- package/dist/{session/manager.js → app/services/session-service.js} +1 -1
- package/dist/app/services/skills-catalog-service.js +21 -0
- package/dist/{stt/client.js → app/services/stt-service.js} +2 -2
- package/dist/{tts/client.js → app/services/tts-service.js} +68 -5
- package/dist/{variant/manager.js → app/services/variant-selection-service.js} +3 -3
- package/dist/{scheduled-task/store.js → app/stores/scheduled-task-store.js} +3 -3
- package/dist/{settings/manager.js → app/stores/settings-store.js} +49 -7
- package/dist/app/types/worktree.js +1 -0
- package/dist/bot/{handlers/agent.js → callbacks/agent-selection-callback-handler.js} +8 -57
- package/dist/bot/callbacks/callback-router.js +113 -0
- package/dist/bot/{commands/commands.js → callbacks/command-catalog-callback-handler.js} +17 -197
- package/dist/bot/{handlers/context.js → callbacks/context-control-callback-handler.js} +3 -32
- package/dist/bot/callbacks/file-browser-callback-handler.js +219 -0
- package/dist/bot/callbacks/inline-menu-cancel-callback-handler.js +32 -0
- package/dist/bot/{commands/mcps.js → callbacks/mcp-catalog-callback-handler.js} +7 -172
- package/dist/bot/{commands/messages.js → callbacks/message-history-callback-handler.js} +17 -248
- package/dist/bot/{handlers/model.js → callbacks/model-selection-callback-handler.js} +10 -71
- package/dist/bot/callbacks/permission-callback-handler.js +114 -0
- package/dist/bot/callbacks/project-callback-handler.js +79 -0
- package/dist/bot/callbacks/question-callback-handler.js +159 -0
- package/dist/bot/{commands/rename.js → callbacks/rename-callback-handler.js} +6 -34
- package/dist/bot/{commands/tasklist.js → callbacks/scheduled-task-callback-handler.js} +83 -68
- package/dist/bot/{commands/sessions.js → callbacks/session-callback-handler.js} +19 -158
- package/dist/bot/callbacks/settings-callback-handler.js +105 -0
- package/dist/bot/{commands/skills.js → callbacks/skills-catalog-callback-handler.js} +8 -188
- package/dist/bot/{handlers/variant.js → callbacks/variant-selection-callback-handler.js} +7 -69
- package/dist/bot/callbacks/worktree-callback-handler.js +97 -0
- package/dist/bot/commands/{abort.js → abort-command.js} +6 -6
- package/dist/bot/commands/command-catalog-command.js +42 -0
- package/dist/bot/commands/definitions.js +1 -1
- package/dist/bot/commands/{detach.js → detach-command.js} +8 -8
- package/dist/bot/commands/ls-command.js +48 -0
- package/dist/bot/commands/mcp-catalog-command.js +39 -0
- package/dist/bot/commands/messages-command.js +59 -0
- package/dist/bot/commands/{new.js → new-command.js} +12 -11
- package/dist/bot/commands/open-command.js +45 -0
- package/dist/bot/commands/{opencode-start.js → opencode-start-command.js} +1 -1
- package/dist/bot/commands/{opencode-stop.js → opencode-stop-command.js} +1 -1
- package/dist/bot/commands/projects-command.js +32 -0
- package/dist/bot/commands/rename-command.js +33 -0
- package/dist/bot/commands/sessions-command.js +42 -0
- package/dist/bot/commands/settings-command.js +10 -0
- package/dist/bot/commands/skills-catalog-command.js +42 -0
- package/dist/bot/commands/{start.js → start-command.js} +12 -12
- package/dist/bot/commands/{status.js → status-command.js} +15 -10
- package/dist/bot/commands/{task.js → task-command.js} +9 -81
- package/dist/bot/commands/tasklist-command.js +40 -0
- package/dist/bot/commands/worktree-command.js +47 -0
- package/dist/bot/handlers/{document.js → document-handler.js} +3 -3
- package/dist/bot/handlers/{media-group.js → media-group-handler.js} +3 -3
- package/dist/bot/handlers/photo-handler.js +44 -0
- package/dist/bot/handlers/prompt.js +17 -17
- package/dist/bot/handlers/text-message-handler.js +61 -0
- package/dist/bot/handlers/tts-response-handler.js +29 -0
- package/dist/bot/handlers/{voice.js → voice-handler.js} +6 -3
- package/dist/bot/index.js +27 -1179
- package/dist/{keyboard/manager.js → bot/keyboards/keyboard-manager.js} +6 -6
- package/dist/bot/keyboards/keyboard-types.js +1 -0
- package/dist/bot/{utils/keyboard.js → keyboards/main-reply-keyboard.js} +2 -2
- package/dist/bot/menus/agent-selection-menu.js +54 -0
- package/dist/bot/menus/command-catalog-menu.js +101 -0
- package/dist/bot/menus/context-control-menu.js +33 -0
- package/dist/bot/menus/file-browser-menu.js +265 -0
- package/dist/bot/{handlers → menus}/inline-menu.js +7 -34
- package/dist/bot/menus/mcp-catalog-menu.js +97 -0
- package/dist/bot/menus/message-history-menu.js +107 -0
- package/dist/bot/menus/model-selection-menu.js +66 -0
- package/dist/bot/menus/permission-menu.js +116 -0
- package/dist/bot/{commands/projects.js → menus/project-selection-menu.js} +5 -105
- package/dist/bot/{handlers/question.js → menus/question-menu.js} +10 -166
- package/dist/bot/menus/rename-menu.js +6 -0
- package/dist/bot/menus/scheduled-task-menu.js +42 -0
- package/dist/bot/menus/session-selection-menu.js +115 -0
- package/dist/bot/menus/settings-menu.js +52 -0
- package/dist/bot/menus/skills-catalog-menu.js +99 -0
- package/dist/bot/menus/variant-selection-menu.js +67 -0
- package/dist/bot/menus/worktree-selection-menu.js +76 -0
- package/dist/bot/{utils → messages}/assistant-rendering.js +2 -2
- package/dist/bot/messages/busy-blocked-renderer.js +11 -0
- package/dist/bot/messages/external-user-input-notification.js +22 -0
- package/dist/bot/messages/scheduled-task-delivery.js +63 -0
- package/dist/bot/{utils → messages}/send-downloaded-file.js +1 -1
- package/dist/bot/messages/summary-message-formatter.js +130 -0
- package/dist/bot/{utils → messages}/thinking-message.js +1 -4
- package/dist/bot/messages/thinking-rendering.js +66 -0
- package/dist/{interaction/guard.js → bot/middleware/interaction-guard-decision.js} +11 -4
- package/dist/bot/middleware/interaction-guard.js +2 -2
- package/dist/bot/middleware/unknown-command.js +1 -1
- package/dist/{pinned/format.js → bot/pinned/pinned-message-format.js} +1 -2
- package/dist/{pinned/manager.js → bot/pinned/pinned-message-manager.js} +10 -10
- package/dist/bot/pinned/pinned-message-types.js +1 -0
- package/dist/{summary → bot/render}/markdown-to-telegram-v2.js +2 -2
- package/dist/bot/render/types.js +1 -0
- package/dist/bot/routers/command-router.js +73 -0
- package/dist/bot/routers/message-router.js +150 -0
- package/dist/bot/services/attach-presentation.js +40 -0
- package/dist/bot/services/event-subscription-service.js +1116 -0
- package/dist/bot/services/project-switch-presentation.js +26 -0
- package/dist/bot/streaming/compact-progress-streamer.js +162 -0
- package/dist/bot/{utils → streaming}/finalize-assistant-response.js +9 -3
- package/dist/bot/streaming/response-streamer.js +16 -1
- package/dist/cli.js +4 -4
- package/dist/config.js +9 -18
- package/dist/i18n/ar.js +31 -6
- package/dist/i18n/de.js +32 -7
- package/dist/i18n/en.js +32 -7
- package/dist/i18n/es.js +32 -7
- package/dist/i18n/fr.js +32 -7
- package/dist/i18n/ru.js +32 -7
- package/dist/i18n/zh.js +32 -7
- package/dist/index.js +1 -1
- package/dist/opencode/ready-refresh.js +2 -2
- package/dist/{service → runtime/service}/manager.js +2 -2
- package/dist/runtime/service/types.js +1 -0
- package/package.json +1 -1
- package/dist/bot/commands/ls.js +0 -445
- package/dist/bot/commands/open.js +0 -319
- package/dist/bot/commands/tts.js +0 -13
- package/dist/bot/commands/worktree.js +0 -201
- package/dist/bot/handlers/permission.js +0 -235
- package/dist/bot/utils/browser-roots.js +0 -140
- package/dist/bot/utils/file-tree.js +0 -92
- package/dist/bot/utils/send-tts-response.js +0 -37
- package/dist/bot/utils/switch-project.js +0 -45
- package/dist/bot/utils/telegram-file-url.js +0 -11
- package/dist/interaction/busy.js +0 -8
- package/dist/interaction/cleanup.js +0 -32
- /package/dist/{scheduled-task/display.js → app/formatters/scheduled-task-display-formatter.js} +0 -0
- /package/dist/{bot/utils/abort-error-suppression.js → app/managers/abort-suppression-manager.js} +0 -0
- /package/dist/{external-input/suppression.js → app/managers/external-input-suppression-manager.js} +0 -0
- /package/dist/{scheduled-task/next-run.js → app/services/scheduled-task-next-run-service.js} +0 -0
- /package/dist/{git/worktree.js → app/services/worktree-service.js} +0 -0
- /package/dist/{agent/types.js → app/types/agent.js} +0 -0
- /package/dist/{interaction/types.js → app/types/interaction.js} +0 -0
- /package/dist/{model/types.js → app/types/model.js} +0 -0
- /package/dist/{keyboard/types.js → app/types/permission.js} +0 -0
- /package/dist/{permission/types.js → app/types/project.js} +0 -0
- /package/dist/{pinned/types.js → app/types/question.js} +0 -0
- /package/dist/{scheduled-task/types.js → app/types/scheduled-task.js} +0 -0
- /package/dist/{question/types.js → app/types/session.js} +0 -0
- /package/dist/{service/types.js → app/types/settings.js} +0 -0
- /package/dist/{telegram/render/types.js → app/types/summary.js} +0 -0
- /package/dist/{variant/types.js → app/types/variant.js} +0 -0
- /package/dist/bot/commands/{help.js → help-command.js} +0 -0
- /package/dist/bot/{utils → messages}/send-with-markdown-fallback.js +0 -0
- /package/dist/bot/{utils → messages}/telegram-text.js +0 -0
- /package/dist/{telegram → bot}/render/block-fallback.js +0 -0
- /package/dist/{telegram → bot}/render/block-parser.js +0 -0
- /package/dist/{telegram → bot}/render/block-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/chunker.js +0 -0
- /package/dist/{telegram → bot}/render/inline-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/markdown-normalizer.js +0 -0
- /package/dist/{telegram → bot}/render/pipeline.js +0 -0
- /package/dist/{telegram → bot}/render/validator.js +0 -0
- /package/dist/bot/{utils/commands.js → routers/command-utils.js} +0 -0
- /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getCurrentProject } from "../../settings/manager.js";
|
|
4
|
-
import { interactionManager } from "../../interaction/manager.js";
|
|
1
|
+
import { loadMcpCatalog, toggleMcpCatalogServer, } from "../../app/services/mcp-catalog-service.js";
|
|
2
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
5
3
|
import { logger } from "../../utils/logger.js";
|
|
6
4
|
import { t } from "../../i18n/index.js";
|
|
7
|
-
|
|
8
|
-
const MCPS_CALLBACK_SELECT_PREFIX = `${MCPS_CALLBACK_PREFIX}select:`;
|
|
9
|
-
const MCPS_CALLBACK_TOGGLE = `${MCPS_CALLBACK_PREFIX}toggle`;
|
|
10
|
-
const MCPS_CALLBACK_BACK = `${MCPS_CALLBACK_PREFIX}back`;
|
|
11
|
-
const MCPS_CALLBACK_CANCEL = `${MCPS_CALLBACK_PREFIX}cancel`;
|
|
12
|
-
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
13
|
-
function normalizeDirectoryForApi(directory) {
|
|
14
|
-
return directory.replace(/\\/g, "/");
|
|
15
|
-
}
|
|
5
|
+
import { buildMcpsDetailKeyboard, buildMcpsDetailText, buildMcpsListKeyboard, MCPS_CALLBACK_BACK, MCPS_CALLBACK_CANCEL, MCPS_CALLBACK_PREFIX, MCPS_CALLBACK_SELECT_PREFIX, MCPS_CALLBACK_TOGGLE, parseMcpSelectCallback, } from "../menus/mcp-catalog-menu.js";
|
|
16
6
|
function getCallbackMessageId(ctx) {
|
|
17
7
|
const message = ctx.callbackQuery?.message;
|
|
18
8
|
if (!message || !("message_id" in message)) {
|
|
@@ -21,87 +11,6 @@ function getCallbackMessageId(ctx) {
|
|
|
21
11
|
const messageId = message.message_id;
|
|
22
12
|
return typeof messageId === "number" ? messageId : null;
|
|
23
13
|
}
|
|
24
|
-
function getStatusLabel(status) {
|
|
25
|
-
switch (status.status) {
|
|
26
|
-
case "connected":
|
|
27
|
-
return t("mcps.status.connected");
|
|
28
|
-
case "disabled":
|
|
29
|
-
return t("mcps.status.disabled");
|
|
30
|
-
case "failed":
|
|
31
|
-
return t("mcps.status.failed");
|
|
32
|
-
case "needs_auth":
|
|
33
|
-
return t("mcps.status.needs_auth");
|
|
34
|
-
case "needs_client_registration":
|
|
35
|
-
return t("mcps.status.needs_client_registration");
|
|
36
|
-
default:
|
|
37
|
-
return t("common.unknown");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function getStatusEmoji(status) {
|
|
41
|
-
switch (status.status) {
|
|
42
|
-
case "connected":
|
|
43
|
-
return "🟢";
|
|
44
|
-
case "disabled":
|
|
45
|
-
return "🔴";
|
|
46
|
-
case "failed":
|
|
47
|
-
return "⚠️";
|
|
48
|
-
case "needs_auth":
|
|
49
|
-
return "🔒";
|
|
50
|
-
case "needs_client_registration":
|
|
51
|
-
return "🔒";
|
|
52
|
-
default:
|
|
53
|
-
return "❓";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function formatMcpButtonLabel(server) {
|
|
57
|
-
const rawLabel = `${getStatusEmoji(server.status)} ${server.name}`;
|
|
58
|
-
if (rawLabel.length <= MAX_INLINE_BUTTON_LABEL_LENGTH) {
|
|
59
|
-
return rawLabel;
|
|
60
|
-
}
|
|
61
|
-
return `${rawLabel.slice(0, MAX_INLINE_BUTTON_LABEL_LENGTH - 3)}...`;
|
|
62
|
-
}
|
|
63
|
-
function buildMcpsListKeyboard(servers) {
|
|
64
|
-
const keyboard = new InlineKeyboard();
|
|
65
|
-
servers.forEach((server, index) => {
|
|
66
|
-
keyboard
|
|
67
|
-
.text(formatMcpButtonLabel(server), `${MCPS_CALLBACK_SELECT_PREFIX}${index}`)
|
|
68
|
-
.row();
|
|
69
|
-
});
|
|
70
|
-
keyboard.text(t("inline.button.cancel"), MCPS_CALLBACK_CANCEL);
|
|
71
|
-
return keyboard;
|
|
72
|
-
}
|
|
73
|
-
function buildMcpsDetailKeyboard(server) {
|
|
74
|
-
const keyboard = new InlineKeyboard();
|
|
75
|
-
let hasToggleButton = false;
|
|
76
|
-
if (server.status.status === "connected") {
|
|
77
|
-
keyboard.text(t("mcps.button.disable"), MCPS_CALLBACK_TOGGLE);
|
|
78
|
-
hasToggleButton = true;
|
|
79
|
-
}
|
|
80
|
-
else if (server.status.status === "disabled" || server.status.status === "failed") {
|
|
81
|
-
keyboard.text(t("mcps.button.enable"), MCPS_CALLBACK_TOGGLE);
|
|
82
|
-
hasToggleButton = true;
|
|
83
|
-
}
|
|
84
|
-
if (hasToggleButton) {
|
|
85
|
-
keyboard.row();
|
|
86
|
-
}
|
|
87
|
-
keyboard.text(t("mcps.button.back"), MCPS_CALLBACK_BACK);
|
|
88
|
-
keyboard.text(t("inline.button.cancel"), MCPS_CALLBACK_CANCEL);
|
|
89
|
-
return keyboard;
|
|
90
|
-
}
|
|
91
|
-
function buildMcpsDetailText(server) {
|
|
92
|
-
const lines = [];
|
|
93
|
-
lines.push(t("mcps.detail.title", { name: server.name }));
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push(t("mcps.detail.status", { status: getStatusLabel(server.status) }));
|
|
96
|
-
if (server.status.status === "failed" || server.status.status === "needs_client_registration") {
|
|
97
|
-
lines.push(t("mcps.detail.error", { error: server.status.error }));
|
|
98
|
-
}
|
|
99
|
-
if (server.status.status === "needs_auth" || server.status.status === "needs_client_registration") {
|
|
100
|
-
lines.push("");
|
|
101
|
-
lines.push(t("mcps.auth_required"));
|
|
102
|
-
}
|
|
103
|
-
return lines.join("\n");
|
|
104
|
-
}
|
|
105
14
|
function parseMcpsServers(value) {
|
|
106
15
|
if (!value || typeof value !== "object") {
|
|
107
16
|
return null;
|
|
@@ -193,80 +102,6 @@ function clearMcpsInteraction(reason) {
|
|
|
193
102
|
interactionManager.clear(reason);
|
|
194
103
|
}
|
|
195
104
|
}
|
|
196
|
-
function parseSelectIndex(data) {
|
|
197
|
-
if (!data.startsWith(MCPS_CALLBACK_SELECT_PREFIX)) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
const index = Number(data.slice(MCPS_CALLBACK_SELECT_PREFIX.length));
|
|
201
|
-
if (!Number.isInteger(index) || index < 0) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
return index;
|
|
205
|
-
}
|
|
206
|
-
async function getMcpServerList(projectDirectory) {
|
|
207
|
-
const { data, error } = await opencodeClient.mcp.status({
|
|
208
|
-
directory: normalizeDirectoryForApi(projectDirectory),
|
|
209
|
-
});
|
|
210
|
-
if (error || !data) {
|
|
211
|
-
throw error || new Error("No MCP status data received");
|
|
212
|
-
}
|
|
213
|
-
const servers = parseMcpsServers(data);
|
|
214
|
-
if (!servers) {
|
|
215
|
-
throw new Error("Invalid MCP status data format");
|
|
216
|
-
}
|
|
217
|
-
return servers;
|
|
218
|
-
}
|
|
219
|
-
async function toggleMcpServer(projectDirectory, serverName, enable) {
|
|
220
|
-
const params = {
|
|
221
|
-
name: serverName,
|
|
222
|
-
directory: normalizeDirectoryForApi(projectDirectory),
|
|
223
|
-
};
|
|
224
|
-
if (enable) {
|
|
225
|
-
const { error } = await opencodeClient.mcp.connect(params);
|
|
226
|
-
if (error) {
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
const { error } = await opencodeClient.mcp.disconnect(params);
|
|
232
|
-
if (error) {
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
export async function mcpsCommand(ctx) {
|
|
238
|
-
try {
|
|
239
|
-
const currentProject = getCurrentProject();
|
|
240
|
-
if (!currentProject) {
|
|
241
|
-
await ctx.reply(t("bot.project_not_selected"));
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
const servers = await getMcpServerList(currentProject.worktree);
|
|
245
|
-
if (servers.length === 0) {
|
|
246
|
-
await ctx.reply(t("mcps.empty"));
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
const keyboard = buildMcpsListKeyboard(servers);
|
|
250
|
-
const message = await ctx.reply(t("mcps.select"), {
|
|
251
|
-
reply_markup: keyboard,
|
|
252
|
-
});
|
|
253
|
-
interactionManager.start({
|
|
254
|
-
kind: "custom",
|
|
255
|
-
expectedInput: "callback",
|
|
256
|
-
metadata: {
|
|
257
|
-
flow: "mcps",
|
|
258
|
-
stage: "list",
|
|
259
|
-
messageId: message.message_id,
|
|
260
|
-
projectDirectory: currentProject.worktree,
|
|
261
|
-
servers,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
logger.error("[Mcps] Error fetching MCP servers list:", error);
|
|
267
|
-
await ctx.reply(t("mcps.fetch_error"));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
105
|
export async function handleMcpsCallback(ctx) {
|
|
271
106
|
const data = ctx.callbackQuery?.data;
|
|
272
107
|
if (!data || !data.startsWith(MCPS_CALLBACK_PREFIX)) {
|
|
@@ -290,7 +125,7 @@ export async function handleMcpsCallback(ctx) {
|
|
|
290
125
|
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
291
126
|
return true;
|
|
292
127
|
}
|
|
293
|
-
const servers = await
|
|
128
|
+
const servers = await loadMcpCatalog(metadata.projectDirectory);
|
|
294
129
|
const keyboard = buildMcpsListKeyboard(servers);
|
|
295
130
|
await ctx.editMessageText(t("mcps.select"), { reply_markup: keyboard });
|
|
296
131
|
await ctx.answerCallbackQuery();
|
|
@@ -319,8 +154,8 @@ export async function handleMcpsCallback(ctx) {
|
|
|
319
154
|
}
|
|
320
155
|
const enable = server.status.status !== "connected";
|
|
321
156
|
await ctx.answerCallbackQuery({ text: enable ? t("mcps.enabling") : t("mcps.disabling") });
|
|
322
|
-
await
|
|
323
|
-
const updatedServers = await
|
|
157
|
+
await toggleMcpCatalogServer(metadata.projectDirectory, serverName, enable);
|
|
158
|
+
const updatedServers = await loadMcpCatalog(metadata.projectDirectory);
|
|
324
159
|
const updatedServer = updatedServers.find((s) => s.name === serverName);
|
|
325
160
|
if (!updatedServer) {
|
|
326
161
|
await ctx.editMessageText(t("mcps.select"), {
|
|
@@ -359,7 +194,7 @@ export async function handleMcpsCallback(ctx) {
|
|
|
359
194
|
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
360
195
|
return true;
|
|
361
196
|
}
|
|
362
|
-
const serverIndex =
|
|
197
|
+
const serverIndex = parseMcpSelectCallback(data);
|
|
363
198
|
const server = serverIndex === null ? undefined : metadata.servers[serverIndex];
|
|
364
199
|
if (!server) {
|
|
365
200
|
await ctx.answerCallbackQuery({ text: t("inline.inactive_callback"), show_alert: true });
|
|
@@ -1,27 +1,18 @@
|
|
|
1
|
-
import { InlineKeyboard } from "grammy";
|
|
2
1
|
import { config } from "../../config.js";
|
|
3
|
-
import { interactionManager } from "../../interaction
|
|
2
|
+
import { clearAllInteractionState, interactionManager } from "../../app/managers/interaction-manager.js";
|
|
4
3
|
import { opencodeClient } from "../../opencode/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { setCurrentSession } from "../../app/services/session-service.js";
|
|
5
|
+
import { attachToSession } from "../../app/services/attach-service.js";
|
|
6
|
+
import { ingestSessionInfoForCache } from "../../app/services/session-cache-service.js";
|
|
7
|
+
import { loadLatestAssistantResponse } from "../../app/services/message-history-service.js";
|
|
8
|
+
import { isForegroundBusy } from "../../app/services/run-control-service.js";
|
|
10
9
|
import { t } from "../../i18n/index.js";
|
|
11
10
|
import { logger } from "../../utils/logger.js";
|
|
12
11
|
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
13
|
-
import { renderAssistantFinalPartsSafe } from "../
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const MESSAGES_CALLBACK_PAGE_PREFIX = `${MESSAGES_CALLBACK_PREFIX}page:`;
|
|
18
|
-
const MESSAGES_CALLBACK_REVERT = `${MESSAGES_CALLBACK_PREFIX}revert`;
|
|
19
|
-
const MESSAGES_CALLBACK_FORK = `${MESSAGES_CALLBACK_PREFIX}fork`;
|
|
20
|
-
const MESSAGES_CALLBACK_BACK = `${MESSAGES_CALLBACK_PREFIX}back`;
|
|
21
|
-
const MESSAGES_CALLBACK_CANCEL = `${MESSAGES_CALLBACK_PREFIX}cancel`;
|
|
22
|
-
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
23
|
-
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
24
|
-
const LATEST_ASSISTANT_RESPONSE_MESSAGES_LIMIT = 20;
|
|
12
|
+
import { renderAssistantFinalPartsSafe } from "../messages/assistant-rendering.js";
|
|
13
|
+
import { replyBusyBlocked } from "../messages/busy-blocked-renderer.js";
|
|
14
|
+
import { sendRenderedBotPart } from "../messages/telegram-text.js";
|
|
15
|
+
import { buildMessageDetailKeyboard, buildMessagesListKeyboard, calculateMessagesPaginationRange, formatMessageDetailText, formatMessagesSelectText, MESSAGES_CALLBACK_BACK, MESSAGES_CALLBACK_CANCEL, MESSAGES_CALLBACK_FORK, MESSAGES_CALLBACK_PREFIX, MESSAGES_CALLBACK_REVERT, parseMessagePageCallback, parseMessageSelectCallback, TELEGRAM_MESSAGE_LIMIT, truncateMessageHistoryText, } from "../menus/message-history-menu.js";
|
|
25
16
|
function getCallbackMessageId(ctx) {
|
|
26
17
|
const message = ctx.callbackQuery?.message;
|
|
27
18
|
if (!message || !("message_id" in message)) {
|
|
@@ -30,99 +21,6 @@ function getCallbackMessageId(ctx) {
|
|
|
30
21
|
const messageId = message.message_id;
|
|
31
22
|
return typeof messageId === "number" ? messageId : null;
|
|
32
23
|
}
|
|
33
|
-
function extractTextParts(parts) {
|
|
34
|
-
const text = parts
|
|
35
|
-
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
36
|
-
.map((part) => part.text)
|
|
37
|
-
.join("")
|
|
38
|
-
.trim();
|
|
39
|
-
return text.length > 0 ? text : null;
|
|
40
|
-
}
|
|
41
|
-
function truncateText(text, maxLength) {
|
|
42
|
-
if (text.length <= maxLength) {
|
|
43
|
-
return text;
|
|
44
|
-
}
|
|
45
|
-
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
46
|
-
}
|
|
47
|
-
function normalizeButtonText(text) {
|
|
48
|
-
return text.replace(/\s+/g, " ").trim();
|
|
49
|
-
}
|
|
50
|
-
function formatMessageTime(created) {
|
|
51
|
-
const date = new Date(created);
|
|
52
|
-
const hours = date.getHours().toString().padStart(2, "0");
|
|
53
|
-
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
54
|
-
return `${hours}:${minutes}`;
|
|
55
|
-
}
|
|
56
|
-
function formatMessageButtonLabel(message) {
|
|
57
|
-
const prefix = `[${formatMessageTime(message.created)}] `;
|
|
58
|
-
const text = normalizeButtonText(message.text);
|
|
59
|
-
return `${prefix}${truncateText(text, MAX_INLINE_BUTTON_LABEL_LENGTH - prefix.length)}`;
|
|
60
|
-
}
|
|
61
|
-
function formatMessagesSelectText(page) {
|
|
62
|
-
if (page === 0) {
|
|
63
|
-
return t("messages.select");
|
|
64
|
-
}
|
|
65
|
-
return t("messages.select_page", { page: page + 1 });
|
|
66
|
-
}
|
|
67
|
-
function formatMessageDetailText(message) {
|
|
68
|
-
const prefix = `[${formatMessageTime(message.created)}]\n\n`;
|
|
69
|
-
return truncateText(`${prefix}${message.text}`, TELEGRAM_MESSAGE_LIMIT);
|
|
70
|
-
}
|
|
71
|
-
export function buildMessagePageCallback(page) {
|
|
72
|
-
return `${MESSAGES_CALLBACK_PAGE_PREFIX}${page}`;
|
|
73
|
-
}
|
|
74
|
-
export function parseMessagePageCallback(data) {
|
|
75
|
-
if (!data.startsWith(MESSAGES_CALLBACK_PAGE_PREFIX)) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const rawPage = data.slice(MESSAGES_CALLBACK_PAGE_PREFIX.length);
|
|
79
|
-
const page = Number(rawPage);
|
|
80
|
-
if (!Number.isInteger(page) || page < 0) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
return page;
|
|
84
|
-
}
|
|
85
|
-
export function calculateMessagesPaginationRange(totalMessages, page, pageSize) {
|
|
86
|
-
const safePageSize = Math.max(1, pageSize);
|
|
87
|
-
const totalPages = Math.max(1, Math.ceil(totalMessages / safePageSize));
|
|
88
|
-
const normalizedPage = Math.min(Math.max(0, page), totalPages - 1);
|
|
89
|
-
const startIndex = normalizedPage * safePageSize;
|
|
90
|
-
const endIndex = Math.min(startIndex + safePageSize, totalMessages);
|
|
91
|
-
return {
|
|
92
|
-
page: normalizedPage,
|
|
93
|
-
totalPages,
|
|
94
|
-
startIndex,
|
|
95
|
-
endIndex,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function buildMessagesListKeyboard(messages, page, pageSize) {
|
|
99
|
-
const keyboard = new InlineKeyboard();
|
|
100
|
-
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateMessagesPaginationRange(messages.length, page, pageSize);
|
|
101
|
-
messages.slice(startIndex, endIndex).forEach((message, index) => {
|
|
102
|
-
const globalIndex = startIndex + index;
|
|
103
|
-
keyboard.text(formatMessageButtonLabel(message), `${MESSAGES_CALLBACK_SELECT_PREFIX}${globalIndex}`).row();
|
|
104
|
-
});
|
|
105
|
-
if (totalPages > 1) {
|
|
106
|
-
if (normalizedPage > 0) {
|
|
107
|
-
keyboard.text(t("messages.button.prev_page"), buildMessagePageCallback(normalizedPage - 1));
|
|
108
|
-
}
|
|
109
|
-
if (normalizedPage < totalPages - 1) {
|
|
110
|
-
keyboard.text(t("messages.button.next_page"), buildMessagePageCallback(normalizedPage + 1));
|
|
111
|
-
}
|
|
112
|
-
keyboard.row();
|
|
113
|
-
}
|
|
114
|
-
keyboard.text(t("messages.button.cancel"), MESSAGES_CALLBACK_CANCEL);
|
|
115
|
-
return keyboard;
|
|
116
|
-
}
|
|
117
|
-
function buildMessageDetailKeyboard() {
|
|
118
|
-
return new InlineKeyboard()
|
|
119
|
-
.text(t("messages.button.revert"), MESSAGES_CALLBACK_REVERT)
|
|
120
|
-
.row()
|
|
121
|
-
.text(t("messages.button.fork"), MESSAGES_CALLBACK_FORK)
|
|
122
|
-
.row()
|
|
123
|
-
.text(t("messages.button.back"), MESSAGES_CALLBACK_BACK)
|
|
124
|
-
.text(t("messages.button.cancel"), MESSAGES_CALLBACK_CANCEL);
|
|
125
|
-
}
|
|
126
24
|
function parseMessages(value) {
|
|
127
25
|
if (!Array.isArray(value)) {
|
|
128
26
|
return null;
|
|
@@ -197,83 +95,6 @@ function clearMessagesInteraction(reason) {
|
|
|
197
95
|
interactionManager.clear(reason);
|
|
198
96
|
}
|
|
199
97
|
}
|
|
200
|
-
async function loadUserMessages(sessionId, directory) {
|
|
201
|
-
const { data, error } = await opencodeClient.session.messages({
|
|
202
|
-
sessionID: sessionId,
|
|
203
|
-
directory,
|
|
204
|
-
});
|
|
205
|
-
if (error || !data) {
|
|
206
|
-
throw error || new Error("No message data received");
|
|
207
|
-
}
|
|
208
|
-
// Get session info to check for revert
|
|
209
|
-
const { data: sessionData } = await opencodeClient.session.get({
|
|
210
|
-
sessionID: sessionId,
|
|
211
|
-
directory,
|
|
212
|
-
});
|
|
213
|
-
const revertMessageID = sessionData?.revert?.messageID;
|
|
214
|
-
const messages = data
|
|
215
|
-
.map((message) => {
|
|
216
|
-
if (message.info.role !== "user") {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
const text = extractTextParts(message.parts);
|
|
220
|
-
if (!text) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
id: message.info.id ?? `${message.info.time?.created ?? 0}`,
|
|
225
|
-
text,
|
|
226
|
-
created: message.info.time?.created ?? 0,
|
|
227
|
-
};
|
|
228
|
-
})
|
|
229
|
-
.filter((message) => Boolean(message))
|
|
230
|
-
.sort((a, b) => b.created - a.created);
|
|
231
|
-
// If there's a revert, filter messages to only include those before the revert point
|
|
232
|
-
// Messages are sorted newest first, so we need to skip the revert message and everything after it
|
|
233
|
-
if (revertMessageID) {
|
|
234
|
-
const revertIndex = messages.findIndex((msg) => msg.id === revertMessageID);
|
|
235
|
-
if (revertIndex !== -1) {
|
|
236
|
-
return messages.slice(revertIndex + 1);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return messages;
|
|
240
|
-
}
|
|
241
|
-
async function loadLatestAssistantResponse(sessionId, directory) {
|
|
242
|
-
try {
|
|
243
|
-
const { data: messages, error } = await opencodeClient.session.messages({
|
|
244
|
-
sessionID: sessionId,
|
|
245
|
-
directory,
|
|
246
|
-
limit: LATEST_ASSISTANT_RESPONSE_MESSAGES_LIMIT,
|
|
247
|
-
});
|
|
248
|
-
if (error || !messages) {
|
|
249
|
-
logger.warn("[Messages] Failed to fetch latest assistant response:", error);
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
const latestResponse = messages.reduce((latest, message) => {
|
|
253
|
-
if (message.info.role !== "assistant") {
|
|
254
|
-
return latest;
|
|
255
|
-
}
|
|
256
|
-
const assistantInfo = message.info;
|
|
257
|
-
if (assistantInfo.summary) {
|
|
258
|
-
return latest;
|
|
259
|
-
}
|
|
260
|
-
const text = extractTextParts(message.parts);
|
|
261
|
-
if (!text) {
|
|
262
|
-
return latest;
|
|
263
|
-
}
|
|
264
|
-
const created = message.info.time?.created ?? 0;
|
|
265
|
-
if (!latest || created >= latest.created) {
|
|
266
|
-
return { text, created };
|
|
267
|
-
}
|
|
268
|
-
return latest;
|
|
269
|
-
}, null);
|
|
270
|
-
return latestResponse?.text ?? null;
|
|
271
|
-
}
|
|
272
|
-
catch (err) {
|
|
273
|
-
logger.error("[Messages] Error loading latest assistant response:", err);
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
98
|
async function sendLatestAssistantResponse(api, chatId, sessionId, directory) {
|
|
278
99
|
const responseText = await loadLatestAssistantResponse(sessionId, directory);
|
|
279
100
|
if (!responseText) {
|
|
@@ -288,67 +109,15 @@ async function sendLatestAssistantResponse(api, chatId, sessionId, directory) {
|
|
|
288
109
|
});
|
|
289
110
|
}
|
|
290
111
|
}
|
|
291
|
-
function parseSelectIndex(data) {
|
|
292
|
-
if (!data.startsWith(MESSAGES_CALLBACK_SELECT_PREFIX)) {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
const rawIndex = data.slice(MESSAGES_CALLBACK_SELECT_PREFIX.length);
|
|
296
|
-
const index = Number(rawIndex);
|
|
297
|
-
if (!Number.isInteger(index) || index < 0) {
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
return index;
|
|
301
|
-
}
|
|
302
|
-
export async function messagesCommand(ctx) {
|
|
303
|
-
try {
|
|
304
|
-
const currentProject = getCurrentProject();
|
|
305
|
-
if (!currentProject) {
|
|
306
|
-
await ctx.reply(t("messages.project_not_selected"));
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const currentSession = getCurrentSession();
|
|
310
|
-
if (!currentSession) {
|
|
311
|
-
await ctx.reply(t("messages.session_not_selected"));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (currentSession.directory !== currentProject.worktree) {
|
|
315
|
-
await ctx.reply(t("messages.session_project_mismatch"));
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const messages = await loadUserMessages(currentSession.id, currentSession.directory);
|
|
319
|
-
if (messages.length === 0) {
|
|
320
|
-
await ctx.reply(t("messages.empty"));
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const pageSize = config.bot.messagesListLimit;
|
|
324
|
-
const keyboard = buildMessagesListKeyboard(messages, 0, pageSize);
|
|
325
|
-
const message = await ctx.reply(formatMessagesSelectText(0), {
|
|
326
|
-
reply_markup: keyboard,
|
|
327
|
-
});
|
|
328
|
-
interactionManager.start({
|
|
329
|
-
kind: "custom",
|
|
330
|
-
expectedInput: "callback",
|
|
331
|
-
metadata: {
|
|
332
|
-
flow: "messages",
|
|
333
|
-
stage: "list",
|
|
334
|
-
messageId: message.message_id,
|
|
335
|
-
projectDirectory: currentProject.worktree,
|
|
336
|
-
sessionId: currentSession.id,
|
|
337
|
-
messages,
|
|
338
|
-
page: 0,
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
logger.error("[Messages] Error fetching messages list:", error);
|
|
344
|
-
await ctx.reply(t("messages.fetch_error"));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
112
|
export async function handleMessagesCallback(ctx, deps) {
|
|
348
113
|
const data = ctx.callbackQuery?.data;
|
|
349
114
|
if (!data || !data.startsWith(MESSAGES_CALLBACK_PREFIX)) {
|
|
350
115
|
return false;
|
|
351
116
|
}
|
|
117
|
+
if (isForegroundBusy()) {
|
|
118
|
+
await replyBusyBlocked(ctx);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
352
121
|
const metadata = parseMessagesMetadata(interactionManager.getSnapshot());
|
|
353
122
|
const callbackMessageId = getCallbackMessageId(ctx);
|
|
354
123
|
if (!metadata || callbackMessageId === null || metadata.messageId !== callbackMessageId) {
|
|
@@ -374,7 +143,7 @@ export async function handleMessagesCallback(ctx, deps) {
|
|
|
374
143
|
messageID: selectedMessage.id,
|
|
375
144
|
});
|
|
376
145
|
const successText = t("messages.revert_success", { text: selectedMessage.text });
|
|
377
|
-
await ctx.editMessageText(
|
|
146
|
+
await ctx.editMessageText(truncateMessageHistoryText(successText, TELEGRAM_MESSAGE_LIMIT), {
|
|
378
147
|
reply_markup: undefined,
|
|
379
148
|
});
|
|
380
149
|
clearMessagesInteraction("messages_revert_success");
|
|
@@ -422,7 +191,7 @@ export async function handleMessagesCallback(ctx, deps) {
|
|
|
422
191
|
ensureEventSubscription: deps.ensureEventSubscription,
|
|
423
192
|
});
|
|
424
193
|
const successText = t("messages.fork_success", { text: selectedMessage.text });
|
|
425
|
-
await ctx.editMessageText(
|
|
194
|
+
await ctx.editMessageText(truncateMessageHistoryText(successText, TELEGRAM_MESSAGE_LIMIT), {
|
|
426
195
|
reply_markup: undefined,
|
|
427
196
|
});
|
|
428
197
|
clearMessagesInteraction("messages_fork_success");
|
|
@@ -499,7 +268,7 @@ export async function handleMessagesCallback(ctx, deps) {
|
|
|
499
268
|
});
|
|
500
269
|
return true;
|
|
501
270
|
}
|
|
502
|
-
const messageIndex =
|
|
271
|
+
const messageIndex = parseMessageSelectCallback(data);
|
|
503
272
|
if (messageIndex === null || metadata.stage !== "list") {
|
|
504
273
|
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
505
274
|
return true;
|
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
import { InlineKeyboard } from "grammy";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { formatVariantForButton } from "../../variant
|
|
2
|
+
import { getStoredAgent, resolveProjectAgent } from "../../app/services/agent-selection-service.js";
|
|
3
|
+
import { searchModels, selectModel, } from "../../app/services/model-selection-service.js";
|
|
4
|
+
import { formatVariantForButton } from "../../app/services/variant-selection-service.js";
|
|
5
|
+
import { formatModelForDisplay } from "../../app/types/model.js";
|
|
6
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
5
7
|
import { logger } from "../../utils/logger.js";
|
|
6
|
-
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
7
|
-
import { getStoredAgent, resolveProjectAgent } from "../../agent/manager.js";
|
|
8
|
-
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
9
|
-
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
|
-
import { clearActiveInlineMenu, ensureActiveInlineMenu, replyWithInlineMenu, } from "./inline-menu.js";
|
|
11
|
-
import { interactionManager } from "../../interaction/manager.js";
|
|
12
8
|
import { t } from "../../i18n/index.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (modelLists.favorites.length === 0) {
|
|
19
|
-
lines.push(t("model.menu.favorites_empty"));
|
|
20
|
-
}
|
|
21
|
-
lines.push(t("model.menu.recent_title"));
|
|
22
|
-
if (modelLists.recent.length === 0) {
|
|
23
|
-
lines.push(t("model.menu.recent_empty"));
|
|
24
|
-
}
|
|
25
|
-
return lines.join("\n");
|
|
26
|
-
}
|
|
9
|
+
import { createMainKeyboard } from "../keyboards/main-reply-keyboard.js";
|
|
10
|
+
import { keyboardManager } from "../keyboards/keyboard-manager.js";
|
|
11
|
+
import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
|
|
12
|
+
import { clearActiveInlineMenu, ensureActiveInlineMenu } from "../menus/inline-menu.js";
|
|
13
|
+
import { MODEL_SEARCH_AGAIN_CALLBACK, MODEL_SEARCH_CALLBACK, MODEL_SEARCH_CANCEL_CALLBACK, } from "../menus/model-selection-menu.js";
|
|
27
14
|
function parseModelSearchMetadata() {
|
|
28
15
|
const state = interactionManager.getSnapshot();
|
|
29
16
|
if (!state || state.kind !== "custom") {
|
|
@@ -114,54 +101,6 @@ export async function handleModelSelect(ctx) {
|
|
|
114
101
|
return false;
|
|
115
102
|
}
|
|
116
103
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Build inline keyboard with favorite and recent models, plus a search button at the top.
|
|
119
|
-
*/
|
|
120
|
-
export async function buildModelSelectionMenu(currentModel, modelLists) {
|
|
121
|
-
const keyboard = new InlineKeyboard();
|
|
122
|
-
const lists = modelLists ?? (await getModelSelectionLists());
|
|
123
|
-
const favorites = lists.favorites;
|
|
124
|
-
const recent = lists.recent;
|
|
125
|
-
// Search button — always present as first row
|
|
126
|
-
keyboard.text(t("model.search.button"), MODEL_SEARCH_CALLBACK).row();
|
|
127
|
-
if (favorites.length === 0 && recent.length === 0) {
|
|
128
|
-
logger.warn("[ModelHandler] No model choices found in favorites/recent");
|
|
129
|
-
return keyboard;
|
|
130
|
-
}
|
|
131
|
-
const addButton = (model, prefix) => {
|
|
132
|
-
const isActive = currentModel &&
|
|
133
|
-
model.providerID === currentModel.providerID &&
|
|
134
|
-
model.modelID === currentModel.modelID;
|
|
135
|
-
const label = `${prefix} ${model.providerID}/${model.modelID}`;
|
|
136
|
-
const labelWithCheck = isActive ? `✅ ${label}` : label;
|
|
137
|
-
keyboard.text(labelWithCheck, `model:${model.providerID}:${model.modelID}`).row();
|
|
138
|
-
};
|
|
139
|
-
favorites.forEach((model) => addButton(model, "⭐"));
|
|
140
|
-
recent.forEach((model) => addButton(model, "🕘"));
|
|
141
|
-
return keyboard;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Show model selection menu
|
|
145
|
-
*/
|
|
146
|
-
export async function showModelSelectionMenu(ctx) {
|
|
147
|
-
try {
|
|
148
|
-
const currentModel = fetchCurrentModel();
|
|
149
|
-
const modelLists = await getModelSelectionLists();
|
|
150
|
-
const keyboard = await buildModelSelectionMenu(currentModel, modelLists);
|
|
151
|
-
// keyboard always has at least the search button, so length > 0
|
|
152
|
-
const text = buildModelSelectionMenuText(modelLists);
|
|
153
|
-
await replyWithInlineMenu(ctx, {
|
|
154
|
-
menuKind: "model",
|
|
155
|
-
text,
|
|
156
|
-
keyboard,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
logger.error("[ModelHandler] Error showing model menu:", err);
|
|
161
|
-
await ctx.reply(t("model.menu.error"));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// ─── Model search handlers ────────────────────────────────────────────────
|
|
165
104
|
/**
|
|
166
105
|
* Handle the search button callback (model:search) from the inline menu.
|
|
167
106
|
* Transitions the interaction to text-input mode and prompts the user.
|