@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,15 +1,15 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
-
import { getGitWorktreeContext } from "../../
|
|
3
|
-
import { getCurrentSession } from "../../session
|
|
4
|
-
import { getCurrentProject,
|
|
5
|
-
import { fetchCurrentAgent } from "../../agent
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { keyboardManager } from "
|
|
9
|
-
import { pinnedMessageManager } from "
|
|
2
|
+
import { getGitWorktreeContext } from "../../app/services/worktree-service.js";
|
|
3
|
+
import { getCurrentSession } from "../../app/services/session-service.js";
|
|
4
|
+
import { getCurrentProject, getTtsMode } from "../../app/stores/settings-store.js";
|
|
5
|
+
import { fetchCurrentAgent } from "../../app/services/agent-selection-service.js";
|
|
6
|
+
import { fetchCurrentModel } from "../../app/services/model-selection-service.js";
|
|
7
|
+
import { getAgentDisplayName } from "../../app/types/agent.js";
|
|
8
|
+
import { keyboardManager } from "../keyboards/keyboard-manager.js";
|
|
9
|
+
import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
11
|
import { t } from "../../i18n/index.js";
|
|
12
|
-
import { sendBotText } from "../
|
|
12
|
+
import { sendBotText } from "../messages/telegram-text.js";
|
|
13
13
|
export async function statusCommand(ctx) {
|
|
14
14
|
try {
|
|
15
15
|
const { data, error } = await opencodeClient.global.health();
|
|
@@ -22,8 +22,13 @@ export async function statusCommand(ctx) {
|
|
|
22
22
|
if (data.version) {
|
|
23
23
|
message += `${t("status.line.version", { version: data.version })}\n`;
|
|
24
24
|
}
|
|
25
|
+
const ttsMode = getTtsMode();
|
|
25
26
|
message += `${t("status.line.tts", {
|
|
26
|
-
tts:
|
|
27
|
+
tts: ttsMode === "off"
|
|
28
|
+
? t("status.tts.off")
|
|
29
|
+
: ttsMode === "all"
|
|
30
|
+
? t("status.tts.all")
|
|
31
|
+
: t("status.tts.auto"),
|
|
27
32
|
})}\n`;
|
|
28
33
|
// Add agent information
|
|
29
34
|
const currentAgent = await fetchCurrentAgent();
|
|
@@ -1,35 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { InlineKeyboard } from "grammy";
|
|
3
2
|
import { config } from "../../config.js";
|
|
4
3
|
import { getDateLocale, t } from "../../i18n/index.js";
|
|
5
|
-
import { interactionManager } from "../../interaction
|
|
6
|
-
import { getStoredModel } from "../../model
|
|
7
|
-
import { getCurrentProject } from "../../settings
|
|
8
|
-
import { taskCreationManager } from "../../scheduled-task
|
|
9
|
-
import { parseTaskSchedule } from "../../scheduled-task
|
|
10
|
-
import { addScheduledTask, listScheduledTasks } from "../../scheduled-task
|
|
11
|
-
import { scheduledTaskRuntime } from "../../scheduled-task
|
|
12
|
-
import {
|
|
4
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
5
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
6
|
+
import { getCurrentProject } from "../../app/stores/settings-store.js";
|
|
7
|
+
import { taskCreationManager } from "../../app/managers/scheduled-task-creation-manager.js";
|
|
8
|
+
import { parseTaskSchedule } from "../../app/services/scheduled-task-schedule-parser-service.js";
|
|
9
|
+
import { addScheduledTask, listScheduledTasks } from "../../app/stores/scheduled-task-store.js";
|
|
10
|
+
import { scheduledTaskRuntime } from "../../app/services/scheduled-task-runtime-service.js";
|
|
11
|
+
import { buildCancelKeyboard, buildRetryScheduleKeyboard } from "../menus/scheduled-task-menu.js";
|
|
12
|
+
import { createScheduledTaskModel, } from "../../app/types/scheduled-task.js";
|
|
13
13
|
import { logger } from "../../utils/logger.js";
|
|
14
|
-
const TASK_RETRY_SCHEDULE_CALLBACK = "task:retry-schedule";
|
|
15
|
-
const TASK_CANCEL_CALLBACK = "task:cancel";
|
|
16
14
|
const TASK_PROMPT_PREVIEW_LENGTH = 100;
|
|
17
|
-
function buildRetryScheduleKeyboard() {
|
|
18
|
-
return new InlineKeyboard()
|
|
19
|
-
.text(t("task.button.retry_schedule"), TASK_RETRY_SCHEDULE_CALLBACK)
|
|
20
|
-
.text(t("task.button.cancel"), TASK_CANCEL_CALLBACK);
|
|
21
|
-
}
|
|
22
|
-
function buildCancelKeyboard() {
|
|
23
|
-
return new InlineKeyboard().text(t("task.button.cancel"), TASK_CANCEL_CALLBACK);
|
|
24
|
-
}
|
|
25
|
-
function getCallbackMessageId(ctx) {
|
|
26
|
-
const message = ctx.callbackQuery?.message;
|
|
27
|
-
if (!message || !("message_id" in message)) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
const messageId = message.message_id;
|
|
31
|
-
return typeof messageId === "number" ? messageId : null;
|
|
32
|
-
}
|
|
33
15
|
function clearTaskInteraction(reason) {
|
|
34
16
|
const state = interactionManager.getSnapshot();
|
|
35
17
|
if (state?.kind === "task") {
|
|
@@ -179,13 +161,6 @@ function buildTaskInteractionMetadata(stage, projectId, projectWorktree, preview
|
|
|
179
161
|
function isTaskInteraction(state) {
|
|
180
162
|
return state?.kind === "task";
|
|
181
163
|
}
|
|
182
|
-
function isTaskCallbackActive(flowState, messageId) {
|
|
183
|
-
return [
|
|
184
|
-
flowState.scheduleRequestMessageId,
|
|
185
|
-
flowState.previewMessageId,
|
|
186
|
-
flowState.promptRequestMessageId,
|
|
187
|
-
].includes(messageId);
|
|
188
|
-
}
|
|
189
164
|
async function deleteMessageIfPresent(ctx, messageId) {
|
|
190
165
|
if (!ctx.chat || typeof messageId !== "number") {
|
|
191
166
|
return;
|
|
@@ -244,53 +219,6 @@ export async function taskCommand(ctx) {
|
|
|
244
219
|
});
|
|
245
220
|
taskCreationManager.setScheduleRequestMessageId(message.message_id);
|
|
246
221
|
}
|
|
247
|
-
export async function handleTaskCallback(ctx) {
|
|
248
|
-
const data = ctx.callbackQuery?.data;
|
|
249
|
-
if (data !== TASK_RETRY_SCHEDULE_CALLBACK && data !== TASK_CANCEL_CALLBACK) {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
const flowState = taskCreationManager.getState();
|
|
253
|
-
const interactionState = interactionManager.getSnapshot();
|
|
254
|
-
const callbackMessageId = getCallbackMessageId(ctx);
|
|
255
|
-
if (!flowState ||
|
|
256
|
-
!isTaskInteraction(interactionState) ||
|
|
257
|
-
callbackMessageId === null ||
|
|
258
|
-
!isTaskCallbackActive(flowState, callbackMessageId)) {
|
|
259
|
-
if (!flowState && isTaskInteraction(interactionState)) {
|
|
260
|
-
clearTaskInteraction("task_retry_inactive_state");
|
|
261
|
-
}
|
|
262
|
-
await ctx.answerCallbackQuery({ text: t("task.inactive_callback"), show_alert: true });
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
if (data === TASK_CANCEL_CALLBACK) {
|
|
266
|
-
await ctx.answerCallbackQuery({ text: t("task.cancel_callback") });
|
|
267
|
-
await deleteMessageIfPresent(ctx, flowState.scheduleRequestMessageId);
|
|
268
|
-
await deleteMessageIfPresent(ctx, flowState.previewMessageId);
|
|
269
|
-
await deleteMessageIfPresent(ctx, flowState.promptRequestMessageId);
|
|
270
|
-
clearTaskFlow("task_cancelled");
|
|
271
|
-
await ctx.reply(t("task.cancelled"));
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
if (!taskCreationManager.isWaitingForPrompt() ||
|
|
275
|
-
callbackMessageId !== flowState.previewMessageId) {
|
|
276
|
-
await ctx.answerCallbackQuery({ text: t("task.inactive_callback"), show_alert: true });
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
taskCreationManager.resetSchedule();
|
|
280
|
-
interactionManager.transition({
|
|
281
|
-
kind: "task",
|
|
282
|
-
expectedInput: "text",
|
|
283
|
-
metadata: buildTaskInteractionMetadata("awaiting_schedule", flowState.projectId, flowState.projectWorktree),
|
|
284
|
-
});
|
|
285
|
-
await ctx.answerCallbackQuery({ text: t("task.retry_schedule_callback") });
|
|
286
|
-
await deleteMessageIfPresent(ctx, flowState.promptRequestMessageId);
|
|
287
|
-
await deleteMessageIfPresent(ctx, flowState.previewMessageId);
|
|
288
|
-
const message = await ctx.reply(t("task.prompt.schedule"), {
|
|
289
|
-
reply_markup: buildCancelKeyboard(),
|
|
290
|
-
});
|
|
291
|
-
taskCreationManager.setScheduleRequestMessageId(message.message_id);
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
222
|
export async function handleTaskTextInput(ctx) {
|
|
295
223
|
const text = ctx.message?.text;
|
|
296
224
|
if (!text || text.startsWith("/")) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
2
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
3
|
+
import { listScheduledTasks } from "../../app/stores/scheduled-task-store.js";
|
|
4
|
+
import { buildTaskListKeyboard } from "../menus/scheduled-task-menu.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
6
|
+
function sortTasks(tasks) {
|
|
7
|
+
return [...tasks].sort((left, right) => {
|
|
8
|
+
const leftNextRun = left.nextRunAt ? Date.parse(left.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
9
|
+
const rightNextRun = right.nextRunAt ? Date.parse(right.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
10
|
+
if (leftNextRun !== rightNextRun) {
|
|
11
|
+
return leftNextRun - rightNextRun;
|
|
12
|
+
}
|
|
13
|
+
return left.createdAt.localeCompare(right.createdAt);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export async function taskListCommand(ctx) {
|
|
17
|
+
try {
|
|
18
|
+
const tasks = sortTasks(listScheduledTasks());
|
|
19
|
+
if (tasks.length === 0) {
|
|
20
|
+
await ctx.reply(t("tasklist.empty"));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const message = await ctx.reply(t("tasklist.select"), {
|
|
24
|
+
reply_markup: buildTaskListKeyboard(tasks),
|
|
25
|
+
});
|
|
26
|
+
interactionManager.start({
|
|
27
|
+
kind: "custom",
|
|
28
|
+
expectedInput: "callback",
|
|
29
|
+
metadata: {
|
|
30
|
+
flow: "tasklist",
|
|
31
|
+
stage: "list",
|
|
32
|
+
messageId: message.message_id,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger.error("[TaskList] Failed to open task list", error);
|
|
38
|
+
await ctx.reply(t("tasklist.load_error"));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getGitWorktreeContext } from "../../app/services/worktree-service.js";
|
|
2
|
+
import { isForegroundBusy } from "../../app/services/run-control-service.js";
|
|
3
|
+
import { getCurrentProject } from "../../app/stores/settings-store.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
6
|
+
import { replyWithInlineMenu } from "../menus/inline-menu.js";
|
|
7
|
+
import { buildWorktreeMenuView } from "../menus/worktree-selection-menu.js";
|
|
8
|
+
import { replyBusyBlocked } from "../messages/busy-blocked-renderer.js";
|
|
9
|
+
async function loadCurrentWorktreeContext() {
|
|
10
|
+
const currentProject = getCurrentProject();
|
|
11
|
+
if (!currentProject) {
|
|
12
|
+
return { currentProject: null, context: null };
|
|
13
|
+
}
|
|
14
|
+
const context = await getGitWorktreeContext(currentProject.worktree);
|
|
15
|
+
return { currentProject, context };
|
|
16
|
+
}
|
|
17
|
+
export async function worktreeCommand(ctx) {
|
|
18
|
+
try {
|
|
19
|
+
if (isForegroundBusy()) {
|
|
20
|
+
await replyBusyBlocked(ctx);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { currentProject, context } = await loadCurrentWorktreeContext();
|
|
24
|
+
if (!currentProject) {
|
|
25
|
+
await ctx.reply(t("worktree.project_not_selected"));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!context) {
|
|
29
|
+
await ctx.reply(t("worktree.not_git_repo"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (context.worktrees.length === 0) {
|
|
33
|
+
await ctx.reply(t("worktree.empty"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { text, keyboard } = buildWorktreeMenuView(context.worktrees, 0);
|
|
37
|
+
await replyWithInlineMenu(ctx, {
|
|
38
|
+
menuKind: "worktree",
|
|
39
|
+
text,
|
|
40
|
+
keyboard,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.error("[Bot] Error loading worktrees:", error);
|
|
45
|
+
await ctx.reply(t("worktree.fetch_error"));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { config } from "../../config.js";
|
|
2
2
|
import { processUserPrompt } from "./prompt.js";
|
|
3
|
-
import { downloadTelegramFile, toDataUri, isTextMimeType, isFileSizeAllowed, } from "
|
|
4
|
-
import { getModelCapabilities, supportsInput } from "../../model
|
|
5
|
-
import { getStoredModel } from "../../model
|
|
3
|
+
import { downloadTelegramFile, toDataUri, isTextMimeType, isFileSizeAllowed, } from "../../app/services/file-download-service.js";
|
|
4
|
+
import { getModelCapabilities, supportsInput } from "../../app/services/model-capabilities-service.js";
|
|
5
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
6
6
|
import { logger } from "../../utils/logger.js";
|
|
7
7
|
import { t } from "../../i18n/index.js";
|
|
8
8
|
export async function handleDocumentMessage(ctx, deps) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { config } from "../../config.js";
|
|
2
2
|
import { t } from "../../i18n/index.js";
|
|
3
|
-
import { getModelCapabilities, supportsInput } from "../../model
|
|
4
|
-
import { getStoredModel } from "../../model
|
|
3
|
+
import { getModelCapabilities, supportsInput } from "../../app/services/model-capabilities-service.js";
|
|
4
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
5
5
|
import { logger } from "../../utils/logger.js";
|
|
6
|
-
import { downloadTelegramFile, isFileSizeAllowed, isTextMimeType, toDataUri, } from "
|
|
6
|
+
import { downloadTelegramFile, isFileSizeAllowed, isTextMimeType, toDataUri, } from "../../app/services/file-download-service.js";
|
|
7
7
|
import { processUserPrompt } from "./prompt.js";
|
|
8
8
|
const DEFAULT_MEDIA_GROUP_DEBOUNCE_MS = 1_000;
|
|
9
9
|
export class MediaGroupAttachmentHandler {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { downloadTelegramFile, toDataUri } from "../../app/services/file-download-service.js";
|
|
2
|
+
import { getModelCapabilities, supportsInput } from "../../app/services/model-capabilities-service.js";
|
|
3
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
6
|
+
import { processUserPrompt } from "./prompt.js";
|
|
7
|
+
export async function handlePhotoMessage(ctx, deps) {
|
|
8
|
+
const photos = ctx.message?.photo;
|
|
9
|
+
if (!photos || photos.length === 0) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const caption = ctx.message.caption || "";
|
|
13
|
+
const largestPhoto = photos[photos.length - 1];
|
|
14
|
+
const downloadFile = deps.downloadFile ?? downloadTelegramFile;
|
|
15
|
+
const getCapabilities = deps.getModelCapabilities ?? getModelCapabilities;
|
|
16
|
+
const getStored = deps.getStoredModel ?? getStoredModel;
|
|
17
|
+
const processPrompt = deps.processPrompt ?? processUserPrompt;
|
|
18
|
+
try {
|
|
19
|
+
const storedModel = getStored();
|
|
20
|
+
const capabilities = await getCapabilities(storedModel.providerID, storedModel.modelID);
|
|
21
|
+
if (!supportsInput(capabilities, "image")) {
|
|
22
|
+
logger.warn(`[Bot] Model ${storedModel.providerID}/${storedModel.modelID} doesn't support image input`);
|
|
23
|
+
await ctx.reply(t("bot.photo_model_no_image"));
|
|
24
|
+
if (caption.trim().length > 0) {
|
|
25
|
+
await processPrompt(ctx, caption, deps);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await ctx.reply(t("bot.photo_downloading"));
|
|
30
|
+
const downloadedFile = await downloadFile(ctx.api, largestPhoto.file_id);
|
|
31
|
+
const filePart = {
|
|
32
|
+
type: "file",
|
|
33
|
+
mime: "image/jpeg",
|
|
34
|
+
filename: "photo.jpg",
|
|
35
|
+
url: toDataUri(downloadedFile.buffer, "image/jpeg"),
|
|
36
|
+
};
|
|
37
|
+
logger.info(`[Bot] Sending photo (${downloadedFile.buffer.length} bytes) with prompt`);
|
|
38
|
+
await processPrompt(ctx, caption, deps, [filePart]);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger.error("[Bot] Error handling photo message:", err);
|
|
42
|
+
await ctx.reply(t("bot.photo_download_error"));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
-
import { clearSession, getCurrentSession, setCurrentSession } from "../../session
|
|
3
|
-
import { ingestSessionInfoForCache } from "../../session
|
|
4
|
-
import { getCurrentProject,
|
|
5
|
-
import { getStoredAgent, resolveProjectAgent } from "../../agent
|
|
6
|
-
import { getStoredModel } from "../../model
|
|
7
|
-
import { formatVariantForButton } from "../../variant
|
|
8
|
-
import { createMainKeyboard } from "../
|
|
9
|
-
import { keyboardManager } from "
|
|
10
|
-
import { pinnedMessageManager } from "
|
|
11
|
-
import { summaryAggregator } from "../../summary
|
|
2
|
+
import { clearSession, getCurrentSession, setCurrentSession, } from "../../app/services/session-service.js";
|
|
3
|
+
import { ingestSessionInfoForCache } from "../../app/services/session-cache-service.js";
|
|
4
|
+
import { getCurrentProject, getTtsMode } from "../../app/stores/settings-store.js";
|
|
5
|
+
import { getStoredAgent, resolveProjectAgent } from "../../app/services/agent-selection-service.js";
|
|
6
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
7
|
+
import { formatVariantForButton } from "../../app/services/variant-selection-service.js";
|
|
8
|
+
import { createMainKeyboard } from "../keyboards/main-reply-keyboard.js";
|
|
9
|
+
import { keyboardManager } from "../keyboards/keyboard-manager.js";
|
|
10
|
+
import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
|
|
11
|
+
import { summaryAggregator } from "../../app/managers/summary-aggregation-manager.js";
|
|
12
12
|
import { stopEventListening } from "../../opencode/events.js";
|
|
13
|
-
import { interactionManager } from "../../interaction
|
|
14
|
-
import { clearAllInteractionState } from "../../interaction
|
|
13
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
14
|
+
import { clearAllInteractionState } from "../../app/managers/interaction-manager.js";
|
|
15
15
|
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
16
16
|
import { formatErrorDetails } from "../../utils/error-format.js";
|
|
17
17
|
import { logger } from "../../utils/logger.js";
|
|
18
18
|
import { t } from "../../i18n/index.js";
|
|
19
|
-
import { foregroundSessionState } from "../../
|
|
20
|
-
import { assistantRunState } from "
|
|
21
|
-
import { attachToSession, detachAttachedSession, markAttachedSessionBusy, markAttachedSessionIdle, } from "../../attach
|
|
22
|
-
import { externalUserInputSuppressionManager } from "../../external-input
|
|
19
|
+
import { foregroundSessionState } from "../../app/managers/foreground-session-state-manager.js";
|
|
20
|
+
import { assistantRunState } from "../../app/managers/assistant-run-state-manager.js";
|
|
21
|
+
import { attachToSession, detachAttachedSession, markAttachedSessionBusy, markAttachedSessionIdle, } from "../../app/services/attach-service.js";
|
|
22
|
+
import { externalUserInputSuppressionManager } from "../../app/managers/external-input-suppression-manager.js";
|
|
23
23
|
/** Module-level references for async callbacks that don't have ctx. */
|
|
24
24
|
let botInstance = null;
|
|
25
25
|
let chatIdInstance = null;
|
|
@@ -91,7 +91,7 @@ async function resetMismatchedSessionContext() {
|
|
|
91
91
|
*/
|
|
92
92
|
export async function processUserPrompt(ctx, text, deps, fileParts = [], options = {}) {
|
|
93
93
|
const { bot, ensureEventSubscription } = deps;
|
|
94
|
-
const responseMode = options.responseMode ?? (
|
|
94
|
+
const responseMode = options.responseMode ?? (getTtsMode() === "all" ? "text_and_tts" : "text_only");
|
|
95
95
|
const currentProject = getCurrentProject();
|
|
96
96
|
if (!currentProject) {
|
|
97
97
|
await ctx.reply(t("bot.project_not_selected"));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
2
|
+
import { t } from "../../i18n/index.js";
|
|
3
|
+
import { clearCommandsInteraction, executeCommand, parseCommandsMetadata, } from "../callbacks/command-catalog-callback-handler.js";
|
|
4
|
+
import { clearSkillsInteraction, executeSkill, parseSkillsMetadata, } from "../callbacks/skills-catalog-callback-handler.js";
|
|
5
|
+
export async function handleCommandTextArguments(ctx, deps) {
|
|
6
|
+
const text = ctx.message?.text;
|
|
7
|
+
if (!text || text.startsWith("/")) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
11
|
+
if (!metadata || metadata.stage !== "confirm") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const argumentsText = text.trim();
|
|
15
|
+
if (!argumentsText) {
|
|
16
|
+
await ctx.reply(t("commands.arguments_empty"));
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
clearCommandsInteraction("commands_arguments_submitted");
|
|
20
|
+
if (ctx.chat) {
|
|
21
|
+
await ctx.api.deleteMessage(ctx.chat.id, metadata.messageId).catch(() => { });
|
|
22
|
+
}
|
|
23
|
+
await executeCommand(ctx, deps, {
|
|
24
|
+
projectDirectory: metadata.projectDirectory,
|
|
25
|
+
commandName: metadata.commandName,
|
|
26
|
+
argumentsText,
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
export async function handleSkillTextArguments(ctx, deps) {
|
|
31
|
+
const text = ctx.message?.text;
|
|
32
|
+
if (!text || text.startsWith("/")) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const metadata = parseSkillsMetadata(interactionManager.getSnapshot());
|
|
36
|
+
if (!metadata || metadata.stage !== "confirm") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const argumentsText = text.trim();
|
|
40
|
+
if (!argumentsText) {
|
|
41
|
+
await ctx.reply(t("skills.arguments_empty"));
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
clearSkillsInteraction("skills_arguments_submitted");
|
|
45
|
+
if (ctx.chat) {
|
|
46
|
+
await ctx.api.deleteMessage(ctx.chat.id, metadata.messageId).catch(() => { });
|
|
47
|
+
}
|
|
48
|
+
await executeSkill(ctx, deps, {
|
|
49
|
+
projectDirectory: metadata.projectDirectory,
|
|
50
|
+
skillName: metadata.skillName,
|
|
51
|
+
argumentsText,
|
|
52
|
+
});
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
export async function handleCatalogTextArguments(ctx, deps) {
|
|
56
|
+
const handledCommandArgs = await handleCommandTextArguments(ctx, deps);
|
|
57
|
+
if (handledCommandArgs) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return handleSkillTextArguments(ctx, deps);
|
|
61
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { InputFile } from "grammy";
|
|
2
|
+
import { prepareTtsResponseForSession } from "../../app/services/tts-service.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
import { consumePromptResponseMode } from "./prompt.js";
|
|
6
|
+
export async function sendTtsResponseForSession({ api, sessionId, chatId, text, consumeResponseMode: consumeResponseModeImpl = consumePromptResponseMode, isTtsConfigured, synthesizeSpeech, }) {
|
|
7
|
+
try {
|
|
8
|
+
const prepared = await prepareTtsResponseForSession({
|
|
9
|
+
sessionId,
|
|
10
|
+
text,
|
|
11
|
+
consumeResponseMode: consumeResponseModeImpl,
|
|
12
|
+
isTtsConfigured,
|
|
13
|
+
synthesizeSpeech,
|
|
14
|
+
});
|
|
15
|
+
if (!prepared.shouldSend) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
await api.sendAudio(chatId, new InputFile(prepared.speech.buffer, prepared.speech.filename));
|
|
19
|
+
logger.info(`[TTS] Sent audio reply for session ${sessionId}`);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
logger.warn(`[TTS] Failed to send audio reply for session ${sessionId}`, error);
|
|
24
|
+
await api.sendMessage(chatId, t("tts.failed")).catch((sendError) => {
|
|
25
|
+
logger.warn(`[TTS] Failed to send audio error message for session ${sessionId}`, sendError);
|
|
26
|
+
});
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -4,11 +4,12 @@ import { URL } from "node:url";
|
|
|
4
4
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
5
5
|
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
6
6
|
import { config } from "../../config.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getTtsMode } from "../../app/stores/settings-store.js";
|
|
8
|
+
import { isSttConfigured, transcribeAudio, } from "../../app/services/stt-service.js";
|
|
8
9
|
import { processUserPrompt } from "./prompt.js";
|
|
9
10
|
import { logger } from "../../utils/logger.js";
|
|
10
11
|
import { t } from "../../i18n/index.js";
|
|
11
|
-
import { buildTelegramFileUrl } from "
|
|
12
|
+
import { buildTelegramFileUrl } from "../../app/services/file-download-service.js";
|
|
12
13
|
const TELEGRAM_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
13
14
|
const TELEGRAM_DOWNLOAD_MAX_REDIRECTS = 3;
|
|
14
15
|
let telegramDownloadAgent;
|
|
@@ -169,7 +170,9 @@ export async function handleVoiceMessage(ctx, deps) {
|
|
|
169
170
|
textForLLM = `${llmNote}\n${recognizedText}`;
|
|
170
171
|
}
|
|
171
172
|
// Process the recognized text as a prompt
|
|
172
|
-
|
|
173
|
+
const currentTtsMode = getTtsMode();
|
|
174
|
+
const responseMode = currentTtsMode === "all" || currentTtsMode === "auto" ? "text_and_tts" : "text_only";
|
|
175
|
+
await processPrompt(ctx, textForLLM, deps, [], { responseMode });
|
|
173
176
|
}
|
|
174
177
|
catch (err) {
|
|
175
178
|
const errorMessage = err instanceof Error ? err.message : "unknown error";
|