@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.
Files changed (193) hide show
  1. package/.env.example +10 -15
  2. package/README.md +33 -13
  3. package/dist/app/{start-bot-app.js → bootstrap/start-bot-app.js} +16 -15
  4. package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js} +1 -1
  5. package/dist/{summary → app/formatters}/subagent-formatter.js +8 -3
  6. package/dist/{summary/formatter.js → app/formatters/summary-formatter.js} +20 -132
  7. package/dist/{summary → app/formatters}/tool-message-batcher.js +1 -1
  8. package/dist/{bot/assistant-run-state.js → app/managers/assistant-run-state-manager.js} +1 -1
  9. package/dist/{attach/manager.js → app/managers/attach-manager.js} +1 -1
  10. package/dist/{background-session/tracker.js → app/managers/background-session-manager.js} +2 -2
  11. package/dist/{scheduled-task/foreground-state.js → app/managers/foreground-session-state-manager.js} +1 -1
  12. package/dist/{interaction/manager.js → app/managers/interaction-manager.js} +31 -1
  13. package/dist/{permission/manager.js → app/managers/permission-manager.js} +1 -1
  14. package/dist/{question/manager.js → app/managers/question-manager.js} +1 -1
  15. package/dist/{rename/manager.js → app/managers/rename-manager.js} +1 -1
  16. package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js} +2 -2
  17. package/dist/{summary/aggregator.js → app/managers/summary-aggregation-manager.js} +177 -13
  18. package/dist/{agent/manager.js → app/services/agent-selection-service.js} +4 -5
  19. package/dist/{attach/service.js → app/services/attach-service.js} +23 -44
  20. package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js} +12 -7
  21. package/dist/app/services/command-catalog-service.js +21 -0
  22. package/dist/{bot/utils/external-user-input.js → app/services/external-user-input-service.js} +4 -19
  23. package/dist/app/services/file-browser-service.js +265 -0
  24. package/dist/{bot/utils/file-download.js → app/services/file-download-service.js} +14 -28
  25. package/dist/app/services/mcp-catalog-service.js +79 -0
  26. package/dist/app/services/message-history-service.js +81 -0
  27. package/dist/{model/capabilities.js → app/services/model-capabilities-service.js} +2 -2
  28. package/dist/{model/context-limit.js → app/services/model-context-limit-service.js} +4 -4
  29. package/dist/{model/manager.js → app/services/model-selection-service.js} +4 -4
  30. package/dist/{project/manager.js → app/services/project-service.js} +3 -3
  31. package/dist/app/services/project-switch-service.js +39 -0
  32. package/dist/{bot/utils/busy-guard.js → app/services/run-control-service.js} +3 -14
  33. package/dist/{scheduled-task/executor.js → app/services/scheduled-task-executor-service.js} +27 -12
  34. package/dist/{scheduled-task/runtime.js → app/services/scheduled-task-runtime-service.js} +17 -56
  35. package/dist/{scheduled-task/schedule-parser.js → app/services/scheduled-task-schedule-parser-service.js} +3 -3
  36. package/dist/{scheduled-task/session-ignore.js → app/services/scheduled-task-session-ignore-service.js} +1 -1
  37. package/dist/{session/cache-manager.js → app/services/session-cache-service.js} +3 -3
  38. package/dist/{session/manager.js → app/services/session-service.js} +1 -1
  39. package/dist/app/services/skills-catalog-service.js +21 -0
  40. package/dist/{stt/client.js → app/services/stt-service.js} +2 -2
  41. package/dist/{tts/client.js → app/services/tts-service.js} +68 -5
  42. package/dist/{variant/manager.js → app/services/variant-selection-service.js} +3 -3
  43. package/dist/{scheduled-task/store.js → app/stores/scheduled-task-store.js} +3 -3
  44. package/dist/{settings/manager.js → app/stores/settings-store.js} +49 -7
  45. package/dist/app/types/worktree.js +1 -0
  46. package/dist/bot/{handlers/agent.js → callbacks/agent-selection-callback-handler.js} +8 -57
  47. package/dist/bot/callbacks/callback-router.js +113 -0
  48. package/dist/bot/{commands/commands.js → callbacks/command-catalog-callback-handler.js} +17 -197
  49. package/dist/bot/{handlers/context.js → callbacks/context-control-callback-handler.js} +3 -32
  50. package/dist/bot/callbacks/file-browser-callback-handler.js +219 -0
  51. package/dist/bot/callbacks/inline-menu-cancel-callback-handler.js +32 -0
  52. package/dist/bot/{commands/mcps.js → callbacks/mcp-catalog-callback-handler.js} +7 -172
  53. package/dist/bot/{commands/messages.js → callbacks/message-history-callback-handler.js} +17 -248
  54. package/dist/bot/{handlers/model.js → callbacks/model-selection-callback-handler.js} +10 -71
  55. package/dist/bot/callbacks/permission-callback-handler.js +114 -0
  56. package/dist/bot/callbacks/project-callback-handler.js +79 -0
  57. package/dist/bot/callbacks/question-callback-handler.js +159 -0
  58. package/dist/bot/{commands/rename.js → callbacks/rename-callback-handler.js} +6 -34
  59. package/dist/bot/{commands/tasklist.js → callbacks/scheduled-task-callback-handler.js} +83 -68
  60. package/dist/bot/{commands/sessions.js → callbacks/session-callback-handler.js} +19 -158
  61. package/dist/bot/callbacks/settings-callback-handler.js +105 -0
  62. package/dist/bot/{commands/skills.js → callbacks/skills-catalog-callback-handler.js} +8 -188
  63. package/dist/bot/{handlers/variant.js → callbacks/variant-selection-callback-handler.js} +7 -69
  64. package/dist/bot/callbacks/worktree-callback-handler.js +97 -0
  65. package/dist/bot/commands/{abort.js → abort-command.js} +6 -6
  66. package/dist/bot/commands/command-catalog-command.js +42 -0
  67. package/dist/bot/commands/definitions.js +1 -1
  68. package/dist/bot/commands/{detach.js → detach-command.js} +8 -8
  69. package/dist/bot/commands/ls-command.js +48 -0
  70. package/dist/bot/commands/mcp-catalog-command.js +39 -0
  71. package/dist/bot/commands/messages-command.js +59 -0
  72. package/dist/bot/commands/{new.js → new-command.js} +12 -11
  73. package/dist/bot/commands/open-command.js +45 -0
  74. package/dist/bot/commands/{opencode-start.js → opencode-start-command.js} +1 -1
  75. package/dist/bot/commands/{opencode-stop.js → opencode-stop-command.js} +1 -1
  76. package/dist/bot/commands/projects-command.js +32 -0
  77. package/dist/bot/commands/rename-command.js +33 -0
  78. package/dist/bot/commands/sessions-command.js +42 -0
  79. package/dist/bot/commands/settings-command.js +10 -0
  80. package/dist/bot/commands/skills-catalog-command.js +42 -0
  81. package/dist/bot/commands/{start.js → start-command.js} +12 -12
  82. package/dist/bot/commands/{status.js → status-command.js} +15 -10
  83. package/dist/bot/commands/{task.js → task-command.js} +9 -81
  84. package/dist/bot/commands/tasklist-command.js +40 -0
  85. package/dist/bot/commands/worktree-command.js +47 -0
  86. package/dist/bot/handlers/{document.js → document-handler.js} +3 -3
  87. package/dist/bot/handlers/{media-group.js → media-group-handler.js} +3 -3
  88. package/dist/bot/handlers/photo-handler.js +44 -0
  89. package/dist/bot/handlers/prompt.js +17 -17
  90. package/dist/bot/handlers/text-message-handler.js +61 -0
  91. package/dist/bot/handlers/tts-response-handler.js +29 -0
  92. package/dist/bot/handlers/{voice.js → voice-handler.js} +6 -3
  93. package/dist/bot/index.js +27 -1179
  94. package/dist/{keyboard/manager.js → bot/keyboards/keyboard-manager.js} +6 -6
  95. package/dist/bot/keyboards/keyboard-types.js +1 -0
  96. package/dist/bot/{utils/keyboard.js → keyboards/main-reply-keyboard.js} +2 -2
  97. package/dist/bot/menus/agent-selection-menu.js +54 -0
  98. package/dist/bot/menus/command-catalog-menu.js +101 -0
  99. package/dist/bot/menus/context-control-menu.js +33 -0
  100. package/dist/bot/menus/file-browser-menu.js +265 -0
  101. package/dist/bot/{handlers → menus}/inline-menu.js +7 -34
  102. package/dist/bot/menus/mcp-catalog-menu.js +97 -0
  103. package/dist/bot/menus/message-history-menu.js +107 -0
  104. package/dist/bot/menus/model-selection-menu.js +66 -0
  105. package/dist/bot/menus/permission-menu.js +116 -0
  106. package/dist/bot/{commands/projects.js → menus/project-selection-menu.js} +5 -105
  107. package/dist/bot/{handlers/question.js → menus/question-menu.js} +10 -166
  108. package/dist/bot/menus/rename-menu.js +6 -0
  109. package/dist/bot/menus/scheduled-task-menu.js +42 -0
  110. package/dist/bot/menus/session-selection-menu.js +115 -0
  111. package/dist/bot/menus/settings-menu.js +52 -0
  112. package/dist/bot/menus/skills-catalog-menu.js +99 -0
  113. package/dist/bot/menus/variant-selection-menu.js +67 -0
  114. package/dist/bot/menus/worktree-selection-menu.js +76 -0
  115. package/dist/bot/{utils → messages}/assistant-rendering.js +2 -2
  116. package/dist/bot/messages/busy-blocked-renderer.js +11 -0
  117. package/dist/bot/messages/external-user-input-notification.js +22 -0
  118. package/dist/bot/messages/scheduled-task-delivery.js +63 -0
  119. package/dist/bot/{utils → messages}/send-downloaded-file.js +1 -1
  120. package/dist/bot/messages/summary-message-formatter.js +130 -0
  121. package/dist/bot/{utils → messages}/thinking-message.js +1 -4
  122. package/dist/bot/messages/thinking-rendering.js +66 -0
  123. package/dist/{interaction/guard.js → bot/middleware/interaction-guard-decision.js} +11 -4
  124. package/dist/bot/middleware/interaction-guard.js +2 -2
  125. package/dist/bot/middleware/unknown-command.js +1 -1
  126. package/dist/{pinned/format.js → bot/pinned/pinned-message-format.js} +1 -2
  127. package/dist/{pinned/manager.js → bot/pinned/pinned-message-manager.js} +10 -10
  128. package/dist/bot/pinned/pinned-message-types.js +1 -0
  129. package/dist/{summary → bot/render}/markdown-to-telegram-v2.js +2 -2
  130. package/dist/bot/render/types.js +1 -0
  131. package/dist/bot/routers/command-router.js +73 -0
  132. package/dist/bot/routers/message-router.js +150 -0
  133. package/dist/bot/services/attach-presentation.js +40 -0
  134. package/dist/bot/services/event-subscription-service.js +1116 -0
  135. package/dist/bot/services/project-switch-presentation.js +26 -0
  136. package/dist/bot/streaming/compact-progress-streamer.js +162 -0
  137. package/dist/bot/{utils → streaming}/finalize-assistant-response.js +9 -3
  138. package/dist/bot/streaming/response-streamer.js +16 -1
  139. package/dist/cli.js +4 -4
  140. package/dist/config.js +9 -18
  141. package/dist/i18n/ar.js +31 -6
  142. package/dist/i18n/de.js +32 -7
  143. package/dist/i18n/en.js +32 -7
  144. package/dist/i18n/es.js +32 -7
  145. package/dist/i18n/fr.js +32 -7
  146. package/dist/i18n/ru.js +32 -7
  147. package/dist/i18n/zh.js +32 -7
  148. package/dist/index.js +1 -1
  149. package/dist/opencode/ready-refresh.js +2 -2
  150. package/dist/{service → runtime/service}/manager.js +2 -2
  151. package/dist/runtime/service/types.js +1 -0
  152. package/package.json +1 -1
  153. package/dist/bot/commands/ls.js +0 -445
  154. package/dist/bot/commands/open.js +0 -319
  155. package/dist/bot/commands/tts.js +0 -13
  156. package/dist/bot/commands/worktree.js +0 -201
  157. package/dist/bot/handlers/permission.js +0 -235
  158. package/dist/bot/utils/browser-roots.js +0 -140
  159. package/dist/bot/utils/file-tree.js +0 -92
  160. package/dist/bot/utils/send-tts-response.js +0 -37
  161. package/dist/bot/utils/switch-project.js +0 -45
  162. package/dist/bot/utils/telegram-file-url.js +0 -11
  163. package/dist/interaction/busy.js +0 -8
  164. package/dist/interaction/cleanup.js +0 -32
  165. /package/dist/{scheduled-task/display.js → app/formatters/scheduled-task-display-formatter.js} +0 -0
  166. /package/dist/{bot/utils/abort-error-suppression.js → app/managers/abort-suppression-manager.js} +0 -0
  167. /package/dist/{external-input/suppression.js → app/managers/external-input-suppression-manager.js} +0 -0
  168. /package/dist/{scheduled-task/next-run.js → app/services/scheduled-task-next-run-service.js} +0 -0
  169. /package/dist/{git/worktree.js → app/services/worktree-service.js} +0 -0
  170. /package/dist/{agent/types.js → app/types/agent.js} +0 -0
  171. /package/dist/{interaction/types.js → app/types/interaction.js} +0 -0
  172. /package/dist/{model/types.js → app/types/model.js} +0 -0
  173. /package/dist/{keyboard/types.js → app/types/permission.js} +0 -0
  174. /package/dist/{permission/types.js → app/types/project.js} +0 -0
  175. /package/dist/{pinned/types.js → app/types/question.js} +0 -0
  176. /package/dist/{scheduled-task/types.js → app/types/scheduled-task.js} +0 -0
  177. /package/dist/{question/types.js → app/types/session.js} +0 -0
  178. /package/dist/{service/types.js → app/types/settings.js} +0 -0
  179. /package/dist/{telegram/render/types.js → app/types/summary.js} +0 -0
  180. /package/dist/{variant/types.js → app/types/variant.js} +0 -0
  181. /package/dist/bot/commands/{help.js → help-command.js} +0 -0
  182. /package/dist/bot/{utils → messages}/send-with-markdown-fallback.js +0 -0
  183. /package/dist/bot/{utils → messages}/telegram-text.js +0 -0
  184. /package/dist/{telegram → bot}/render/block-fallback.js +0 -0
  185. /package/dist/{telegram → bot}/render/block-parser.js +0 -0
  186. /package/dist/{telegram → bot}/render/block-renderer.js +0 -0
  187. /package/dist/{telegram → bot}/render/chunker.js +0 -0
  188. /package/dist/{telegram → bot}/render/inline-renderer.js +0 -0
  189. /package/dist/{telegram → bot}/render/markdown-normalizer.js +0 -0
  190. /package/dist/{telegram → bot}/render/pipeline.js +0 -0
  191. /package/dist/{telegram → bot}/render/validator.js +0 -0
  192. /package/dist/bot/{utils/commands.js → routers/command-utils.js} +0 -0
  193. /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
