@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,40 +1,16 @@
|
|
|
1
|
-
import { config } from "
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { computeNextRunAt, isTaskDue } from "./next-run.js";
|
|
12
|
-
import { getScheduledTask, listScheduledTasks, removeScheduledTask, replaceScheduledTasks, updateScheduledTask, } from "./store.js";
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { t } from "../../i18n/index.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
5
|
+
import { formatAssistantRunFooter } from "../formatters/assistant-run-footer-formatter.js";
|
|
6
|
+
import { executeScheduledTask, SCHEDULED_TASK_AGENT } from "./scheduled-task-executor-service.js";
|
|
7
|
+
import { foregroundSessionState } from "../managers/foreground-session-state-manager.js";
|
|
8
|
+
import { cleanupScheduledTaskSessionIgnores } from "./scheduled-task-session-ignore-service.js";
|
|
9
|
+
import { computeNextRunAt, isTaskDue } from "./scheduled-task-next-run-service.js";
|
|
10
|
+
import { getScheduledTask, listScheduledTasks, removeScheduledTask, replaceScheduledTasks, updateScheduledTask, } from "../stores/scheduled-task-store.js";
|
|
13
11
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
14
|
-
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
15
12
|
const TASK_DESCRIPTION_PREVIEW_LENGTH = 64;
|
|
16
13
|
const RESTART_INTERRUPTED_ERROR = "Interrupted by bot restart during scheduled task execution.";
|
|
17
|
-
function getScheduledTaskDeliveryFormat() {
|
|
18
|
-
return config.bot.messageFormatMode === "markdown" ? "markdown_v2" : "raw";
|
|
19
|
-
}
|
|
20
|
-
function buildScheduledTaskSuccessMessageParts(delivery) {
|
|
21
|
-
if (!delivery.resultText) {
|
|
22
|
-
return [delivery.notificationText];
|
|
23
|
-
}
|
|
24
|
-
if (config.bot.messageFormatMode !== "markdown") {
|
|
25
|
-
return formatSummaryWithMode(`${delivery.notificationText}\n\n${delivery.resultText}`, config.bot.messageFormatMode);
|
|
26
|
-
}
|
|
27
|
-
const header = escapePlainTextForTelegramMarkdownV2(delivery.notificationText);
|
|
28
|
-
const resultParts = formatSummaryWithMode(delivery.resultText, config.bot.messageFormatMode);
|
|
29
|
-
if (resultParts.length === 0) {
|
|
30
|
-
return [header];
|
|
31
|
-
}
|
|
32
|
-
const firstPart = `${header}\n\n${resultParts[0]}`;
|
|
33
|
-
if (firstPart.length <= TELEGRAM_MESSAGE_LIMIT) {
|
|
34
|
-
return [firstPart, ...resultParts.slice(1)];
|
|
35
|
-
}
|
|
36
|
-
return [header, ...resultParts];
|
|
37
|
-
}
|
|
38
14
|
function normalizeTaskPrompt(prompt) {
|
|
39
15
|
const normalized = prompt.replace(/\s+/g, " ").trim();
|
|
40
16
|
if (normalized.length <= TASK_DESCRIPTION_PREVIEW_LENGTH) {
|
|
@@ -85,14 +61,16 @@ function buildErrorDelivery(task, runAt, errorMessage) {
|
|
|
85
61
|
export class ScheduledTaskRuntime {
|
|
86
62
|
botApi = null;
|
|
87
63
|
chatId = null;
|
|
64
|
+
deliverySender = null;
|
|
88
65
|
initialized = false;
|
|
89
66
|
timersByTaskId = new Map();
|
|
90
67
|
runningTaskIds = new Set();
|
|
91
68
|
deliveryQueue = [];
|
|
92
69
|
flushInProgress = false;
|
|
93
|
-
async initialize(bot) {
|
|
70
|
+
async initialize(bot, deliverySender) {
|
|
94
71
|
this.botApi = bot.api;
|
|
95
72
|
this.chatId = config.telegram.allowedUserId;
|
|
73
|
+
this.deliverySender = deliverySender ?? null;
|
|
96
74
|
if (this.initialized) {
|
|
97
75
|
return;
|
|
98
76
|
}
|
|
@@ -153,6 +131,7 @@ export class ScheduledTaskRuntime {
|
|
|
153
131
|
}
|
|
154
132
|
this.botApi = null;
|
|
155
133
|
this.chatId = null;
|
|
134
|
+
this.deliverySender = null;
|
|
156
135
|
this.initialized = false;
|
|
157
136
|
this.timersByTaskId.clear();
|
|
158
137
|
this.runningTaskIds.clear();
|
|
@@ -370,28 +349,10 @@ export class ScheduledTaskRuntime {
|
|
|
370
349
|
return false;
|
|
371
350
|
}
|
|
372
351
|
try {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
: [delivery.notificationText];
|
|
376
|
-
const format = delivery.status === "success" ? getScheduledTaskDeliveryFormat() : "raw";
|
|
377
|
-
const suppressResultNotification = delivery.status === "success" && Boolean(delivery.footerText);
|
|
378
|
-
for (const part of messageParts) {
|
|
379
|
-
await sendBotText({
|
|
380
|
-
api: this.botApi,
|
|
381
|
-
chatId: this.chatId,
|
|
382
|
-
text: part,
|
|
383
|
-
format,
|
|
384
|
-
...(suppressResultNotification ? { options: { disable_notification: true } } : {}),
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
if (delivery.status === "success" && delivery.footerText) {
|
|
388
|
-
await sendBotText({
|
|
389
|
-
api: this.botApi,
|
|
390
|
-
chatId: this.chatId,
|
|
391
|
-
text: delivery.footerText,
|
|
392
|
-
format: "raw",
|
|
393
|
-
});
|
|
352
|
+
if (this.deliverySender) {
|
|
353
|
+
return await this.deliverySender.send(delivery);
|
|
394
354
|
}
|
|
355
|
+
await this.botApi.sendMessage(this.chatId, delivery.notificationText);
|
|
395
356
|
return true;
|
|
396
357
|
}
|
|
397
358
|
catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { opencodeClient } from "
|
|
2
|
-
import { logger } from "
|
|
3
|
-
import { cleanupScheduledTaskSessionIgnores, registerScheduledTaskSessionIgnore, } from "./session-ignore.js";
|
|
1
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { cleanupScheduledTaskSessionIgnores, registerScheduledTaskSessionIgnore, } from "./scheduled-task-session-ignore-service.js";
|
|
4
4
|
const SCHEDULE_PARSE_SESSION_TITLE = "Scheduled task schedule parser";
|
|
5
5
|
function getLocalTimezone() {
|
|
6
6
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getScheduledTaskSessionIgnores, setScheduledTaskSessionIgnores, } from "../settings
|
|
1
|
+
import { getScheduledTaskSessionIgnores, setScheduledTaskSessionIgnores, } from "../stores/settings-store.js";
|
|
2
2
|
const SCHEDULED_TASK_SESSION_IGNORE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
3
3
|
let mutationQueue = Promise.resolve();
|
|
4
4
|
function isFreshIgnore(ignore, nowMs) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
|
-
import { opencodeClient } from "
|
|
5
|
-
import { getSessionDirectoryCache, setSessionDirectoryCache } from "../settings
|
|
6
|
-
import { logger } from "
|
|
4
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
5
|
+
import { getSessionDirectoryCache, setSessionDirectoryCache } from "../stores/settings-store.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
7
|
const CACHE_VERSION = 1;
|
|
8
8
|
const INITIAL_WARMUP_LIMIT = 1000;
|
|
9
9
|
const INCREMENTAL_SYNC_LIMIT = 1000;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCurrentSession as getSettingsSession, setCurrentSession as setSettingsSession, clearSession as clearSettingsSession, } from "../settings
|
|
1
|
+
import { getCurrentSession as getSettingsSession, setCurrentSession as setSettingsSession, clearSession as clearSettingsSession, } from "../stores/settings-store.js";
|
|
2
2
|
export function setCurrentSession(sessionInfo) {
|
|
3
3
|
setSettingsSession(sessionInfo);
|
|
4
4
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
+
function normalizeDirectoryForCommandApi(directory) {
|
|
3
|
+
return directory.replace(/\\/g, "/");
|
|
4
|
+
}
|
|
5
|
+
export async function loadSkillsCatalog(projectDirectory) {
|
|
6
|
+
const { data, error } = await opencodeClient.command.list({
|
|
7
|
+
directory: normalizeDirectoryForCommandApi(projectDirectory),
|
|
8
|
+
});
|
|
9
|
+
if (error || !data) {
|
|
10
|
+
throw error || new Error("No skill data received");
|
|
11
|
+
}
|
|
12
|
+
return data
|
|
13
|
+
.filter((skill) => {
|
|
14
|
+
const source = skill.source;
|
|
15
|
+
return typeof skill.name === "string" && skill.name.trim().length > 0 && source === "skill";
|
|
16
|
+
})
|
|
17
|
+
.map((skill) => ({
|
|
18
|
+
name: skill.name,
|
|
19
|
+
description: skill.description,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { config } from "
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
const STT_REQUEST_TIMEOUT_MS = 60_000;
|
|
4
4
|
/**
|
|
5
5
|
* Returns true if STT is configured (API URL and API key are set).
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { config } from "
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
import textToSpeech from "@google-cloud/text-to-speech";
|
|
4
4
|
const TTS_REQUEST_TIMEOUT_MS = 60_000;
|
|
5
|
+
const MAX_TTS_INPUT_CHARS = 4_000;
|
|
5
6
|
export function isTtsConfigured() {
|
|
6
7
|
if (config.tts.provider === "google") {
|
|
7
8
|
return Boolean(process.env.GOOGLE_APPLICATION_CREDENTIALS);
|
|
@@ -104,11 +105,51 @@ async function synthesizeWithOpenAi(text) {
|
|
|
104
105
|
clearTimeout(timeout);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
async function synthesizeWithElevenLabs(text) {
|
|
109
|
+
const apiUrl = config.tts.apiUrl.replace(/\/+$/, "");
|
|
110
|
+
const voiceId = config.tts.voice || "21m00Tcm4TlvDq8ikWAM";
|
|
111
|
+
const url = `${apiUrl}/text-to-speech/${encodeURIComponent(voiceId)}`;
|
|
112
|
+
logger.debug(`[TTS] ElevenLabs: url=${url}, model=${config.tts.model}, voice=${voiceId}, chars=${text.length}`);
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const timeout = setTimeout(() => controller.abort(), TTS_REQUEST_TIMEOUT_MS);
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch(url, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"xi-api-key": config.tts.apiKey,
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
Accept: "audio/mpeg",
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
text,
|
|
125
|
+
model_id: config.tts.model || "eleven_flash_v2_5",
|
|
126
|
+
}),
|
|
127
|
+
signal: controller.signal,
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const errorBody = await response.text().catch(() => "");
|
|
131
|
+
throw new Error(`TTS API returned HTTP ${response.status}: ${errorBody || response.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
134
|
+
if (buffer.length === 0) {
|
|
135
|
+
throw new Error("ElevenLabs TTS API returned an empty audio response");
|
|
136
|
+
}
|
|
137
|
+
logger.debug(`[TTS] Generated ElevenLabs speech audio: ${buffer.length} bytes`);
|
|
138
|
+
return { buffer, filename: "assistant-reply.mp3", mimeType: "audio/mpeg" };
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
107
144
|
// --- Public API ---
|
|
108
145
|
function getNotConfiguredMessage() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
if (config.tts.provider === "google") {
|
|
147
|
+
return "TTS is not configured: set GOOGLE_APPLICATION_CREDENTIALS for Google Cloud TTS";
|
|
148
|
+
}
|
|
149
|
+
if (config.tts.provider === "elevenlabs") {
|
|
150
|
+
return "TTS is not configured: set TTS_API_URL and TTS_API_KEY for ElevenLabs";
|
|
151
|
+
}
|
|
152
|
+
return "TTS is not configured: set TTS_API_URL and TTS_API_KEY";
|
|
112
153
|
}
|
|
113
154
|
export async function synthesizeSpeech(text) {
|
|
114
155
|
if (!isTtsConfigured()) {
|
|
@@ -123,6 +164,9 @@ export async function synthesizeSpeech(text) {
|
|
|
123
164
|
if (config.tts.provider === "google") {
|
|
124
165
|
return await synthesizeWithGoogle(input);
|
|
125
166
|
}
|
|
167
|
+
if (config.tts.provider === "elevenlabs") {
|
|
168
|
+
return await synthesizeWithElevenLabs(input);
|
|
169
|
+
}
|
|
126
170
|
return await synthesizeWithOpenAi(input);
|
|
127
171
|
}
|
|
128
172
|
catch (err) {
|
|
@@ -132,3 +176,22 @@ export async function synthesizeSpeech(text) {
|
|
|
132
176
|
throw err;
|
|
133
177
|
}
|
|
134
178
|
}
|
|
179
|
+
export async function prepareTtsResponseForSession({ sessionId, text, consumeResponseMode, isTtsConfigured: isTtsConfiguredImpl = isTtsConfigured, synthesizeSpeech: synthesizeSpeechImpl = synthesizeSpeech, }) {
|
|
180
|
+
const responseMode = consumeResponseMode(sessionId);
|
|
181
|
+
if (responseMode !== "text_and_tts") {
|
|
182
|
+
return { shouldSend: false };
|
|
183
|
+
}
|
|
184
|
+
const normalizedText = text.trim();
|
|
185
|
+
if (!normalizedText) {
|
|
186
|
+
return { shouldSend: false };
|
|
187
|
+
}
|
|
188
|
+
if (!isTtsConfiguredImpl()) {
|
|
189
|
+
logger.info(`[TTS] Skipping audio reply for session ${sessionId}: TTS is not configured`);
|
|
190
|
+
return { shouldSend: false };
|
|
191
|
+
}
|
|
192
|
+
if (normalizedText.length > MAX_TTS_INPUT_CHARS) {
|
|
193
|
+
logger.warn(`[TTS] Skipping audio reply for session ${sessionId}: text length ${normalizedText.length} exceeds limit ${MAX_TTS_INPUT_CHARS}`);
|
|
194
|
+
return { shouldSend: false };
|
|
195
|
+
}
|
|
196
|
+
return { shouldSend: true, speech: await synthesizeSpeechImpl(normalizedText) };
|
|
197
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Variant Manager - manages model variants (reasoning modes)
|
|
3
3
|
*/
|
|
4
|
-
import { opencodeClient } from "
|
|
5
|
-
import { getCurrentModel, setCurrentModel } from "../settings
|
|
6
|
-
import { logger } from "
|
|
4
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
5
|
+
import { getCurrentModel, setCurrentModel } from "../stores/settings-store.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
7
|
/**
|
|
8
8
|
* Get available variants for a model from OpenCode API
|
|
9
9
|
* @param providerID Provider ID
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getScheduledTasks, setScheduledTasks } from "
|
|
2
|
-
import { logger } from "
|
|
3
|
-
import { cloneScheduledTask } from "
|
|
1
|
+
import { getScheduledTasks, setScheduledTasks } from "./settings-store.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { cloneScheduledTask } from "../types/scheduled-task.js";
|
|
4
4
|
let scheduledTaskMutationQueue = Promise.resolve();
|
|
5
5
|
async function mutateScheduledTasks(mutator) {
|
|
6
6
|
const runMutation = async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { cloneScheduledTask } from "../scheduled-task/types.js";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { cloneScheduledTask } from "../types/scheduled-task.js";
|
|
3
|
+
import { getRuntimePaths } from "../../runtime/paths.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
5
|
function cloneScheduledTasks(tasks) {
|
|
6
6
|
return tasks?.map((task) => cloneScheduledTask(task));
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { formatVariantForButton } from "../../variant/manager.js";
|
|
1
|
+
import { selectAgent } from "../../app/services/agent-selection-service.js";
|
|
2
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
3
|
+
import { formatVariantForButton } from "../../app/services/variant-selection-service.js";
|
|
4
|
+
import { getAgentDisplayName } from "../../app/types/agent.js";
|
|
6
5
|
import { logger } from "../../utils/logger.js";
|
|
7
|
-
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
8
|
-
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
9
|
-
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
|
-
import { clearActiveInlineMenu, ensureActiveInlineMenu, replyWithInlineMenu, } from "./inline-menu.js";
|
|
11
6
|
import { t } from "../../i18n/index.js";
|
|
7
|
+
import { createMainKeyboard } from "../keyboards/main-reply-keyboard.js";
|
|
8
|
+
import { keyboardManager } from "../keyboards/keyboard-manager.js";
|
|
9
|
+
import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
|
|
10
|
+
import { clearActiveInlineMenu, ensureActiveInlineMenu } from "../menus/inline-menu.js";
|
|
12
11
|
/**
|
|
13
12
|
* Handle agent selection callback
|
|
14
13
|
* @param ctx grammY context
|
|
@@ -67,51 +66,3 @@ export async function handleAgentSelect(ctx) {
|
|
|
67
66
|
return false;
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Build inline keyboard with available agents
|
|
72
|
-
* @param currentAgent Current agent name for highlighting
|
|
73
|
-
* @returns InlineKeyboard with agent selection buttons
|
|
74
|
-
*/
|
|
75
|
-
export async function buildAgentSelectionMenu(currentAgent) {
|
|
76
|
-
const keyboard = new InlineKeyboard();
|
|
77
|
-
const agents = await getAvailableAgents();
|
|
78
|
-
if (agents.length === 0) {
|
|
79
|
-
logger.warn("[AgentHandler] No available agents found");
|
|
80
|
-
return keyboard;
|
|
81
|
-
}
|
|
82
|
-
// Add button for each agent
|
|
83
|
-
agents.forEach((agent) => {
|
|
84
|
-
const isActive = agent.name === currentAgent;
|
|
85
|
-
const label = isActive
|
|
86
|
-
? `✅ ${getAgentDisplayName(agent.name)}`
|
|
87
|
-
: getAgentDisplayName(agent.name);
|
|
88
|
-
keyboard.text(label, `agent:${agent.name}`).row();
|
|
89
|
-
});
|
|
90
|
-
return keyboard;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Show agent selection menu
|
|
94
|
-
* @param ctx grammY context
|
|
95
|
-
*/
|
|
96
|
-
export async function showAgentSelectionMenu(ctx) {
|
|
97
|
-
try {
|
|
98
|
-
const currentAgent = await fetchCurrentAgent();
|
|
99
|
-
const keyboard = await buildAgentSelectionMenu(currentAgent);
|
|
100
|
-
if (keyboard.inline_keyboard.length === 0) {
|
|
101
|
-
await ctx.reply(t("agent.menu.empty"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const text = currentAgent
|
|
105
|
-
? t("agent.menu.current", { name: getAgentDisplayName(currentAgent) })
|
|
106
|
-
: t("agent.menu.select");
|
|
107
|
-
await replyWithInlineMenu(ctx, {
|
|
108
|
-
menuKind: "agent",
|
|
109
|
-
text,
|
|
110
|
-
keyboard,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
logger.error("[AgentHandler] Error showing agent menu:", err);
|
|
115
|
-
await ctx.reply(t("agent.menu.error"));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { clearAllInteractionState } from "../../app/managers/interaction-manager.js";
|
|
2
|
+
import { t } from "../../i18n/index.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { handleAgentSelect } from "./agent-selection-callback-handler.js";
|
|
5
|
+
import { handleCommandsCallback } from "./command-catalog-callback-handler.js";
|
|
6
|
+
import { handleCompactConfirm } from "./context-control-callback-handler.js";
|
|
7
|
+
import { handleLsCallback, handleOpenCallback } from "./file-browser-callback-handler.js";
|
|
8
|
+
import { handleInlineMenuCancel } from "./inline-menu-cancel-callback-handler.js";
|
|
9
|
+
import { handleMcpsCallback } from "./mcp-catalog-callback-handler.js";
|
|
10
|
+
import { handleMessagesCallback } from "./message-history-callback-handler.js";
|
|
11
|
+
import { handleModelSearchCallback, handleModelSearchResults, handleModelSelect, } from "./model-selection-callback-handler.js";
|
|
12
|
+
import { handlePermissionCallback } from "./permission-callback-handler.js";
|
|
13
|
+
import { handleProjectSelect } from "./project-callback-handler.js";
|
|
14
|
+
import { handleQuestionCallback } from "./question-callback-handler.js";
|
|
15
|
+
import { handleRenameCancel } from "./rename-callback-handler.js";
|
|
16
|
+
import { handleBackgroundSessionOpen, handleSessionSelect, } from "./session-callback-handler.js";
|
|
17
|
+
import { handleSkillsCallback } from "./skills-catalog-callback-handler.js";
|
|
18
|
+
import { handleTaskCallback, handleTaskListCallback, } from "./scheduled-task-callback-handler.js";
|
|
19
|
+
import { handleVariantSelect } from "./variant-selection-callback-handler.js";
|
|
20
|
+
import { handleWorktreeCallback } from "./worktree-callback-handler.js";
|
|
21
|
+
import { clearLsPathIndex, clearOpenPathIndex } from "../menus/file-browser-menu.js";
|
|
22
|
+
export function registerCallbackRouter(bot, deps) {
|
|
23
|
+
bot.on("callback_query:data", async (ctx) => {
|
|
24
|
+
logger.debug(`[Bot] Received callback_query:data: ${ctx.callbackQuery?.data}`);
|
|
25
|
+
logger.debug(`[Bot] Callback context: from=${ctx.from?.id}, chat=${ctx.chat?.id}`);
|
|
26
|
+
if (ctx.chat) {
|
|
27
|
+
deps.setTelegramContext(bot, ctx.chat.id);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const handledBackgroundSession = await handleBackgroundSessionOpen(ctx, {
|
|
31
|
+
bot,
|
|
32
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
33
|
+
});
|
|
34
|
+
const handledInlineCancel = await handleInlineMenuCancel(ctx);
|
|
35
|
+
if (handledInlineCancel) {
|
|
36
|
+
clearOpenPathIndex();
|
|
37
|
+
clearLsPathIndex();
|
|
38
|
+
}
|
|
39
|
+
const handledSession = await handleSessionSelect(ctx, {
|
|
40
|
+
bot,
|
|
41
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
42
|
+
});
|
|
43
|
+
const handledProject = await handleProjectSelect(ctx, {
|
|
44
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
45
|
+
});
|
|
46
|
+
const handledWorktree = await handleWorktreeCallback(ctx, {
|
|
47
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
48
|
+
});
|
|
49
|
+
const handledOpen = await handleOpenCallback(ctx, {
|
|
50
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
51
|
+
});
|
|
52
|
+
const handledLs = await handleLsCallback(ctx);
|
|
53
|
+
const handledQuestion = await handleQuestionCallback(ctx);
|
|
54
|
+
const handledPermission = await handlePermissionCallback(ctx);
|
|
55
|
+
const handledAgent = await handleAgentSelect(ctx);
|
|
56
|
+
const handledModelSearch = await handleModelSearchCallback(ctx);
|
|
57
|
+
const handledModelSearchResults = await handleModelSearchResults(ctx);
|
|
58
|
+
const handledModel = await handleModelSelect(ctx);
|
|
59
|
+
const handledVariant = await handleVariantSelect(ctx);
|
|
60
|
+
const handledCompactConfirm = await handleCompactConfirm(ctx);
|
|
61
|
+
const handledTask = await handleTaskCallback(ctx);
|
|
62
|
+
const handledTaskList = await handleTaskListCallback(ctx);
|
|
63
|
+
const handledRenameCancel = await handleRenameCancel(ctx);
|
|
64
|
+
const handledCommands = await handleCommandsCallback(ctx, {
|
|
65
|
+
bot,
|
|
66
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
67
|
+
});
|
|
68
|
+
const handledMessages = await handleMessagesCallback(ctx, {
|
|
69
|
+
bot,
|
|
70
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
71
|
+
});
|
|
72
|
+
const handledSkills = await handleSkillsCallback(ctx, {
|
|
73
|
+
bot,
|
|
74
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
75
|
+
});
|
|
76
|
+
const handledMcps = await handleMcpsCallback(ctx);
|
|
77
|
+
logger.debug(`[Bot] Callback handled: backgroundSession=${handledBackgroundSession}, inlineCancel=${handledInlineCancel}, session=${handledSession}, project=${handledProject}, worktree=${handledWorktree}, open=${handledOpen}, ls=${handledLs}, question=${handledQuestion}, permission=${handledPermission}, agent=${handledAgent}, modelSearch=${handledModelSearch}, modelSearchResults=${handledModelSearchResults}, model=${handledModel}, variant=${handledVariant}, compactConfirm=${handledCompactConfirm}, task=${handledTask}, taskList=${handledTaskList}, rename=${handledRenameCancel}, commands=${handledCommands}, messages=${handledMessages}, skills=${handledSkills}, mcps=${handledMcps}`);
|
|
78
|
+
if (!handledBackgroundSession &&
|
|
79
|
+
!handledInlineCancel &&
|
|
80
|
+
!handledSession &&
|
|
81
|
+
!handledProject &&
|
|
82
|
+
!handledWorktree &&
|
|
83
|
+
!handledOpen &&
|
|
84
|
+
!handledLs &&
|
|
85
|
+
!handledQuestion &&
|
|
86
|
+
!handledPermission &&
|
|
87
|
+
!handledAgent &&
|
|
88
|
+
!handledModelSearch &&
|
|
89
|
+
!handledModelSearchResults &&
|
|
90
|
+
!handledModel &&
|
|
91
|
+
!handledVariant &&
|
|
92
|
+
!handledCompactConfirm &&
|
|
93
|
+
!handledTask &&
|
|
94
|
+
!handledTaskList &&
|
|
95
|
+
!handledRenameCancel &&
|
|
96
|
+
!handledCommands &&
|
|
97
|
+
!handledMessages &&
|
|
98
|
+
!handledSkills &&
|
|
99
|
+
!handledMcps) {
|
|
100
|
+
logger.debug("Unknown callback query:", ctx.callbackQuery?.data);
|
|
101
|
+
await ctx.answerCallbackQuery({ text: t("callback.unknown_command") });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
logger.error("[Bot] Error handling callback:", err);
|
|
106
|
+
clearAllInteractionState("callback_handler_error");
|
|
107
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") }).catch(() => { });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|