@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { permissionManager } from "../../app/managers/permission-manager.js";
|
|
2
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
3
|
+
import { getCurrentProject } from "../../app/stores/settings-store.js";
|
|
4
|
+
import { getCurrentSession } from "../../app/services/session-service.js";
|
|
5
|
+
import { summaryAggregator } from "../../app/managers/summary-aggregation-manager.js";
|
|
6
|
+
import { clearPermissionInteraction, syncPermissionInteractionState } from "../menus/permission-menu.js";
|
|
7
|
+
import { t } from "../../i18n/index.js";
|
|
8
|
+
import { logger } from "../../utils/logger.js";
|
|
9
|
+
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
10
|
+
function getCallbackMessageId(ctx) {
|
|
11
|
+
const message = ctx.callbackQuery?.message;
|
|
12
|
+
if (!message || !("message_id" in message)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const messageId = message.message_id;
|
|
16
|
+
return typeof messageId === "number" ? messageId : null;
|
|
17
|
+
}
|
|
18
|
+
function isPermissionReply(value) {
|
|
19
|
+
return value === "once" || value === "always" || value === "reject";
|
|
20
|
+
}
|
|
21
|
+
export async function handlePermissionCallback(ctx) {
|
|
22
|
+
const data = ctx.callbackQuery?.data;
|
|
23
|
+
if (!data)
|
|
24
|
+
return false;
|
|
25
|
+
if (!data.startsWith("permission:")) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
logger.debug(`[PermissionHandler] Received callback: ${data}`);
|
|
29
|
+
if (!permissionManager.isActive()) {
|
|
30
|
+
clearPermissionInteraction("permission_inactive_callback");
|
|
31
|
+
await ctx.answerCallbackQuery({ text: t("permission.inactive_callback"), show_alert: true });
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const callbackMessageId = getCallbackMessageId(ctx);
|
|
35
|
+
if (!permissionManager.isActiveMessage(callbackMessageId)) {
|
|
36
|
+
await ctx.answerCallbackQuery({ text: t("permission.inactive_callback"), show_alert: true });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const requestID = permissionManager.getRequestID(callbackMessageId);
|
|
40
|
+
if (!requestID) {
|
|
41
|
+
await ctx.answerCallbackQuery({ text: t("permission.inactive_callback"), show_alert: true });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const parts = data.split(":");
|
|
45
|
+
const action = parts[1];
|
|
46
|
+
if (!isPermissionReply(action)) {
|
|
47
|
+
await ctx.answerCallbackQuery({
|
|
48
|
+
text: t("permission.processing_error_callback"),
|
|
49
|
+
show_alert: true,
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await handlePermissionReply(ctx, action, requestID, callbackMessageId);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.error("[PermissionHandler] Error handling callback:", err);
|
|
58
|
+
await ctx.answerCallbackQuery({
|
|
59
|
+
text: t("permission.processing_error_callback"),
|
|
60
|
+
show_alert: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
async function handlePermissionReply(ctx, reply, requestID, callbackMessageId) {
|
|
66
|
+
const currentProject = getCurrentProject();
|
|
67
|
+
const currentSession = getCurrentSession();
|
|
68
|
+
const chatId = ctx.chat?.id;
|
|
69
|
+
const directory = currentSession?.directory ?? currentProject?.worktree;
|
|
70
|
+
if (!directory || !chatId) {
|
|
71
|
+
permissionManager.clear();
|
|
72
|
+
clearPermissionInteraction("permission_invalid_runtime_context");
|
|
73
|
+
await ctx.answerCallbackQuery({
|
|
74
|
+
text: t("permission.no_active_request_callback"),
|
|
75
|
+
show_alert: true,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const replyLabels = {
|
|
80
|
+
once: t("permission.reply.once"),
|
|
81
|
+
always: t("permission.reply.always"),
|
|
82
|
+
reject: t("permission.reply.reject"),
|
|
83
|
+
};
|
|
84
|
+
await ctx.answerCallbackQuery({ text: replyLabels[reply] });
|
|
85
|
+
await ctx.deleteMessage().catch(() => { });
|
|
86
|
+
summaryAggregator.stopTypingIndicator();
|
|
87
|
+
logger.info(`[PermissionHandler] Sending permission reply: ${reply}, requestID=${requestID}`);
|
|
88
|
+
safeBackgroundTask({
|
|
89
|
+
taskName: "permission.reply",
|
|
90
|
+
task: () => opencodeClient.permission.reply({
|
|
91
|
+
requestID,
|
|
92
|
+
directory,
|
|
93
|
+
reply,
|
|
94
|
+
}),
|
|
95
|
+
onSuccess: ({ error }) => {
|
|
96
|
+
if (error) {
|
|
97
|
+
logger.error("[PermissionHandler] Failed to send permission reply:", error);
|
|
98
|
+
if (ctx.api && chatId) {
|
|
99
|
+
void ctx.api.sendMessage(chatId, t("permission.send_reply_error")).catch(() => { });
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
logger.info("[PermissionHandler] Permission reply sent successfully");
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
permissionManager.removeByMessageId(callbackMessageId);
|
|
107
|
+
if (!permissionManager.isActive()) {
|
|
108
|
+
clearPermissionInteraction("permission_replied");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
syncPermissionInteractionState({
|
|
112
|
+
lastRepliedRequestID: requestID,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { clearAllInteractionState } from "../../app/managers/interaction-manager.js";
|
|
2
|
+
import { getProjects } from "../../app/services/project-service.js";
|
|
3
|
+
import { isForegroundBusy } from "../../app/services/run-control-service.js";
|
|
4
|
+
import { switchToProject } from "../../app/services/project-switch-service.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
|
+
import { appendInlineMenuCancelButton, ensureActiveInlineMenu } from "../menus/inline-menu.js";
|
|
8
|
+
import { buildProjectsMenuView, parseProjectPageCallback } from "../menus/project-selection-menu.js";
|
|
9
|
+
import { replyBusyBlocked } from "../messages/busy-blocked-renderer.js";
|
|
10
|
+
import { createProjectSwitchPresentation } from "../services/project-switch-presentation.js";
|
|
11
|
+
export async function handleProjectSelect(ctx, deps = {}) {
|
|
12
|
+
const callbackQuery = ctx.callbackQuery;
|
|
13
|
+
if (!callbackQuery?.data) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const page = parseProjectPageCallback(callbackQuery.data);
|
|
17
|
+
const isProjectSelection = callbackQuery.data.startsWith("project:");
|
|
18
|
+
if (page === null && !isProjectSelection) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (isForegroundBusy()) {
|
|
22
|
+
await replyBusyBlocked(ctx);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (page !== null) {
|
|
26
|
+
const isActiveMenu = await ensureActiveInlineMenu(ctx, "project");
|
|
27
|
+
if (!isActiveMenu) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const projects = await getProjects();
|
|
32
|
+
if (projects.length === 0) {
|
|
33
|
+
await ctx.answerCallbackQuery();
|
|
34
|
+
await ctx.reply(t("projects.empty"));
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const { text, keyboard } = await buildProjectsMenuView(projects, page);
|
|
38
|
+
await ctx.answerCallbackQuery();
|
|
39
|
+
await ctx.editMessageText(text, {
|
|
40
|
+
reply_markup: appendInlineMenuCancelButton(keyboard, "project"),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.error("[Bot] Error switching projects page:", error);
|
|
45
|
+
await ctx.answerCallbackQuery({ text: t("projects.page_load_error") });
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const projectId = callbackQuery.data.replace("project:", "");
|
|
50
|
+
const isActiveMenu = await ensureActiveInlineMenu(ctx, "project");
|
|
51
|
+
if (!isActiveMenu) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const projects = await getProjects();
|
|
56
|
+
const selectedProject = projects.find((p) => p.id === projectId);
|
|
57
|
+
if (!selectedProject) {
|
|
58
|
+
throw new Error(`Project with id ${projectId} not found`);
|
|
59
|
+
}
|
|
60
|
+
const projectName = selectedProject.name || selectedProject.worktree;
|
|
61
|
+
logger.info(`[Bot] Project selected: ${projectName} (id: ${projectId})`);
|
|
62
|
+
const keyboard = await switchToProject(ctx, selectedProject, "project_switched", {
|
|
63
|
+
ensureEventSubscription: deps.ensureEventSubscription,
|
|
64
|
+
presentation: createProjectSwitchPresentation(),
|
|
65
|
+
});
|
|
66
|
+
await ctx.answerCallbackQuery();
|
|
67
|
+
await ctx.reply(t("projects.selected", { project: projectName }), {
|
|
68
|
+
reply_markup: keyboard,
|
|
69
|
+
});
|
|
70
|
+
await ctx.deleteMessage();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
clearAllInteractionState("project_select_error");
|
|
74
|
+
logger.error("[Bot] Error selecting project:", error);
|
|
75
|
+
await ctx.answerCallbackQuery();
|
|
76
|
+
await ctx.reply(t("projects.select_error"));
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { questionManager } from "../../app/managers/question-manager.js";
|
|
2
|
+
import { clearQuestionInteraction, showNextQuestion, syncQuestionInteractionState, updateQuestionMessage, } from "../menus/question-menu.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
function getCallbackMessageId(ctx) {
|
|
6
|
+
const message = ctx.callbackQuery?.message;
|
|
7
|
+
if (!message || !("message_id" in message)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const messageId = message.message_id;
|
|
11
|
+
return typeof messageId === "number" ? messageId : null;
|
|
12
|
+
}
|
|
13
|
+
export async function handleQuestionCallback(ctx) {
|
|
14
|
+
const data = ctx.callbackQuery?.data;
|
|
15
|
+
if (!data)
|
|
16
|
+
return false;
|
|
17
|
+
if (!data.startsWith("question:")) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
logger.debug(`[QuestionHandler] Received callback: ${data}`);
|
|
21
|
+
if (!questionManager.isActive()) {
|
|
22
|
+
clearQuestionInteraction("question_inactive_callback");
|
|
23
|
+
await ctx.answerCallbackQuery({ text: t("question.inactive_callback"), show_alert: true });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const callbackMessageId = getCallbackMessageId(ctx);
|
|
27
|
+
if (!questionManager.isActiveMessage(callbackMessageId)) {
|
|
28
|
+
await ctx.answerCallbackQuery({ text: t("question.inactive_callback"), show_alert: true });
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const parts = data.split(":");
|
|
32
|
+
const action = parts[1];
|
|
33
|
+
const questionIndex = parseInt(parts[2], 10);
|
|
34
|
+
if (Number.isNaN(questionIndex) || questionIndex !== questionManager.getCurrentIndex()) {
|
|
35
|
+
await ctx.answerCallbackQuery({ text: t("question.inactive_callback"), show_alert: true });
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
switch (action) {
|
|
40
|
+
case "select":
|
|
41
|
+
{
|
|
42
|
+
const optionIndex = parseInt(parts[3], 10);
|
|
43
|
+
if (Number.isNaN(optionIndex)) {
|
|
44
|
+
await ctx.answerCallbackQuery({
|
|
45
|
+
text: t("question.processing_error_callback"),
|
|
46
|
+
show_alert: true,
|
|
47
|
+
});
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
await handleSelectOption(ctx, questionIndex, optionIndex);
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case "submit":
|
|
54
|
+
await handleSubmitAnswer(ctx, questionIndex);
|
|
55
|
+
break;
|
|
56
|
+
case "custom":
|
|
57
|
+
await handleCustomAnswer(ctx, questionIndex);
|
|
58
|
+
break;
|
|
59
|
+
case "cancel":
|
|
60
|
+
await handleCancelPoll(ctx);
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
await ctx.answerCallbackQuery({
|
|
64
|
+
text: t("question.processing_error_callback"),
|
|
65
|
+
show_alert: true,
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logger.error("[QuestionHandler] Error handling callback:", err);
|
|
72
|
+
await ctx.answerCallbackQuery({
|
|
73
|
+
text: t("question.processing_error_callback"),
|
|
74
|
+
show_alert: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
async function handleSelectOption(ctx, questionIndex, optionIndex) {
|
|
80
|
+
logger.debug(`[QuestionHandler] handleSelectOption: qIndex=${questionIndex}, oIndex=${optionIndex}`);
|
|
81
|
+
const question = questionManager.getCurrentQuestion();
|
|
82
|
+
if (!question) {
|
|
83
|
+
logger.debug("[QuestionHandler] No current question");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (questionManager.isWaitingForCustomInput(questionIndex)) {
|
|
87
|
+
questionManager.clearCustomInput();
|
|
88
|
+
syncQuestionInteractionState("callback", questionIndex, questionManager.getActiveMessageId());
|
|
89
|
+
}
|
|
90
|
+
questionManager.selectOption(questionIndex, optionIndex);
|
|
91
|
+
if (question.multiple) {
|
|
92
|
+
logger.debug("[QuestionHandler] Multiple choice mode, updating message");
|
|
93
|
+
await updateQuestionMessage(ctx);
|
|
94
|
+
await ctx.answerCallbackQuery();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
logger.debug("[QuestionHandler] Single choice mode, moving to next question");
|
|
98
|
+
await ctx.answerCallbackQuery();
|
|
99
|
+
const answer = questionManager.getSelectedAnswer(questionIndex);
|
|
100
|
+
logger.debug(`[QuestionHandler] Selected answer for question ${questionIndex}: ${answer}`);
|
|
101
|
+
await ctx.deleteMessage().catch(() => { });
|
|
102
|
+
await showNextQuestion(ctx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function handleSubmitAnswer(ctx, questionIndex) {
|
|
106
|
+
if (questionManager.isWaitingForCustomInput(questionIndex)) {
|
|
107
|
+
questionManager.clearCustomInput();
|
|
108
|
+
syncQuestionInteractionState("callback", questionIndex, questionManager.getActiveMessageId());
|
|
109
|
+
}
|
|
110
|
+
const answer = questionManager.getSelectedAnswer(questionIndex);
|
|
111
|
+
if (!answer) {
|
|
112
|
+
await ctx.answerCallbackQuery({
|
|
113
|
+
text: t("question.select_one_required_callback"),
|
|
114
|
+
show_alert: true,
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
logger.debug(`[QuestionHandler] Submit answer for question ${questionIndex}: ${answer}`);
|
|
119
|
+
await ctx.answerCallbackQuery();
|
|
120
|
+
await ctx.deleteMessage().catch(() => { });
|
|
121
|
+
await showNextQuestion(ctx);
|
|
122
|
+
}
|
|
123
|
+
async function handleCustomAnswer(ctx, questionIndex) {
|
|
124
|
+
questionManager.startCustomInput(questionIndex);
|
|
125
|
+
syncQuestionInteractionState("mixed", questionIndex, questionManager.getActiveMessageId());
|
|
126
|
+
await ctx.answerCallbackQuery({
|
|
127
|
+
text: t("question.enter_custom_callback"),
|
|
128
|
+
show_alert: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function handleCancelPoll(ctx) {
|
|
132
|
+
questionManager.cancel();
|
|
133
|
+
clearQuestionInteraction("question_cancelled");
|
|
134
|
+
await ctx.editMessageText(t("question.cancelled")).catch(() => { });
|
|
135
|
+
await ctx.answerCallbackQuery();
|
|
136
|
+
questionManager.clear();
|
|
137
|
+
}
|
|
138
|
+
export async function handleQuestionTextAnswer(ctx) {
|
|
139
|
+
const text = ctx.message?.text;
|
|
140
|
+
if (!text)
|
|
141
|
+
return;
|
|
142
|
+
const currentIndex = questionManager.getCurrentIndex();
|
|
143
|
+
if (!questionManager.isWaitingForCustomInput(currentIndex)) {
|
|
144
|
+
await ctx.reply(t("question.use_custom_button_first"));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (questionManager.hasCustomAnswer(currentIndex)) {
|
|
148
|
+
await ctx.reply(t("question.answer_already_received"));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
logger.debug(`[QuestionHandler] Custom text answer for question ${currentIndex}: ${text}`);
|
|
152
|
+
questionManager.setCustomAnswer(currentIndex, text);
|
|
153
|
+
questionManager.clearCustomInput();
|
|
154
|
+
const activeMessageId = questionManager.getActiveMessageId();
|
|
155
|
+
if (activeMessageId !== null && ctx.chat) {
|
|
156
|
+
await ctx.api.deleteMessage(ctx.chat.id, activeMessageId).catch(() => { });
|
|
157
|
+
}
|
|
158
|
+
await showNextQuestion(ctx);
|
|
159
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { InlineKeyboard } from "grammy";
|
|
2
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
3
|
-
import {
|
|
4
|
-
import { renameManager } from "../../rename
|
|
5
|
-
import { interactionManager } from "../../interaction
|
|
6
|
-
import { pinnedMessageManager } from "
|
|
2
|
+
import { setCurrentSession } from "../../app/services/session-service.js";
|
|
3
|
+
import { renameManager } from "../../app/managers/rename-manager.js";
|
|
4
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
5
|
+
import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
|
|
7
6
|
import { logger } from "../../utils/logger.js";
|
|
8
7
|
import { t } from "../../i18n/index.js";
|
|
8
|
+
import { RENAME_CANCEL_CALLBACK } from "../menus/rename-menu.js";
|
|
9
9
|
function getCallbackMessageId(ctx) {
|
|
10
10
|
const message = ctx.callbackQuery?.message;
|
|
11
11
|
if (!message || !("message_id" in message)) {
|
|
@@ -20,37 +20,9 @@ function clearRenameInteraction(reason) {
|
|
|
20
20
|
interactionManager.clear(reason);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export async function renameCommand(ctx) {
|
|
24
|
-
try {
|
|
25
|
-
const currentSession = getCurrentSession();
|
|
26
|
-
if (!currentSession) {
|
|
27
|
-
await ctx.reply(t("rename.no_session"));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const keyboard = new InlineKeyboard().text(t("rename.button.cancel"), "rename:cancel");
|
|
31
|
-
const message = await ctx.reply(t("rename.prompt", { title: currentSession.title }), {
|
|
32
|
-
reply_markup: keyboard,
|
|
33
|
-
});
|
|
34
|
-
renameManager.startWaiting(currentSession.id, currentSession.directory, currentSession.title);
|
|
35
|
-
renameManager.setMessageId(message.message_id);
|
|
36
|
-
interactionManager.start({
|
|
37
|
-
kind: "rename",
|
|
38
|
-
expectedInput: "text",
|
|
39
|
-
metadata: {
|
|
40
|
-
sessionId: currentSession.id,
|
|
41
|
-
messageId: message.message_id,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
logger.info(`[RenameCommand] Waiting for new title for session: ${currentSession.id}`);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
logger.error("[RenameCommand] Error starting rename flow:", error);
|
|
48
|
-
await ctx.reply(t("rename.error"));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
23
|
export async function handleRenameCancel(ctx) {
|
|
52
24
|
const data = ctx.callbackQuery?.data;
|
|
53
|
-
if (!data || data !==
|
|
25
|
+
if (!data || data !== RENAME_CANCEL_CALLBACK) {
|
|
54
26
|
return false;
|
|
55
27
|
}
|
|
56
28
|
logger.debug("[RenameHandler] Cancel callback received");
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import { InlineKeyboard } from "grammy";
|
|
2
1
|
import { getDateLocale, t } from "../../i18n/index.js";
|
|
3
|
-
import { interactionManager } from "../../interaction
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
3
|
+
import { taskCreationManager } from "../../app/managers/scheduled-task-creation-manager.js";
|
|
4
|
+
import { getScheduledTask, removeScheduledTask } from "../../app/stores/scheduled-task-store.js";
|
|
5
|
+
import { scheduledTaskRuntime } from "../../app/services/scheduled-task-runtime-service.js";
|
|
7
6
|
import { logger } from "../../utils/logger.js";
|
|
8
|
-
|
|
9
|
-
const TASKLIST_OPEN_PREFIX = `${TASKLIST_CALLBACK_PREFIX}open:`;
|
|
10
|
-
const TASKLIST_DELETE_PREFIX = `${TASKLIST_CALLBACK_PREFIX}delete:`;
|
|
11
|
-
const TASKLIST_CANCEL_CALLBACK = `${TASKLIST_CALLBACK_PREFIX}cancel`;
|
|
12
|
-
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
7
|
+
import { buildCancelKeyboard, buildTaskDetailsKeyboard, TASK_CANCEL_CALLBACK, TASK_RETRY_SCHEDULE_CALLBACK, TASKLIST_CALLBACK_PREFIX, TASKLIST_CANCEL_CALLBACK, TASKLIST_DELETE_PREFIX, TASKLIST_OPEN_PREFIX, } from "../menus/scheduled-task-menu.js";
|
|
13
8
|
function getCallbackMessageId(ctx) {
|
|
14
9
|
const message = ctx.callbackQuery?.message;
|
|
15
10
|
if (!message || !("message_id" in message)) {
|
|
@@ -18,6 +13,41 @@ function getCallbackMessageId(ctx) {
|
|
|
18
13
|
const messageId = message.message_id;
|
|
19
14
|
return typeof messageId === "number" ? messageId : null;
|
|
20
15
|
}
|
|
16
|
+
async function deleteMessageIfPresent(ctx, messageId) {
|
|
17
|
+
if (!ctx.chat || typeof messageId !== "number") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await ctx.api.deleteMessage(ctx.chat.id, messageId).catch(() => { });
|
|
21
|
+
}
|
|
22
|
+
function buildTaskInteractionMetadata(stage, projectId, projectWorktree, previewMessageId) {
|
|
23
|
+
return {
|
|
24
|
+
flow: "task",
|
|
25
|
+
stage,
|
|
26
|
+
projectId,
|
|
27
|
+
projectWorktree,
|
|
28
|
+
previewMessageId,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isTaskInteraction(state) {
|
|
32
|
+
return state?.kind === "task";
|
|
33
|
+
}
|
|
34
|
+
function clearTaskInteraction(reason) {
|
|
35
|
+
const state = interactionManager.getSnapshot();
|
|
36
|
+
if (state?.kind === "task") {
|
|
37
|
+
interactionManager.clear(reason);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function clearTaskFlow(reason) {
|
|
41
|
+
taskCreationManager.clear();
|
|
42
|
+
clearTaskInteraction(reason);
|
|
43
|
+
}
|
|
44
|
+
function isTaskCallbackActive(flowState, messageId) {
|
|
45
|
+
return [
|
|
46
|
+
flowState.scheduleRequestMessageId,
|
|
47
|
+
flowState.previewMessageId,
|
|
48
|
+
flowState.promptRequestMessageId,
|
|
49
|
+
].includes(messageId);
|
|
50
|
+
}
|
|
21
51
|
function parseTaskListMetadata(state) {
|
|
22
52
|
if (!state || state.kind !== "custom") {
|
|
23
53
|
return null;
|
|
@@ -55,12 +85,6 @@ function clearTaskListInteraction(reason) {
|
|
|
55
85
|
interactionManager.clear(reason);
|
|
56
86
|
}
|
|
57
87
|
}
|
|
58
|
-
function truncateText(text, maxLength) {
|
|
59
|
-
if (text.length <= maxLength) {
|
|
60
|
-
return text;
|
|
61
|
-
}
|
|
62
|
-
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
63
|
-
}
|
|
64
88
|
function formatDateTime(dateIso, timezone) {
|
|
65
89
|
if (!dateIso) {
|
|
66
90
|
return "-";
|
|
@@ -76,37 +100,6 @@ function formatDateTime(dateIso, timezone) {
|
|
|
76
100
|
return dateIso;
|
|
77
101
|
}
|
|
78
102
|
}
|
|
79
|
-
function formatTaskButtonPrefix(task) {
|
|
80
|
-
return formatTaskListBadge(task);
|
|
81
|
-
}
|
|
82
|
-
function formatTaskButtonLabel(task) {
|
|
83
|
-
const prefix = `[${formatTaskButtonPrefix(task)}]`;
|
|
84
|
-
const prompt = task.prompt.replace(/\s+/g, " ").trim();
|
|
85
|
-
return truncateText(`${prefix} ${prompt}`, MAX_INLINE_BUTTON_LABEL_LENGTH);
|
|
86
|
-
}
|
|
87
|
-
function buildTaskListKeyboard(tasks) {
|
|
88
|
-
const keyboard = new InlineKeyboard();
|
|
89
|
-
tasks.forEach((task) => {
|
|
90
|
-
keyboard.text(formatTaskButtonLabel(task), `${TASKLIST_OPEN_PREFIX}${task.id}`).row();
|
|
91
|
-
});
|
|
92
|
-
keyboard.text(t("tasklist.button.cancel"), TASKLIST_CANCEL_CALLBACK);
|
|
93
|
-
return keyboard;
|
|
94
|
-
}
|
|
95
|
-
function buildTaskDetailsKeyboard(taskId) {
|
|
96
|
-
return new InlineKeyboard()
|
|
97
|
-
.text(t("tasklist.button.delete"), `${TASKLIST_DELETE_PREFIX}${taskId}`)
|
|
98
|
-
.text(t("tasklist.button.cancel"), TASKLIST_CANCEL_CALLBACK);
|
|
99
|
-
}
|
|
100
|
-
function sortTasks(tasks) {
|
|
101
|
-
return [...tasks].sort((left, right) => {
|
|
102
|
-
const leftNextRun = left.nextRunAt ? Date.parse(left.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
103
|
-
const rightNextRun = right.nextRunAt ? Date.parse(right.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
104
|
-
if (leftNextRun !== rightNextRun) {
|
|
105
|
-
return leftNextRun - rightNextRun;
|
|
106
|
-
}
|
|
107
|
-
return left.createdAt.localeCompare(right.createdAt);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
103
|
function formatTaskDetails(task) {
|
|
111
104
|
const variant = task.model.variant ? ` (${task.model.variant})` : "";
|
|
112
105
|
const model = `${task.model.providerID}/${task.model.modelID}${variant}`;
|
|
@@ -122,30 +115,52 @@ function formatTaskDetails(task) {
|
|
|
122
115
|
runCount: String(task.runCount),
|
|
123
116
|
});
|
|
124
117
|
}
|
|
125
|
-
export async function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
export async function handleTaskCallback(ctx) {
|
|
119
|
+
const data = ctx.callbackQuery?.data;
|
|
120
|
+
if (data !== TASK_RETRY_SCHEDULE_CALLBACK && data !== TASK_CANCEL_CALLBACK) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const flowState = taskCreationManager.getState();
|
|
124
|
+
const interactionState = interactionManager.getSnapshot();
|
|
125
|
+
const callbackMessageId = getCallbackMessageId(ctx);
|
|
126
|
+
if (!flowState ||
|
|
127
|
+
!isTaskInteraction(interactionState) ||
|
|
128
|
+
callbackMessageId === null ||
|
|
129
|
+
!isTaskCallbackActive(flowState, callbackMessageId)) {
|
|
130
|
+
if (!flowState && isTaskInteraction(interactionState)) {
|
|
131
|
+
clearTaskInteraction("task_retry_inactive_state");
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
interactionManager.start({
|
|
136
|
-
kind: "custom",
|
|
137
|
-
expectedInput: "callback",
|
|
138
|
-
metadata: {
|
|
139
|
-
flow: "tasklist",
|
|
140
|
-
stage: "list",
|
|
141
|
-
messageId: message.message_id,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
133
|
+
await ctx.answerCallbackQuery({ text: t("task.inactive_callback"), show_alert: true });
|
|
134
|
+
return true;
|
|
144
135
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await ctx.
|
|
136
|
+
if (data === TASK_CANCEL_CALLBACK) {
|
|
137
|
+
await ctx.answerCallbackQuery({ text: t("task.cancel_callback") });
|
|
138
|
+
await deleteMessageIfPresent(ctx, flowState.scheduleRequestMessageId);
|
|
139
|
+
await deleteMessageIfPresent(ctx, flowState.previewMessageId);
|
|
140
|
+
await deleteMessageIfPresent(ctx, flowState.promptRequestMessageId);
|
|
141
|
+
clearTaskFlow("task_cancelled");
|
|
142
|
+
await ctx.reply(t("task.cancelled"));
|
|
143
|
+
return true;
|
|
148
144
|
}
|
|
145
|
+
if (!taskCreationManager.isWaitingForPrompt() ||
|
|
146
|
+
callbackMessageId !== flowState.previewMessageId) {
|
|
147
|
+
await ctx.answerCallbackQuery({ text: t("task.inactive_callback"), show_alert: true });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
taskCreationManager.resetSchedule();
|
|
151
|
+
interactionManager.transition({
|
|
152
|
+
kind: "task",
|
|
153
|
+
expectedInput: "text",
|
|
154
|
+
metadata: buildTaskInteractionMetadata("awaiting_schedule", flowState.projectId, flowState.projectWorktree),
|
|
155
|
+
});
|
|
156
|
+
await ctx.answerCallbackQuery({ text: t("task.retry_schedule_callback") });
|
|
157
|
+
await deleteMessageIfPresent(ctx, flowState.promptRequestMessageId);
|
|
158
|
+
await deleteMessageIfPresent(ctx, flowState.previewMessageId);
|
|
159
|
+
const message = await ctx.reply(t("task.prompt.schedule"), {
|
|
160
|
+
reply_markup: buildCancelKeyboard(),
|
|
161
|
+
});
|
|
162
|
+
taskCreationManager.setScheduleRequestMessageId(message.message_id);
|
|
163
|
+
return true;
|
|
149
164
|
}
|
|
150
165
|
export async function handleTaskListCallback(ctx) {
|
|
151
166
|
const data = ctx.callbackQuery?.data;
|