@@ -0,0 +1,66 @@
1
+ import { t } from "../../i18n/index.js";
2
+ function formatHeader(title) {
3
+ const fallback = t("bot.thinking");
4
+ const normalizedTitle = title?.trim();
5
+ return normalizedTitle ? `${fallback} — ${normalizedTitle}` : fallback;
6
+ }
7
+ function quoteFallbackText(text) {
8
+ return text
9
+ .split("\n")
10
+ .map((line) => `> ${line}`)
11
+ .join("\n");
12
+ }
13
+ function splitText(text, maxLength) {
14
+ if (text.length <= maxLength) {
15
+ return [text];
16
+ }
17
+ const chunks = [];
18
+ for (let offset = 0; offset < text.length; offset += maxLength) {
19
+ chunks.push(text.slice(offset, offset + maxLength));
20
+ }
21
+ return chunks;
22
+ }
23
+ function createThinkingPart(header, text, expandable) {
24
+ if (!text) {
25
+ return {
26
+ text: header,
27
+ fallbackText: header,
28
+ source: "entities",
29
+ };
30
+ }
31
+ const renderedText = `${header}\n${text}`;
32
+ const entity = {
33
+ type: expandable ? "expandable_blockquote" : "blockquote",
34
+ offset: header.length + 1,
35
+ length: text.length,
36
+ };
37
+ return {
38
+ text: renderedText,
39
+ entities: [entity],
40
+ fallbackText: `${header}\n${quoteFallbackText(text)}`,
41
+ source: "entities",
42
+ };
43
+ }
44
+ export function prepareThinkingStreamingPayload(sections, maxPartLength, options = {}) {
45
+ const parts = [];
46
+ const expandable = options.expandable ?? true;
47
+ for (const section of sections) {
48
+ const header = formatHeader(section.title);
49
+ const text = section.text.replace(/\r\n/g, "\n").trimEnd();
50
+ const textLimit = Math.max(1, maxPartLength - header.length - 1);
51
+ const chunks = text ? splitText(text, textLimit) : [""];
52
+ for (const chunk of chunks) {
53
+ parts.push(createThinkingPart(header, chunk, expandable));
54
+ }
55
+ }
56
+ return parts.length > 0 ? { parts } : null;
57
+ }
58
+ export function makeThinkingPayloadExpandable(payload) {
59
+ return {
60
+ ...payload,
61
+ parts: payload.parts.map((part) => ({
62
+ ...part,
63
+ entities: part.entities?.map((entity) => entity.type === "blockquote" ? { ...entity, type: "expandable_blockquote" } : entity),
64
+ })),
65
+ };
66
+ }
@@ -1,7 +1,14 @@
1
- import { interactionManager } from "./manager.js";
2
- import { allowsBusyInteraction, isBusyAllowedCommand } from "./busy.js";
3
- import { foregroundSessionState } from "../scheduled-task/foreground-state.js";
4
- import { attachManager } from "../attach/manager.js";
1
+ import { interactionManager } from "../../app/managers/interaction-manager.js";
2
+ import { foregroundSessionState } from "../../app/managers/foreground-session-state-manager.js";
3
+ import { attachManager } from "../../app/managers/attach-manager.js";
4
+ const BUSY_ALLOWED_COMMANDS = ["/abort", "/detach", "/status", "/help"];
5
+ const BUSY_ALLOWED_COMMAND_SET = new Set(BUSY_ALLOWED_COMMANDS);
6
+ function isBusyAllowedCommand(command) {
7
+ return Boolean(command && BUSY_ALLOWED_COMMAND_SET.has(command));
8
+ }
9
+ function allowsBusyInteraction(kind) {
10
+ return kind === "question" || kind === "permission";
11
+ }
5
12
  function normalizeIncomingCommand(text) {
6
13
  const trimmed = text.trim();
7
14
  if (!trimmed.startsWith("/")) {
@@ -1,5 +1,5 @@
1
- import { resolveInteractionGuardDecision } from "../../interaction/guard.js";
2
- import { reconcileForegroundBusyState } from "../utils/busy-guard.js";
1
+ import { resolveInteractionGuardDecision } from "./interaction-guard-decision.js";
2
+ import { reconcileForegroundBusyState } from "../../app/services/run-control-service.js";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { t } from "../../i18n/index.js";
5
5
  function getInteractionBlockedMessage(reason, interactionKind) {
@@ -1,4 +1,4 @@
1
- import { extractCommandName, isKnownCommand } from "../utils/commands.js";
1
+ import { extractCommandName, isKnownCommand } from "../routers/command-utils.js";
2
2
  import { logger } from "../../utils/logger.js";
3
3
  import { t } from "../../i18n/index.js";
4
4
  export async function unknownCommandMiddleware(ctx, next) {
@@ -1,5 +1,4 @@
1
- import { t } from "../i18n/index.js";
2
- export const DEFAULT_CONTEXT_LIMIT = 200000;
1
+ import { t } from "../../i18n/index.js";
3
2
  export function formatTokenCount(count) {
4
3
  if (count >= 1000000) {
5
4
  return `${(count / 1000000).toFixed(1)}M`;
@@ -1,13 +1,13 @@
1
- import { logger } from "../utils/logger.js";
2
- import { opencodeClient } from "../opencode/client.js";
3
- import { getGitWorktreeContext } from "../git/worktree.js";
4
- import { getCurrentSession } from "../session/manager.js";
5
- import { getCurrentProject, getPinnedMessageId, setPinnedMessageId, clearPinnedMessageId, } from "../settings/manager.js";
6
- import { getStoredModel } from "../model/manager.js";
7
- import { getModelContextLimit } from "../model/context-limit.js";
8
- import { isExpectedOpencodeUnavailableError } from "../utils/opencode-error.js";
9
- import { t } from "../i18n/index.js";
10
- import { DEFAULT_CONTEXT_LIMIT, formatContextLine, formatCostLine, formatModelDisplayName, } from "./format.js";
1
+ import { logger } from "../../utils/logger.js";
2
+ import { opencodeClient } from "../../opencode/client.js";
3
+ import { getGitWorktreeContext } from "../../app/services/worktree-service.js";
4
+ import { getCurrentSession } from "../../app/services/session-service.js";
5
+ import { getCurrentProject, getPinnedMessageId, setPinnedMessageId, clearPinnedMessageId, } from "../../app/stores/settings-store.js";
6
+ import { DEFAULT_CONTEXT_LIMIT, getModelContextLimit, } from "../../app/services/model-context-limit-service.js";
7
+ import { getStoredModel } from "../../app/services/model-selection-service.js";
8
+ import { isExpectedOpencodeUnavailableError } from "../../utils/opencode-error.js";
9
+ import { t } from "../../i18n/index.js";
10
+ import { formatContextLine, formatCostLine, formatModelDisplayName, } from "./pinned-message-format.js";
11
11
  class PinnedMessageManager {
12
12
  api = null;
13
13
  chatId = null;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
- import { parseTelegramBlocks } from "../telegram/render/block-parser.js";
2
- import { normalizeMarkdownForTelegramRendering } from "../telegram/render/markdown-normalizer.js";
1
+ import { parseTelegramBlocks } from "./block-parser.js";
2
+ import { normalizeMarkdownForTelegramRendering } from "./markdown-normalizer.js";
3
3
  /**
4
4
  * Escapes characters reserved in Telegram MarkdownV2.
5
5
  * These must be escaped everywhere except when used as syntactic delimiters
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ import { config } from "../../config.js";
2
+ import { settingsCommand } from "../commands/settings-command.js";
3
+ import { opencodeStartCommand } from "../commands/opencode-start-command.js";
4
+ import { opencodeStopCommand } from "../commands/opencode-stop-command.js";
5
+ import { projectsCommand } from "../commands/projects-command.js";
6
+ import { worktreeCommand } from "../commands/worktree-command.js";
7
+ import { openCommand } from "../commands/open-command.js";
8
+ import { lsCommand } from "../commands/ls-command.js";
9
+ import { sessionsCommand } from "../commands/sessions-command.js";
10
+ import { messagesCommand } from "../commands/messages-command.js";
11
+ import { newCommand } from "../commands/new-command.js";
12
+ import { abortCommand } from "../commands/abort-command.js";
13
+ import { detachCommand } from "../commands/detach-command.js";
14
+ import { taskCommand } from "../commands/task-command.js";
15
+ import { taskListCommand } from "../commands/tasklist-command.js";
16
+ import { renameCommand } from "../commands/rename-command.js";
17
+ import { commandsCommand } from "../commands/command-catalog-command.js";
18
+ import { skillsCommand } from "../commands/skills-catalog-command.js";
19
+ import { mcpsCommand } from "../commands/mcp-catalog-command.js";
20
+ import { startCommand } from "../commands/start-command.js";
21
+ import { helpCommand } from "../commands/help-command.js";
22
+ import { statusCommand } from "../commands/status-command.js";
23
+ import { BOT_COMMANDS } from "../commands/definitions.js";
24
+ import { logger } from "../../utils/logger.js";
25
+ let commandsInitialized = false;
26
+ export async function ensureCommandsInitialized(ctx, next) {
27
+ if (commandsInitialized || !ctx.from || ctx.from.id !== config.telegram.allowedUserId) {
28
+ await next();
29
+ return;
30
+ }
31
+ if (!ctx.chat) {
32
+ logger.warn("[Bot] Cannot initialize commands: chat context is missing");
33
+ await next();
34
+ return;
35
+ }
36
+ try {
37
+ await ctx.api.setMyCommands(BOT_COMMANDS, {
38
+ scope: {
39
+ type: "chat",
40
+ chat_id: ctx.chat.id,
41
+ },
42
+ });
43
+ commandsInitialized = true;
44
+ logger.debug(`[Bot] Commands initialized for authorized user (chat_id=${ctx.chat.id})`);
45
+ }
46
+ catch (err) {
47
+ logger.error("[Bot] Failed to set commands:", err);
48
+ }
49
+ await next();
50
+ }
51
+ export function registerCommandRouter(bot, deps) {
52
+ bot.command("start", startCommand);
53
+ bot.command("help", helpCommand);
54
+ bot.command("status", statusCommand);
55
+ bot.command("settings", settingsCommand);
56
+ bot.command("opencode_start", opencodeStartCommand);
57
+ bot.command("opencode_stop", opencodeStopCommand);
58
+ bot.command("projects", projectsCommand);
59
+ bot.command("worktree", worktreeCommand);
60
+ bot.command("open", openCommand);
61
+ bot.command("ls", lsCommand);
62
+ bot.command("sessions", sessionsCommand);
63
+ bot.command("messages", messagesCommand);
64
+ bot.command("new", (ctx) => newCommand(ctx, { bot, ensureEventSubscription: deps.ensureEventSubscription }));
65
+ bot.command("abort", abortCommand);
66
+ bot.command("detach", detachCommand);
67
+ bot.command("task", taskCommand);
68
+ bot.command("tasklist", taskListCommand);
69
+ bot.command("rename", renameCommand);
70
+ bot.command("commands", commandsCommand);
71
+ bot.command("skills", skillsCommand);
72
+ bot.command("mcps", mcpsCommand);
73
+ }
@@ -0,0 +1,150 @@
1
+ import { interactionManager } from "../../app/managers/interaction-manager.js";
2
+ import { questionManager } from "../../app/managers/question-manager.js";
3
+ import { t } from "../../i18n/index.js";
4
+ import { logger } from "../../utils/logger.js";
5
+ import { handleTaskTextInput } from "../commands/task-command.js";
6
+ import { handleModelSearchTextInput, } from "../callbacks/model-selection-callback-handler.js";
7
+ import { handleQuestionTextAnswer } from "../callbacks/question-callback-handler.js";
8
+ import { handleRenameTextAnswer } from "../callbacks/rename-callback-handler.js";
9
+ import { handleContextButtonPress } from "../menus/context-control-menu.js";
10
+ import { showAgentSelectionMenu } from "../menus/agent-selection-menu.js";
11
+ import { showModelSelectionMenu } from "../menus/model-selection-menu.js";
12
+ import { showVariantSelectionMenu } from "../menus/variant-selection-menu.js";
13
+ import { AGENT_MODE_BUTTON_TEXT_PATTERN, MODEL_BUTTON_TEXT_PATTERN, VARIANT_BUTTON_TEXT_PATTERN, } from "../message-patterns.js";
14
+ import { handleDocumentMessage } from "../handlers/document-handler.js";
15
+ import { createMediaGroupAttachmentMiddleware } from "../handlers/media-group-handler.js";
16
+ import { handlePhotoMessage } from "../handlers/photo-handler.js";
17
+ import { processUserPrompt } from "../handlers/prompt.js";
18
+ import { handleCatalogTextArguments } from "../handlers/text-message-handler.js";
19
+ import { handleVoiceMessage } from "../handlers/voice-handler.js";
20
+ import { unknownCommandMiddleware } from "../middleware/unknown-command.js";
21
+ async function blockMenuWhileInteractionActive(ctx) {
22
+ const activeInteraction = interactionManager.getSnapshot();
23
+ if (!activeInteraction) {
24
+ return false;
25
+ }
26
+ logger.debug(`[Bot] Blocking menu open while interaction active: kind=${activeInteraction.kind}, expectedInput=${activeInteraction.expectedInput}`);
27
+ await ctx.reply(t("interaction.blocked.finish_current"));
28
+ return true;
29
+ }
30
+ export function registerMessageRouter(bot, deps) {
31
+ bot.on("message:text", unknownCommandMiddleware);
32
+ bot.hears(AGENT_MODE_BUTTON_TEXT_PATTERN, async (ctx) => {
33
+ logger.debug(`[Bot] Agent button pressed: ${ctx.message?.text}`);
34
+ try {
35
+ if (await blockMenuWhileInteractionActive(ctx)) {
36
+ return;
37
+ }
38
+ await showAgentSelectionMenu(ctx);
39
+ }
40
+ catch (err) {
41
+ logger.error("[Bot] Error showing agent menu:", err);
42
+ await ctx.reply(t("error.load_agents"));
43
+ }
44
+ });
45
+ bot.hears(MODEL_BUTTON_TEXT_PATTERN, async (ctx) => {
46
+ logger.debug(`[Bot] Model button pressed: ${ctx.message?.text}`);
47
+ try {
48
+ if (await blockMenuWhileInteractionActive(ctx)) {
49
+ return;
50
+ }
51
+ await showModelSelectionMenu(ctx);
52
+ }
53
+ catch (err) {
54
+ logger.error("[Bot] Error showing model menu:", err);
55
+ await ctx.reply(t("error.load_models"));
56
+ }
57
+ });
58
+ bot.hears(/^📊(?:\s|$)/, async (ctx) => {
59
+ logger.debug(`[Bot] Context button pressed: ${ctx.message?.text}`);
60
+ try {
61
+ if (await blockMenuWhileInteractionActive(ctx)) {
62
+ return;
63
+ }
64
+ await handleContextButtonPress(ctx);
65
+ }
66
+ catch (err) {
67
+ logger.error("[Bot] Error handling context button:", err);
68
+ await ctx.reply(t("error.context_button"));
69
+ }
70
+ });
71
+ bot.hears(VARIANT_BUTTON_TEXT_PATTERN, async (ctx) => {
72
+ logger.debug(`[Bot] Variant button pressed: ${ctx.message?.text}`);
73
+ try {
74
+ if (await blockMenuWhileInteractionActive(ctx)) {
75
+ return;
76
+ }
77
+ await showVariantSelectionMenu(ctx);
78
+ }
79
+ catch (err) {
80
+ logger.error("[Bot] Error showing variant menu:", err);
81
+ await ctx.reply(t("error.load_variants"));
82
+ }
83
+ });
84
+ bot.on("message:text", async (ctx, next) => {
85
+ const text = ctx.message?.text;
86
+ if (text) {
87
+ const isCommand = text.startsWith("/");
88
+ logger.debug(`[Bot] Received text message: ${isCommand ? `command="${text}"` : `prompt (length=${text.length})`}, chatId=${ctx.chat.id}`);
89
+ }
90
+ await next();
91
+ });
92
+ const voicePromptDeps = { bot, ensureEventSubscription: deps.ensureEventSubscription };
93
+ bot.on("message:voice", async (ctx) => {
94
+ logger.debug(`[Bot] Received voice message, chatId=${ctx.chat.id}`);
95
+ deps.setTelegramContext(bot, ctx.chat.id);
96
+ await handleVoiceMessage(ctx, voicePromptDeps);
97
+ });
98
+ bot.on("message:audio", async (ctx) => {
99
+ logger.debug(`[Bot] Received audio message, chatId=${ctx.chat.id}`);
100
+ deps.setTelegramContext(bot, ctx.chat.id);
101
+ await handleVoiceMessage(ctx, voicePromptDeps);
102
+ });
103
+ bot.on("message", createMediaGroupAttachmentMiddleware({
104
+ bot,
105
+ ensureEventSubscription: deps.ensureEventSubscription,
106
+ }));
107
+ bot.on("message:photo", async (ctx) => {
108
+ logger.debug(`[Bot] Received photo message, chatId=${ctx.chat.id}`);
109
+ deps.setTelegramContext(bot, ctx.chat.id);
110
+ await handlePhotoMessage(ctx, { bot, ensureEventSubscription: deps.ensureEventSubscription });
111
+ });
112
+ bot.on("message:document", async (ctx) => {
113
+ logger.debug(`[Bot] Received document message, chatId=${ctx.chat.id}`);
114
+ deps.setTelegramContext(bot, ctx.chat.id);
115
+ await handleDocumentMessage(ctx, { bot, ensureEventSubscription: deps.ensureEventSubscription });
116
+ });
117
+ bot.on("message:text", async (ctx) => {
118
+ const text = ctx.message?.text;
119
+ if (!text) {
120
+ return;
121
+ }
122
+ deps.setTelegramContext(bot, ctx.chat.id);
123
+ if (text.startsWith("/")) {
124
+ return;
125
+ }
126
+ if (questionManager.isActive()) {
127
+ await handleQuestionTextAnswer(ctx);
128
+ return;
129
+ }
130
+ const handledTask = await handleTaskTextInput(ctx);
131
+ if (handledTask) {
132
+ return;
133
+ }
134
+ const handledModelSearchText = await handleModelSearchTextInput(ctx);
135
+ if (handledModelSearchText) {
136
+ return;
137
+ }
138
+ const handledRename = await handleRenameTextAnswer(ctx);
139
+ if (handledRename) {
140
+ return;
141
+ }
142
+ const promptDeps = { bot, ensureEventSubscription: deps.ensureEventSubscription };
143
+ const handledCatalogTextArgs = await handleCatalogTextArguments(ctx, promptDeps);
144
+ if (handledCatalogTextArgs) {
145
+ return;
146
+ }
147
+ await processUserPrompt(ctx, text, promptDeps);
148
+ logger.debug("[Bot] message:text handler completed (prompt sent in background)");
149
+ });
150
+ }
@@ -0,0 +1,40 @@
1
+ import { keyboardManager } from "../keyboards/keyboard-manager.js";
2
+ import { showPermissionRequest } from "../menus/permission-menu.js";
3
+ import { showCurrentQuestion } from "../menus/question-menu.js";
4
+ import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
5
+ export function createAttachPresentation() {
6
+ return {
7
+ async ensurePinnedSession({ api, chatId, session, forceFullRestore = false }) {
8
+ if (!pinnedMessageManager.isInitialized()) {
9
+ pinnedMessageManager.initialize(api, chatId);
10
+ }
11
+ keyboardManager.initialize(api, chatId);
12
+ const pinnedState = pinnedMessageManager.getState();
13
+ if (pinnedState.sessionId === session.id && pinnedState.messageId) {
14
+ if (forceFullRestore) {
15
+ await pinnedMessageManager.loadContextFromHistory(session.id, session.directory);
16
+ }
17
+ return;
18
+ }
19
+ if (pinnedState.messageId && pinnedState.sessionId === null) {
20
+ await pinnedMessageManager.restoreExistingSession(session.id, session.title);
21
+ }
22
+ else {
23
+ await pinnedMessageManager.onSessionChange(session.id, session.title);
24
+ }
25
+ await pinnedMessageManager.loadContextFromHistory(session.id, session.directory);
26
+ const contextInfo = pinnedMessageManager.getContextInfo();
27
+ if (contextInfo) {
28
+ keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
29
+ }
30
+ },
31
+ async syncAttachState(attached, busy) {
32
+ if (!pinnedMessageManager.isInitialized()) {
33
+ return;
34
+ }
35
+ await pinnedMessageManager.setAttachState(attached, busy);
36
+ },
37
+ showCurrentQuestion,
38
+ showPermissionRequest,
39
+ };
40
+ }