@grinev/opencode-telegram-bot 0.21.1 → 0.21.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/.env.example +11 -1
- package/README.md +18 -5
- 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} +4 -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} +3 -3
- 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} +5 -5
- 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} +3 -3
- 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 +110 -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/{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/{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/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} +9 -9
- package/dist/bot/commands/{task.js → task-command.js} +9 -81
- package/dist/bot/commands/tasklist-command.js +40 -0
- package/dist/bot/commands/{tts.js → tts-command.js} +2 -2
- 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 +16 -16
- 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} +2 -2
- 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 +4 -33
- 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/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/{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 +842 -0
- package/dist/bot/services/project-switch-presentation.js +26 -0
- package/dist/cli.js +4 -4
- package/dist/config.js +9 -3
- 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/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/bot/{utils → messages}/thinking-message.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/bot/{utils → streaming}/finalize-assistant-response.js +0 -0
- /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
|
@@ -1,64 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { getStoredAgent, resolveProjectAgent } from "../../agent/manager.js";
|
|
9
|
-
import { getStoredModel } from "../../model/manager.js";
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { clearSession, getCurrentSession, setCurrentSession, } from "../../app/services/session-service.js";
|
|
3
|
+
import { ingestSessionInfoForCache } from "../../app/services/session-cache-service.js";
|
|
4
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
5
|
+
import { summaryAggregator } from "../../app/managers/summary-aggregation-manager.js";
|
|
6
|
+
import { getStoredAgent, resolveProjectAgent } from "../../app/services/agent-selection-service.js";
|
|
7
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
10
8
|
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
11
9
|
import { logger } from "../../utils/logger.js";
|
|
12
10
|
import { t } from "../../i18n/index.js";
|
|
13
|
-
import { foregroundSessionState } from "../../
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
const COMMANDS_CALLBACK_SELECT_PREFIX = `${COMMANDS_CALLBACK_PREFIX}select:`;
|
|
20
|
-
const COMMANDS_CALLBACK_PAGE_PREFIX = `${COMMANDS_CALLBACK_PREFIX}page:`;
|
|
21
|
-
const COMMANDS_CALLBACK_CANCEL = `${COMMANDS_CALLBACK_PREFIX}cancel`;
|
|
22
|
-
const COMMANDS_CALLBACK_EXECUTE = `${COMMANDS_CALLBACK_PREFIX}execute`;
|
|
23
|
-
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
24
|
-
function formatExecutingCommandMessage(commandName, args) {
|
|
25
|
-
const prefix = t("commands.executing_prefix");
|
|
26
|
-
const commandText = `/${commandName}`;
|
|
27
|
-
const argsSuffix = args ? ` ${args}` : "";
|
|
28
|
-
return {
|
|
29
|
-
text: `${prefix}\n${commandText}${argsSuffix}`,
|
|
30
|
-
entities: [
|
|
31
|
-
{
|
|
32
|
-
type: "code",
|
|
33
|
-
offset: prefix.length + 1,
|
|
34
|
-
length: commandText.length,
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
export function buildCommandPageCallback(page) {
|
|
40
|
-
return `${COMMANDS_CALLBACK_PAGE_PREFIX}${page}`;
|
|
41
|
-
}
|
|
42
|
-
export function parseCommandPageCallback(data) {
|
|
43
|
-
if (!data.startsWith(COMMANDS_CALLBACK_PAGE_PREFIX)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
const rawPage = data.slice(COMMANDS_CALLBACK_PAGE_PREFIX.length);
|
|
47
|
-
const page = Number(rawPage);
|
|
48
|
-
if (!Number.isInteger(page) || page < 0) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
return page;
|
|
52
|
-
}
|
|
53
|
-
export function formatCommandsSelectText(page) {
|
|
54
|
-
if (page === 0) {
|
|
55
|
-
return t("commands.select");
|
|
56
|
-
}
|
|
57
|
-
return t("commands.select_page", { page: page + 1 });
|
|
58
|
-
}
|
|
59
|
-
function normalizeDirectoryForCommandApi(directory) {
|
|
60
|
-
return directory.replace(/\\/g, "/");
|
|
61
|
-
}
|
|
11
|
+
import { foregroundSessionState } from "../../app/managers/foreground-session-state-manager.js";
|
|
12
|
+
import { assistantRunState } from "../../app/managers/assistant-run-state-manager.js";
|
|
13
|
+
import { attachToSession, detachAttachedSession, markAttachedSessionBusy, markAttachedSessionIdle, } from "../../app/services/attach-service.js";
|
|
14
|
+
import { externalUserInputSuppressionManager } from "../../app/managers/external-input-suppression-manager.js";
|
|
15
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
16
|
+
import { buildCommandsConfirmKeyboard, buildCommandsListKeyboard, calculateCommandsPaginationRange, COMMANDS_CALLBACK_CANCEL, COMMANDS_CALLBACK_EXECUTE, COMMANDS_CALLBACK_PREFIX, formatCommandsSelectText, formatExecutingCommandMessage, parseCommandPageCallback, parseCommandSelectCallback, } from "../menus/command-catalog-menu.js";
|
|
62
17
|
function getCallbackMessageId(ctx) {
|
|
63
18
|
const message = ctx.callbackQuery?.message;
|
|
64
19
|
if (!message || !("message_id" in message)) {
|
|
@@ -67,53 +22,6 @@ function getCallbackMessageId(ctx) {
|
|
|
67
22
|
const messageId = message.message_id;
|
|
68
23
|
return typeof messageId === "number" ? messageId : null;
|
|
69
24
|
}
|
|
70
|
-
function formatCommandButtonLabel(command) {
|
|
71
|
-
const description = command.description?.trim() || t("commands.no_description");
|
|
72
|
-
const rawLabel = `/${command.name} - ${description}`;
|
|
73
|
-
if (rawLabel.length <= MAX_INLINE_BUTTON_LABEL_LENGTH) {
|
|
74
|
-
return rawLabel;
|
|
75
|
-
}
|
|
76
|
-
return `${rawLabel.slice(0, MAX_INLINE_BUTTON_LABEL_LENGTH - 3)}...`;
|
|
77
|
-
}
|
|
78
|
-
export function calculateCommandsPaginationRange(totalCommands, page, pageSize) {
|
|
79
|
-
const safePageSize = Math.max(1, pageSize);
|
|
80
|
-
const totalPages = Math.max(1, Math.ceil(totalCommands / safePageSize));
|
|
81
|
-
const normalizedPage = Math.min(Math.max(0, page), totalPages - 1);
|
|
82
|
-
const startIndex = normalizedPage * safePageSize;
|
|
83
|
-
const endIndex = Math.min(startIndex + safePageSize, totalCommands);
|
|
84
|
-
return {
|
|
85
|
-
page: normalizedPage,
|
|
86
|
-
totalPages,
|
|
87
|
-
startIndex,
|
|
88
|
-
endIndex,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function buildCommandsListKeyboard(commands, page, pageSize) {
|
|
92
|
-
const keyboard = new InlineKeyboard();
|
|
93
|
-
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateCommandsPaginationRange(commands.length, page, pageSize);
|
|
94
|
-
commands.slice(startIndex, endIndex).forEach((command, index) => {
|
|
95
|
-
const globalIndex = startIndex + index;
|
|
96
|
-
keyboard
|
|
97
|
-
.text(formatCommandButtonLabel(command), `${COMMANDS_CALLBACK_SELECT_PREFIX}${globalIndex}`)
|
|
98
|
-
.row();
|
|
99
|
-
});
|
|
100
|
-
if (totalPages > 1) {
|
|
101
|
-
if (normalizedPage > 0) {
|
|
102
|
-
keyboard.text(t("commands.button.prev_page"), buildCommandPageCallback(normalizedPage - 1));
|
|
103
|
-
}
|
|
104
|
-
if (normalizedPage < totalPages - 1) {
|
|
105
|
-
keyboard.text(t("commands.button.next_page"), buildCommandPageCallback(normalizedPage + 1));
|
|
106
|
-
}
|
|
107
|
-
keyboard.row();
|
|
108
|
-
}
|
|
109
|
-
keyboard.text(t("commands.button.cancel"), COMMANDS_CALLBACK_CANCEL);
|
|
110
|
-
return keyboard;
|
|
111
|
-
}
|
|
112
|
-
function buildCommandsConfirmKeyboard() {
|
|
113
|
-
return new InlineKeyboard()
|
|
114
|
-
.text(t("commands.button.execute"), COMMANDS_CALLBACK_EXECUTE)
|
|
115
|
-
.text(t("commands.button.cancel"), COMMANDS_CALLBACK_CANCEL);
|
|
116
|
-
}
|
|
117
25
|
function parseCommandItems(value) {
|
|
118
26
|
if (!Array.isArray(value)) {
|
|
119
27
|
return null;
|
|
@@ -135,7 +43,7 @@ function parseCommandItems(value) {
|
|
|
135
43
|
}
|
|
136
44
|
return commands;
|
|
137
45
|
}
|
|
138
|
-
function parseCommandsMetadata(state) {
|
|
46
|
+
export function parseCommandsMetadata(state) {
|
|
139
47
|
if (!state || state.kind !== "custom") {
|
|
140
48
|
return null;
|
|
141
49
|
}
|
|
@@ -180,40 +88,12 @@ function parseCommandsMetadata(state) {
|
|
|
180
88
|
}
|
|
181
89
|
return null;
|
|
182
90
|
}
|
|
183
|
-
function clearCommandsInteraction(reason) {
|
|
91
|
+
export function clearCommandsInteraction(reason) {
|
|
184
92
|
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
185
93
|
if (metadata) {
|
|
186
94
|
interactionManager.clear(reason);
|
|
187
95
|
}
|
|
188
96
|
}
|
|
189
|
-
async function getCommandList(projectDirectory) {
|
|
190
|
-
const { data, error } = await opencodeClient.command.list({
|
|
191
|
-
directory: normalizeDirectoryForCommandApi(projectDirectory),
|
|
192
|
-
});
|
|
193
|
-
if (error || !data) {
|
|
194
|
-
throw error || new Error("No command data received");
|
|
195
|
-
}
|
|
196
|
-
return data
|
|
197
|
-
.filter((command) => {
|
|
198
|
-
const source = command.source;
|
|
199
|
-
return (typeof command.name === "string" && command.name.trim().length > 0 && source === "command");
|
|
200
|
-
})
|
|
201
|
-
.map((command) => ({
|
|
202
|
-
name: command.name,
|
|
203
|
-
description: command.description,
|
|
204
|
-
}));
|
|
205
|
-
}
|
|
206
|
-
function parseSelectIndex(data) {
|
|
207
|
-
if (!data.startsWith(COMMANDS_CALLBACK_SELECT_PREFIX)) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
const rawIndex = data.slice(COMMANDS_CALLBACK_SELECT_PREFIX.length);
|
|
211
|
-
const index = Number(rawIndex);
|
|
212
|
-
if (!Number.isInteger(index) || index < 0) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
return index;
|
|
216
|
-
}
|
|
217
97
|
async function isSessionBusy(sessionId, directory) {
|
|
218
98
|
try {
|
|
219
99
|
const { data, error } = await opencodeClient.session.status({ directory });
|
|
@@ -265,7 +145,7 @@ async function ensureSessionForProject(ctx, projectDirectory) {
|
|
|
265
145
|
await ctx.reply(t("bot.session_created", { title: session.title }));
|
|
266
146
|
return sessionInfo;
|
|
267
147
|
}
|
|
268
|
-
async function executeCommand(ctx, deps, params) {
|
|
148
|
+
export async function executeCommand(ctx, deps, params) {
|
|
269
149
|
if (!ctx.chat) {
|
|
270
150
|
return;
|
|
271
151
|
}
|
|
@@ -342,41 +222,6 @@ async function executeCommand(ctx, deps, params) {
|
|
|
342
222
|
},
|
|
343
223
|
});
|
|
344
224
|
}
|
|
345
|
-
export async function commandsCommand(ctx) {
|
|
346
|
-
try {
|
|
347
|
-
const currentProject = getCurrentProject();
|
|
348
|
-
if (!currentProject) {
|
|
349
|
-
await ctx.reply(t("bot.project_not_selected"));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const commands = await getCommandList(currentProject.worktree);
|
|
353
|
-
if (commands.length === 0) {
|
|
354
|
-
await ctx.reply(t("commands.empty"));
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const pageSize = config.bot.commandsListLimit;
|
|
358
|
-
const keyboard = buildCommandsListKeyboard(commands, 0, pageSize);
|
|
359
|
-
const message = await ctx.reply(formatCommandsSelectText(0), {
|
|
360
|
-
reply_markup: keyboard,
|
|
361
|
-
});
|
|
362
|
-
interactionManager.start({
|
|
363
|
-
kind: "custom",
|
|
364
|
-
expectedInput: "callback",
|
|
365
|
-
metadata: {
|
|
366
|
-
flow: "commands",
|
|
367
|
-
stage: "list",
|
|
368
|
-
messageId: message.message_id,
|
|
369
|
-
projectDirectory: currentProject.worktree,
|
|
370
|
-
commands,
|
|
371
|
-
page: 0,
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
logger.error("[Commands] Error fetching commands list:", error);
|
|
377
|
-
await ctx.reply(t("commands.fetch_error"));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
225
|
export async function handleCommandsCallback(ctx, deps) {
|
|
381
226
|
const data = ctx.callbackQuery?.data;
|
|
382
227
|
if (!data || !data.startsWith(COMMANDS_CALLBACK_PREFIX)) {
|
|
@@ -440,7 +285,7 @@ export async function handleCommandsCallback(ctx, deps) {
|
|
|
440
285
|
});
|
|
441
286
|
return true;
|
|
442
287
|
}
|
|
443
|
-
const commandIndex =
|
|
288
|
+
const commandIndex = parseCommandSelectCallback(data);
|
|
444
289
|
if (commandIndex === null || metadata.stage !== "list") {
|
|
445
290
|
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
446
291
|
return true;
|
|
@@ -473,28 +318,3 @@ export async function handleCommandsCallback(ctx, deps) {
|
|
|
473
318
|
return true;
|
|
474
319
|
}
|
|
475
320
|
}
|
|
476
|
-
export async function handleCommandTextArguments(ctx, deps) {
|
|
477
|
-
const text = ctx.message?.text;
|
|
478
|
-
if (!text || text.startsWith("/")) {
|
|
479
|
-
return false;
|
|
480
|
-
}
|
|
481
|
-
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
482
|
-
if (!metadata || metadata.stage !== "confirm") {
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
const argumentsText = text.trim();
|
|
486
|
-
if (!argumentsText) {
|
|
487
|
-
await ctx.reply(t("commands.arguments_empty"));
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
clearCommandsInteraction("commands_arguments_submitted");
|
|
491
|
-
if (ctx.chat) {
|
|
492
|
-
await ctx.api.deleteMessage(ctx.chat.id, metadata.messageId).catch(() => { });
|
|
493
|
-
}
|
|
494
|
-
await executeCommand(ctx, deps, {
|
|
495
|
-
projectDirectory: metadata.projectDirectory,
|
|
496
|
-
commandName: metadata.commandName,
|
|
497
|
-
argumentsText,
|
|
498
|
-
});
|
|
499
|
-
return true;
|
|
500
|
-
}
|
|
@@ -1,38 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getCurrentSession } from "../../session/manager.js";
|
|
1
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
3
2
|
import { opencodeClient } from "../../opencode/client.js";
|
|
4
|
-
import {
|
|
5
|
-
import { clearActiveInlineMenu, ensureActiveInlineMenu, replyWithInlineMenu, } from "./inline-menu.js";
|
|
3
|
+
import { getCurrentSession } from "../../app/services/session-service.js";
|
|
6
4
|
import { logger } from "../../utils/logger.js";
|
|
7
5
|
import { t } from "../../i18n/index.js";
|
|
8
|
-
|
|
9
|
-
* Build inline keyboard with compact confirmation menu
|
|
10
|
-
* @returns InlineKeyboard with confirmation button
|
|
11
|
-
*/
|
|
12
|
-
export function buildCompactConfirmationMenu() {
|
|
13
|
-
const keyboard = new InlineKeyboard();
|
|
14
|
-
keyboard.text(t("context.button.confirm"), "compact:confirm");
|
|
15
|
-
return keyboard;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Handle context button press (text message from Reply Keyboard)
|
|
19
|
-
* Shows inline menu with compact confirmation
|
|
20
|
-
* @param ctx grammY context
|
|
21
|
-
*/
|
|
22
|
-
export async function handleContextButtonPress(ctx) {
|
|
23
|
-
logger.debug("[ContextHandler] Context button pressed");
|
|
24
|
-
const session = getCurrentSession();
|
|
25
|
-
if (!session) {
|
|
26
|
-
await ctx.reply(t("context.no_active_session"));
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const keyboard = buildCompactConfirmationMenu();
|
|
30
|
-
await replyWithInlineMenu(ctx, {
|
|
31
|
-
menuKind: "context",
|
|
32
|
-
text: t("context.confirm_text", { title: session.title }),
|
|
33
|
-
keyboard,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
6
|
+
import { clearActiveInlineMenu, ensureActiveInlineMenu } from "../menus/inline-menu.js";
|
|
36
7
|
/**
|
|
37
8
|
* Handle compact confirmation callback
|
|
38
9
|
* Calls OpenCode API to compact the session
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { getProjectRoot, isPathWithinDirectory, isWithinAllowedRoot, isWithinProjectRoot, pathToDisplayPath, } from "../../app/services/file-browser-service.js";
|
|
2
|
+
import { isForegroundBusy } from "../../app/services/run-control-service.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
4
|
+
import { getProjectByWorktree } from "../../app/services/project-service.js";
|
|
5
|
+
import { upsertSessionDirectory } from "../../app/services/session-cache-service.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
|
+
import { replyBusyBlocked } from "../messages/busy-blocked-renderer.js";
|
|
8
|
+
import { ensureActiveInlineMenu, clearActiveInlineMenu } from "../menus/inline-menu.js";
|
|
9
|
+
import { sendDownloadedFile } from "../messages/send-downloaded-file.js";
|
|
10
|
+
import { switchToProject } from "../../app/services/project-switch-service.js";
|
|
11
|
+
import { createProjectSwitchPresentation } from "../services/project-switch-presentation.js";
|
|
12
|
+
import { buildOpenRootsKeyboard, clearLsPathIndex, clearOpenPathIndex, decodeLsBackCallback, decodeLsFileCallback, decodeLsPaginationCallback, decodeLsPathFromCallback, decodeOpenPaginationCallback, decodeOpenPathFromCallback, LS_CALLBACK_DOWNLOAD_PREFIX, LS_CALLBACK_NAV_PREFIX, LS_CALLBACK_PREFIX, OPEN_CALLBACK_NAV_PREFIX, OPEN_CALLBACK_PREFIX, OPEN_CALLBACK_ROOTS, OPEN_CALLBACK_SELECT_PREFIX, renderLsBrowseView, renderLsFileDetailsView, renderOpenBrowseView, } from "../menus/file-browser-menu.js";
|
|
13
|
+
const sessionDirectories = new Map();
|
|
14
|
+
export function clearSessionDirectories() {
|
|
15
|
+
sessionDirectories.clear();
|
|
16
|
+
}
|
|
17
|
+
export function resolveInitialLsDirectory(userId) {
|
|
18
|
+
const currentProject = getProjectRoot();
|
|
19
|
+
if (!currentProject) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (userId) {
|
|
23
|
+
const cachedDirectory = sessionDirectories.get(userId);
|
|
24
|
+
if (cachedDirectory && isPathWithinDirectory(cachedDirectory, currentProject)) {
|
|
25
|
+
return cachedDirectory;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return currentProject;
|
|
29
|
+
}
|
|
30
|
+
export function rememberLsDirectory(userId, directory) {
|
|
31
|
+
if (userId) {
|
|
32
|
+
sessionDirectories.set(userId, directory);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function handleOpenCallback(ctx, deps = {}) {
|
|
36
|
+
const data = ctx.callbackQuery?.data;
|
|
37
|
+
if (!data || !data.startsWith(OPEN_CALLBACK_PREFIX)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (isForegroundBusy()) {
|
|
41
|
+
await replyBusyBlocked(ctx);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const isActiveMenu = await ensureActiveInlineMenu(ctx, "open");
|
|
45
|
+
if (!isActiveMenu) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
if (data === OPEN_CALLBACK_ROOTS) {
|
|
50
|
+
await showOpenRoots(ctx);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const navPath = decodeOpenPathFromCallback(OPEN_CALLBACK_NAV_PREFIX, data);
|
|
54
|
+
if (navPath !== null) {
|
|
55
|
+
if (!isWithinAllowedRoot(navPath)) {
|
|
56
|
+
await ctx.answerCallbackQuery({ text: t("open.access_denied") });
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
await navigateOpenTo(ctx, navPath);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const pageInfo = decodeOpenPaginationCallback(data);
|
|
63
|
+
if (pageInfo !== null) {
|
|
64
|
+
if (!isWithinAllowedRoot(pageInfo.path)) {
|
|
65
|
+
await ctx.answerCallbackQuery({ text: t("open.access_denied") });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
await navigateOpenTo(ctx, pageInfo.path, pageInfo.page);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const selectPath = decodeOpenPathFromCallback(OPEN_CALLBACK_SELECT_PREFIX, data);
|
|
72
|
+
if (selectPath !== null) {
|
|
73
|
+
if (!isWithinAllowedRoot(selectPath)) {
|
|
74
|
+
await ctx.answerCallbackQuery({ text: t("open.access_denied") });
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
await selectDirectory(ctx, selectPath, deps);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger.error("[Bot] Error handling open callback:", error);
|
|
84
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function showOpenRoots(ctx) {
|
|
89
|
+
await ctx.answerCallbackQuery();
|
|
90
|
+
await ctx.editMessageText(t("open.select_root"), { reply_markup: buildOpenRootsKeyboard() });
|
|
91
|
+
}
|
|
92
|
+
async function navigateOpenTo(ctx, dirPath, page = 0) {
|
|
93
|
+
const view = await renderOpenBrowseView(dirPath, page);
|
|
94
|
+
if ("error" in view) {
|
|
95
|
+
await ctx.answerCallbackQuery({ text: view.error });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await ctx.answerCallbackQuery();
|
|
99
|
+
await ctx.editMessageText(view.text, { reply_markup: view.keyboard });
|
|
100
|
+
}
|
|
101
|
+
async function selectDirectory(ctx, directory, deps = {}) {
|
|
102
|
+
const displayPath = pathToDisplayPath(directory);
|
|
103
|
+
try {
|
|
104
|
+
logger.info(`[Bot] Adding project directory: ${directory}`);
|
|
105
|
+
await upsertSessionDirectory(directory, Date.now());
|
|
106
|
+
const projectInfo = await getProjectByWorktree(directory);
|
|
107
|
+
const selectedProjectInfo = { ...projectInfo, name: displayPath };
|
|
108
|
+
const replyKeyboard = await switchToProject(ctx, selectedProjectInfo, "open_project_selected", {
|
|
109
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
110
|
+
presentation: createProjectSwitchPresentation(),
|
|
111
|
+
});
|
|
112
|
+
await ctx.answerCallbackQuery();
|
|
113
|
+
await ctx.reply(t("open.selected", { project: displayPath }), { reply_markup: replyKeyboard });
|
|
114
|
+
await ctx.deleteMessage();
|
|
115
|
+
clearOpenPathIndex();
|
|
116
|
+
logger.info(`[Bot] Project added and selected: ${displayPath}`);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.error("[Bot] Error selecting directory:", error);
|
|
120
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") });
|
|
121
|
+
await ctx.reply(t("open.select_error"));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export async function handleLsCallback(ctx) {
|
|
125
|
+
const data = ctx.callbackQuery?.data;
|
|
126
|
+
if (!data || !data.startsWith(LS_CALLBACK_PREFIX)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (isForegroundBusy()) {
|
|
130
|
+
await replyBusyBlocked(ctx);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
const isActiveMenu = await ensureActiveInlineMenu(ctx, "ls");
|
|
134
|
+
if (!isActiveMenu) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const navPath = decodeLsPathFromCallback(LS_CALLBACK_NAV_PREFIX, data);
|
|
139
|
+
if (navPath !== null) {
|
|
140
|
+
if (!isWithinProjectRoot(navPath)) {
|
|
141
|
+
await ctx.answerCallbackQuery({ text: t("ls.access_denied") });
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
await navigateLsTo(ctx, navPath);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const pageInfo = decodeLsPaginationCallback(data);
|
|
148
|
+
if (pageInfo !== null) {
|
|
149
|
+
if (!isWithinProjectRoot(pageInfo.path)) {
|
|
150
|
+
await ctx.answerCallbackQuery({ text: t("ls.access_denied") });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
await navigateLsTo(ctx, pageInfo.path, pageInfo.page);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
const fileInfo = decodeLsFileCallback(data);
|
|
157
|
+
if (fileInfo !== null) {
|
|
158
|
+
if (!isWithinProjectRoot(fileInfo.path)) {
|
|
159
|
+
await ctx.answerCallbackQuery({ text: t("ls.access_denied") });
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
await showLsFileDetails(ctx, fileInfo.path, fileInfo.page);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
const downloadPath = decodeLsPathFromCallback(LS_CALLBACK_DOWNLOAD_PREFIX, data);
|
|
166
|
+
if (downloadPath !== null) {
|
|
167
|
+
if (!isWithinProjectRoot(downloadPath)) {
|
|
168
|
+
await ctx.answerCallbackQuery({ text: t("ls.access_denied") });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
await downloadFileAndClose(ctx, downloadPath);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const backInfo = decodeLsBackCallback(data);
|
|
175
|
+
if (backInfo !== null) {
|
|
176
|
+
if (!isWithinProjectRoot(backInfo.path)) {
|
|
177
|
+
await ctx.answerCallbackQuery({ text: t("ls.access_denied") });
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
await navigateLsTo(ctx, backInfo.path, backInfo.page);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
logger.error("[Ls] Error handling callback:", error);
|
|
187
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") });
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function navigateLsTo(ctx, dirPath, page = 0) {
|
|
192
|
+
const view = await renderLsBrowseView(dirPath, page);
|
|
193
|
+
if ("error" in view) {
|
|
194
|
+
await ctx.answerCallbackQuery({ text: view.error });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
rememberLsDirectory(ctx.from?.id, dirPath);
|
|
198
|
+
await ctx.answerCallbackQuery();
|
|
199
|
+
await ctx.editMessageText(view.text, { parse_mode: "HTML", reply_markup: view.keyboard });
|
|
200
|
+
}
|
|
201
|
+
async function showLsFileDetails(ctx, filePath, page) {
|
|
202
|
+
const view = await renderLsFileDetailsView(filePath, page);
|
|
203
|
+
if ("error" in view) {
|
|
204
|
+
await ctx.answerCallbackQuery({ text: view.error });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
await ctx.answerCallbackQuery();
|
|
208
|
+
await ctx.editMessageText(view.text, { parse_mode: "HTML", reply_markup: view.keyboard });
|
|
209
|
+
}
|
|
210
|
+
async function downloadFileAndClose(ctx, filePath) {
|
|
211
|
+
await ctx.answerCallbackQuery({ text: t("commands.download.downloading") });
|
|
212
|
+
const downloaded = await sendDownloadedFile(ctx, filePath, { announce: false });
|
|
213
|
+
if (!downloaded) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
clearActiveInlineMenu("ls_downloaded");
|
|
217
|
+
clearLsPathIndex();
|
|
218
|
+
await ctx.deleteMessage().catch(() => { });
|
|
219
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { clearActiveInlineMenu, ensureActiveInlineMenu, INLINE_MENU_CANCEL_PREFIX, isInlineMenuKind, LEGACY_CONTEXT_CANCEL_CALLBACK, } from "../menus/inline-menu.js";
|
|
2
|
+
import { t } from "../../i18n/index.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
export async function handleInlineMenuCancel(ctx) {
|
|
5
|
+
const data = ctx.callbackQuery?.data;
|
|
6
|
+
if (!data) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
let menuKind = null;
|
|
10
|
+
if (data === LEGACY_CONTEXT_CANCEL_CALLBACK) {
|
|
11
|
+
menuKind = "context";
|
|
12
|
+
}
|
|
13
|
+
else if (data.startsWith(INLINE_MENU_CANCEL_PREFIX)) {
|
|
14
|
+
const rawKind = data.slice(INLINE_MENU_CANCEL_PREFIX.length);
|
|
15
|
+
if (!isInlineMenuKind(rawKind)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
menuKind = rawKind;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const isActive = await ensureActiveInlineMenu(ctx, menuKind);
|
|
24
|
+
if (!isActive) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
clearActiveInlineMenu(`inline_menu_cancel:${menuKind}`);
|
|
28
|
+
await ctx.answerCallbackQuery({ text: t("inline.cancelled_callback") }).catch(() => { });
|
|
29
|
+
await ctx.deleteMessage().catch(() => { });
|
|
30
|
+
logger.debug(`[InlineMenu] Menu cancelled: kind=${menuKind}`);
|
|
31
|
+
return true;
|
|
32
|
+
}
|