@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,115 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
3
|
+
import { getDateLocale, t } from "../../i18n/index.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
export const SESSION_CALLBACK_PREFIX = "session:";
|
|
6
|
+
const SESSION_PAGE_CALLBACK_PREFIX = "session:page:";
|
|
7
|
+
const BACKGROUND_SESSION_CALLBACK_PREFIX = "background-session:";
|
|
8
|
+
const SESSION_FETCH_EXTRA_COUNT = 1;
|
|
9
|
+
const BACKGROUND_SESSION_KIND_CALLBACK_MARKERS = {
|
|
10
|
+
assistant_response: "a",
|
|
11
|
+
question_asked: "q",
|
|
12
|
+
permission_asked: "p",
|
|
13
|
+
};
|
|
14
|
+
const BACKGROUND_SESSION_KIND_BY_CALLBACK_MARKER = {
|
|
15
|
+
a: "assistant_response",
|
|
16
|
+
q: "question_asked",
|
|
17
|
+
p: "permission_asked",
|
|
18
|
+
};
|
|
19
|
+
function buildSessionPageCallback(page) {
|
|
20
|
+
return `${SESSION_PAGE_CALLBACK_PREFIX}${page}`;
|
|
21
|
+
}
|
|
22
|
+
export function parseSessionPageCallback(data) {
|
|
23
|
+
if (!data.startsWith(SESSION_PAGE_CALLBACK_PREFIX)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const rawPage = data.slice(SESSION_PAGE_CALLBACK_PREFIX.length);
|
|
27
|
+
const page = Number(rawPage);
|
|
28
|
+
if (!Number.isInteger(page) || page < 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return page;
|
|
32
|
+
}
|
|
33
|
+
export function parseSessionIdCallback(data) {
|
|
34
|
+
if (!data.startsWith(SESSION_CALLBACK_PREFIX)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (data.startsWith(SESSION_PAGE_CALLBACK_PREFIX)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const sessionId = data.slice(SESSION_CALLBACK_PREFIX.length);
|
|
41
|
+
return sessionId.length > 0 ? sessionId : null;
|
|
42
|
+
}
|
|
43
|
+
export function parseBackgroundSessionCallback(data) {
|
|
44
|
+
if (!data.startsWith(BACKGROUND_SESSION_CALLBACK_PREFIX)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const payload = data.slice(BACKGROUND_SESSION_CALLBACK_PREFIX.length);
|
|
48
|
+
const markerSeparatorIndex = payload.indexOf(":");
|
|
49
|
+
if (markerSeparatorIndex < 0) {
|
|
50
|
+
return payload.length > 0 ? { sessionId: payload, kind: null } : null;
|
|
51
|
+
}
|
|
52
|
+
const marker = payload.slice(0, markerSeparatorIndex);
|
|
53
|
+
const sessionId = payload.slice(markerSeparatorIndex + 1);
|
|
54
|
+
const kind = BACKGROUND_SESSION_KIND_BY_CALLBACK_MARKER[marker];
|
|
55
|
+
if (!kind || sessionId.length === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return { sessionId, kind };
|
|
59
|
+
}
|
|
60
|
+
export function buildBackgroundSessionOpenKeyboard(sessionId, kind) {
|
|
61
|
+
const marker = BACKGROUND_SESSION_KIND_CALLBACK_MARKERS[kind];
|
|
62
|
+
return new InlineKeyboard().text(t("background.open_session_button"), `${BACKGROUND_SESSION_CALLBACK_PREFIX}${marker}:${sessionId}`);
|
|
63
|
+
}
|
|
64
|
+
function formatSessionsSelectText(page) {
|
|
65
|
+
if (page === 0) {
|
|
66
|
+
return t("sessions.select");
|
|
67
|
+
}
|
|
68
|
+
return t("sessions.select_page", { page: page + 1 });
|
|
69
|
+
}
|
|
70
|
+
export async function loadSessionPage(directory, page, pageSize) {
|
|
71
|
+
const startIndex = page * pageSize;
|
|
72
|
+
const endExclusive = startIndex + pageSize;
|
|
73
|
+
const { data: sessions, error } = await opencodeClient.session.list({
|
|
74
|
+
directory,
|
|
75
|
+
limit: endExclusive + SESSION_FETCH_EXTRA_COUNT,
|
|
76
|
+
roots: true,
|
|
77
|
+
});
|
|
78
|
+
if (error || !sessions) {
|
|
79
|
+
throw error || new Error("No data received from server");
|
|
80
|
+
}
|
|
81
|
+
const hasNext = sessions.length > endExclusive;
|
|
82
|
+
const pagedSessions = sessions.slice(startIndex, endExclusive);
|
|
83
|
+
logger.debug(`[Sessions] Loaded page=${page + 1}, startIndex=${startIndex}, endExclusive=${endExclusive}, pageSize=${pageSize}, items=${pagedSessions.length}, hasNext=${hasNext}`);
|
|
84
|
+
return {
|
|
85
|
+
sessions: pagedSessions,
|
|
86
|
+
hasNext,
|
|
87
|
+
page,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function buildSessionsKeyboard(pageData, pageSize) {
|
|
91
|
+
const keyboard = new InlineKeyboard();
|
|
92
|
+
const localeForDate = getDateLocale();
|
|
93
|
+
const pageStartIndex = pageData.page * pageSize;
|
|
94
|
+
pageData.sessions.forEach((session, index) => {
|
|
95
|
+
const date = new Date(session.time.created).toLocaleDateString(localeForDate);
|
|
96
|
+
const label = `${pageStartIndex + index + 1}. ${session.title} (${date})`;
|
|
97
|
+
keyboard.text(label, `${SESSION_CALLBACK_PREFIX}${session.id}`).row();
|
|
98
|
+
});
|
|
99
|
+
if (pageData.page > 0) {
|
|
100
|
+
keyboard.text(t("sessions.button.prev_page"), buildSessionPageCallback(pageData.page - 1));
|
|
101
|
+
}
|
|
102
|
+
if (pageData.hasNext) {
|
|
103
|
+
keyboard.text(t("sessions.button.next_page"), buildSessionPageCallback(pageData.page + 1));
|
|
104
|
+
}
|
|
105
|
+
if (pageData.page > 0 || pageData.hasNext) {
|
|
106
|
+
keyboard.row();
|
|
107
|
+
}
|
|
108
|
+
return keyboard;
|
|
109
|
+
}
|
|
110
|
+
export function buildSessionSelectionMenuView(pageData, pageSize) {
|
|
111
|
+
return {
|
|
112
|
+
text: formatSessionsSelectText(pageData.page),
|
|
113
|
+
keyboard: buildSessionsKeyboard(pageData, pageSize),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { t } from "../../i18n/index.js";
|
|
3
|
+
export const SKILLS_CALLBACK_PREFIX = "skills:";
|
|
4
|
+
export const SKILLS_CALLBACK_SELECT_PREFIX = `${SKILLS_CALLBACK_PREFIX}select:`;
|
|
5
|
+
const SKILLS_CALLBACK_PAGE_PREFIX = `${SKILLS_CALLBACK_PREFIX}page:`;
|
|
6
|
+
export const SKILLS_CALLBACK_CANCEL = `${SKILLS_CALLBACK_PREFIX}cancel`;
|
|
7
|
+
export const SKILLS_CALLBACK_EXECUTE = `${SKILLS_CALLBACK_PREFIX}execute`;
|
|
8
|
+
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
9
|
+
export function formatExecutingSkillMessage(skillName, args) {
|
|
10
|
+
const prefix = t("skills.executing_prefix");
|
|
11
|
+
const skillText = `/${skillName}`;
|
|
12
|
+
const argsSuffix = args ? ` ${args}` : "";
|
|
13
|
+
return {
|
|
14
|
+
text: `${prefix}\n${skillText}${argsSuffix}`,
|
|
15
|
+
entities: [
|
|
16
|
+
{
|
|
17
|
+
type: "code",
|
|
18
|
+
offset: prefix.length + 1,
|
|
19
|
+
length: skillText.length,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function buildSkillPageCallback(page) {
|
|
25
|
+
return `${SKILLS_CALLBACK_PAGE_PREFIX}${page}`;
|
|
26
|
+
}
|
|
27
|
+
export function parseSkillPageCallback(data) {
|
|
28
|
+
if (!data.startsWith(SKILLS_CALLBACK_PAGE_PREFIX)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const rawPage = data.slice(SKILLS_CALLBACK_PAGE_PREFIX.length);
|
|
32
|
+
const page = Number(rawPage);
|
|
33
|
+
if (!Number.isInteger(page) || page < 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return page;
|
|
37
|
+
}
|
|
38
|
+
export function parseSkillSelectCallback(data) {
|
|
39
|
+
if (!data.startsWith(SKILLS_CALLBACK_SELECT_PREFIX)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const rawIndex = data.slice(SKILLS_CALLBACK_SELECT_PREFIX.length);
|
|
43
|
+
const index = Number(rawIndex);
|
|
44
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return index;
|
|
48
|
+
}
|
|
49
|
+
export function formatSkillsSelectText(page) {
|
|
50
|
+
if (page === 0) {
|
|
51
|
+
return t("skills.select");
|
|
52
|
+
}
|
|
53
|
+
return t("skills.select_page", { page: page + 1 });
|
|
54
|
+
}
|
|
55
|
+
function formatSkillButtonLabel(skill) {
|
|
56
|
+
const description = skill.description?.trim() || t("skills.no_description");
|
|
57
|
+
const rawLabel = `/${skill.name} - ${description}`;
|
|
58
|
+
if (rawLabel.length <= MAX_INLINE_BUTTON_LABEL_LENGTH) {
|
|
59
|
+
return rawLabel;
|
|
60
|
+
}
|
|
61
|
+
return `${rawLabel.slice(0, MAX_INLINE_BUTTON_LABEL_LENGTH - 3)}...`;
|
|
62
|
+
}
|
|
63
|
+
export function calculateSkillsPaginationRange(totalSkills, page, pageSize) {
|
|
64
|
+
const safePageSize = Math.max(1, pageSize);
|
|
65
|
+
const totalPages = Math.max(1, Math.ceil(totalSkills / safePageSize));
|
|
66
|
+
const normalizedPage = Math.min(Math.max(0, page), totalPages - 1);
|
|
67
|
+
const startIndex = normalizedPage * safePageSize;
|
|
68
|
+
const endIndex = Math.min(startIndex + safePageSize, totalSkills);
|
|
69
|
+
return {
|
|
70
|
+
page: normalizedPage,
|
|
71
|
+
totalPages,
|
|
72
|
+
startIndex,
|
|
73
|
+
endIndex,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function buildSkillsListKeyboard(skills, page, pageSize) {
|
|
77
|
+
const keyboard = new InlineKeyboard();
|
|
78
|
+
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateSkillsPaginationRange(skills.length, page, pageSize);
|
|
79
|
+
skills.slice(startIndex, endIndex).forEach((skill, index) => {
|
|
80
|
+
const globalIndex = startIndex + index;
|
|
81
|
+
keyboard.text(formatSkillButtonLabel(skill), `${SKILLS_CALLBACK_SELECT_PREFIX}${globalIndex}`).row();
|
|
82
|
+
});
|
|
83
|
+
if (totalPages > 1) {
|
|
84
|
+
if (normalizedPage > 0) {
|
|
85
|
+
keyboard.text(t("skills.button.prev_page"), buildSkillPageCallback(normalizedPage - 1));
|
|
86
|
+
}
|
|
87
|
+
if (normalizedPage < totalPages - 1) {
|
|
88
|
+
keyboard.text(t("skills.button.next_page"), buildSkillPageCallback(normalizedPage + 1));
|
|
89
|
+
}
|
|
90
|
+
keyboard.row();
|
|
91
|
+
}
|
|
92
|
+
keyboard.text(t("skills.button.cancel"), SKILLS_CALLBACK_CANCEL);
|
|
93
|
+
return keyboard;
|
|
94
|
+
}
|
|
95
|
+
export function buildSkillsConfirmKeyboard() {
|
|
96
|
+
return new InlineKeyboard()
|
|
97
|
+
.text(t("skills.button.execute"), SKILLS_CALLBACK_EXECUTE)
|
|
98
|
+
.text(t("skills.button.cancel"), SKILLS_CALLBACK_CANCEL);
|
|
99
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { getStoredModel } from "../../app/services/model-selection-service.js";
|
|
3
|
+
import { formatVariantForDisplay, getAvailableVariants, getCurrentVariant, } from "../../app/services/variant-selection-service.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
6
|
+
import { replyWithInlineMenu } from "./inline-menu.js";
|
|
7
|
+
/**
|
|
8
|
+
* Build inline keyboard with available variants
|
|
9
|
+
* @param currentVariant Current variant for highlighting
|
|
10
|
+
* @param providerID Provider ID
|
|
11
|
+
* @param modelID Model ID
|
|
12
|
+
* @returns InlineKeyboard with variant selection buttons
|
|
13
|
+
*/
|
|
14
|
+
export async function buildVariantSelectionMenu(currentVariant, providerID, modelID) {
|
|
15
|
+
const keyboard = new InlineKeyboard();
|
|
16
|
+
const variants = await getAvailableVariants(providerID, modelID);
|
|
17
|
+
if (variants.length === 0) {
|
|
18
|
+
logger.warn("[VariantHandler] No variants found");
|
|
19
|
+
return keyboard;
|
|
20
|
+
}
|
|
21
|
+
// Filter only active variants (not disabled)
|
|
22
|
+
const activeVariants = variants.filter((v) => !v.disabled);
|
|
23
|
+
if (activeVariants.length === 0) {
|
|
24
|
+
logger.warn("[VariantHandler] No active variants found");
|
|
25
|
+
// If no active variants, show default at least
|
|
26
|
+
keyboard.text(`✅ ${formatVariantForDisplay("default")}`, "variant:default").row();
|
|
27
|
+
return keyboard;
|
|
28
|
+
}
|
|
29
|
+
// Add button for each variant (one per row)
|
|
30
|
+
activeVariants.forEach((variant) => {
|
|
31
|
+
const isActive = variant.id === currentVariant;
|
|
32
|
+
const label = formatVariantForDisplay(variant.id);
|
|
33
|
+
const labelWithCheck = isActive ? `✅ ${label}` : label;
|
|
34
|
+
keyboard.text(labelWithCheck, `variant:${variant.id}`).row();
|
|
35
|
+
});
|
|
36
|
+
return keyboard;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Show variant selection menu
|
|
40
|
+
* @param ctx grammY context
|
|
41
|
+
*/
|
|
42
|
+
export async function showVariantSelectionMenu(ctx) {
|
|
43
|
+
try {
|
|
44
|
+
const currentModel = getStoredModel();
|
|
45
|
+
if (!currentModel.providerID || !currentModel.modelID) {
|
|
46
|
+
await ctx.reply(t("variant.select_model_first"));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const currentVariant = getCurrentVariant();
|
|
50
|
+
const keyboard = await buildVariantSelectionMenu(currentVariant, currentModel.providerID, currentModel.modelID);
|
|
51
|
+
if (keyboard.inline_keyboard.length === 0) {
|
|
52
|
+
await ctx.reply(t("variant.menu.empty"));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const displayName = formatVariantForDisplay(currentVariant);
|
|
56
|
+
const text = t("variant.menu.current", { name: displayName });
|
|
57
|
+
await replyWithInlineMenu(ctx, {
|
|
58
|
+
menuKind: "variant",
|
|
59
|
+
text,
|
|
60
|
+
keyboard,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
logger.error("[VariantHandler] Error showing variant menu:", err);
|
|
65
|
+
await ctx.reply(t("variant.menu.error"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { config } from "../../config.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
4
|
+
import { buildProjectButtonLabel, calculateProjectsPaginationRange, } from "./project-selection-menu.js";
|
|
5
|
+
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
6
|
+
export const WORKTREE_CALLBACK_PREFIX = "worktree:";
|
|
7
|
+
const WORKTREE_PAGE_CALLBACK_PREFIX = "worktree:page:";
|
|
8
|
+
function formatWorktreeButtonLabel(label, isActive) {
|
|
9
|
+
const prefix = isActive ? "✅ " : "";
|
|
10
|
+
const availableLength = MAX_INLINE_BUTTON_LABEL_LENGTH - prefix.length;
|
|
11
|
+
if (label.length <= availableLength) {
|
|
12
|
+
return `${prefix}${label}`;
|
|
13
|
+
}
|
|
14
|
+
return `${prefix}${label.slice(0, Math.max(0, availableLength - 3))}...`;
|
|
15
|
+
}
|
|
16
|
+
function buildWorktreeButtonLabel(index, entry) {
|
|
17
|
+
return buildProjectButtonLabel(index, entry.path);
|
|
18
|
+
}
|
|
19
|
+
export function parseWorktreePageCallback(data) {
|
|
20
|
+
if (!data.startsWith(WORKTREE_PAGE_CALLBACK_PREFIX)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const rawPage = data.slice(WORKTREE_PAGE_CALLBACK_PREFIX.length);
|
|
24
|
+
if (!/^\d+$/.test(rawPage)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return Number.parseInt(rawPage, 10);
|
|
28
|
+
}
|
|
29
|
+
export function parseWorktreeIndexCallback(data) {
|
|
30
|
+
if (!data.startsWith(WORKTREE_CALLBACK_PREFIX) ||
|
|
31
|
+
data.startsWith(WORKTREE_PAGE_CALLBACK_PREFIX)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const rawIndex = data.slice(WORKTREE_CALLBACK_PREFIX.length);
|
|
35
|
+
if (!/^\d+$/.test(rawIndex)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return Number.parseInt(rawIndex, 10);
|
|
39
|
+
}
|
|
40
|
+
function buildWorktreeMenuText(page, totalPages) {
|
|
41
|
+
const baseText = t("worktree.select_with_current");
|
|
42
|
+
if (totalPages <= 1) {
|
|
43
|
+
return baseText;
|
|
44
|
+
}
|
|
45
|
+
return `${baseText}\n\n${t("projects.page_indicator", {
|
|
46
|
+
current: String(page + 1),
|
|
47
|
+
total: String(totalPages),
|
|
48
|
+
})}`;
|
|
49
|
+
}
|
|
50
|
+
function buildWorktreeKeyboard(worktrees, page) {
|
|
51
|
+
const keyboard = new InlineKeyboard();
|
|
52
|
+
const pageSize = config.bot.projectsListLimit;
|
|
53
|
+
const { page: normalizedPage, totalPages, startIndex, endIndex, } = calculateProjectsPaginationRange(worktrees.length, page, pageSize);
|
|
54
|
+
worktrees.slice(startIndex, endIndex).forEach((entry, index) => {
|
|
55
|
+
const label = buildWorktreeButtonLabel(startIndex + index, entry);
|
|
56
|
+
keyboard
|
|
57
|
+
.text(formatWorktreeButtonLabel(label, entry.isCurrent), `${WORKTREE_CALLBACK_PREFIX}${startIndex + index}`)
|
|
58
|
+
.row();
|
|
59
|
+
});
|
|
60
|
+
if (totalPages > 1) {
|
|
61
|
+
if (normalizedPage > 0) {
|
|
62
|
+
keyboard.text(t("projects.prev_page"), `${WORKTREE_PAGE_CALLBACK_PREFIX}${normalizedPage - 1}`);
|
|
63
|
+
}
|
|
64
|
+
if (normalizedPage < totalPages - 1) {
|
|
65
|
+
keyboard.text(t("projects.next_page"), `${WORKTREE_PAGE_CALLBACK_PREFIX}${normalizedPage + 1}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return keyboard;
|
|
69
|
+
}
|
|
70
|
+
export function buildWorktreeMenuView(worktrees, page) {
|
|
71
|
+
const { page: normalizedPage, totalPages } = calculateProjectsPaginationRange(worktrees.length, page, config.bot.projectsListLimit);
|
|
72
|
+
return {
|
|
73
|
+
text: buildWorktreeMenuText(normalizedPage, totalPages),
|
|
74
|
+
keyboard: buildWorktreeKeyboard(worktrees, normalizedPage),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { config } from "../../config.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
-
import { chunkTelegramRenderedBlocks } from "
|
|
4
|
-
import { renderTelegramBlocks, renderTelegramParts } from "
|
|
3
|
+
import { chunkTelegramRenderedBlocks } from "../render/chunker.js";
|
|
4
|
+
import { renderTelegramBlocks, renderTelegramParts } from "../render/pipeline.js";
|
|
5
5
|
export function createPlainRenderedBlock(text) {
|
|
6
6
|
return {
|
|
7
7
|
blockType: "plain",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
2
|
+
export async function replyBusyBlocked(ctx) {
|
|
3
|
+
const message = t("bot.session_busy");
|
|
4
|
+
if (ctx.callbackQuery) {
|
|
5
|
+
await ctx.answerCallbackQuery({ text: message }).catch(() => { });
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (ctx.chat) {
|
|
9
|
+
await ctx.reply(message).catch(() => { });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { buildExternalUserInputNotification, } from "../../app/services/external-user-input-service.js";
|
|
2
|
+
import { sendBotText } from "./telegram-text.js";
|
|
3
|
+
async function sendExternalUserInputNotification(api, chatId, notification) {
|
|
4
|
+
await sendBotText({
|
|
5
|
+
api,
|
|
6
|
+
chatId,
|
|
7
|
+
text: notification.text,
|
|
8
|
+
rawFallbackText: notification.rawFallbackText,
|
|
9
|
+
format: "markdown_v2",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export async function deliverExternalUserInputNotification({ api, chatId, currentSessionId, sessionId, text, consumeSuppressedInput, }) {
|
|
13
|
+
const notification = buildExternalUserInputNotification(text);
|
|
14
|
+
if (!notification || currentSessionId !== sessionId) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (consumeSuppressedInput(sessionId, text)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
await sendExternalUserInputNotification(api, chatId, notification);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { escapePlainTextForTelegramMarkdownV2, formatSummaryWithMode, } from "./summary-message-formatter.js";
|
|
3
|
+
import { sendBotText } from "./telegram-text.js";
|
|
4
|
+
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
5
|
+
function getScheduledTaskDeliveryFormat() {
|
|
6
|
+
return config.bot.messageFormatMode === "markdown" ? "markdown_v2" : "raw";
|
|
7
|
+
}
|
|
8
|
+
function getSilentDeliveryOptions() {
|
|
9
|
+
return config.bot.scheduledTaskNotificationsSilent
|
|
10
|
+
? { options: { disable_notification: true } }
|
|
11
|
+
: {};
|
|
12
|
+
}
|
|
13
|
+
function buildScheduledTaskSuccessMessageParts(delivery) {
|
|
14
|
+
if (!delivery.resultText) {
|
|
15
|
+
return [delivery.notificationText];
|
|
16
|
+
}
|
|
17
|
+
if (config.bot.messageFormatMode !== "markdown") {
|
|
18
|
+
return formatSummaryWithMode(`${delivery.notificationText}\n\n${delivery.resultText}`, config.bot.messageFormatMode);
|
|
19
|
+
}
|
|
20
|
+
const header = escapePlainTextForTelegramMarkdownV2(delivery.notificationText);
|
|
21
|
+
const resultParts = formatSummaryWithMode(delivery.resultText, config.bot.messageFormatMode);
|
|
22
|
+
if (resultParts.length === 0) {
|
|
23
|
+
return [header];
|
|
24
|
+
}
|
|
25
|
+
const firstPart = `${header}\n\n${resultParts[0]}`;
|
|
26
|
+
if (firstPart.length <= TELEGRAM_MESSAGE_LIMIT) {
|
|
27
|
+
return [firstPart, ...resultParts.slice(1)];
|
|
28
|
+
}
|
|
29
|
+
return [header, ...resultParts];
|
|
30
|
+
}
|
|
31
|
+
export function createScheduledTaskDeliverySender(api, chatId) {
|
|
32
|
+
return {
|
|
33
|
+
async send(delivery) {
|
|
34
|
+
const messageParts = delivery.status === "success"
|
|
35
|
+
? buildScheduledTaskSuccessMessageParts(delivery)
|
|
36
|
+
: [delivery.notificationText];
|
|
37
|
+
const format = delivery.status === "success" ? getScheduledTaskDeliveryFormat() : "raw";
|
|
38
|
+
const suppressResultNotification = delivery.status === "success" && Boolean(delivery.footerText);
|
|
39
|
+
const resultDeliveryOptions = suppressResultNotification && !config.bot.scheduledTaskNotificationsSilent
|
|
40
|
+
? { options: { disable_notification: true } }
|
|
41
|
+
: getSilentDeliveryOptions();
|
|
42
|
+
for (const part of messageParts) {
|
|
43
|
+
await sendBotText({
|
|
44
|
+
api,
|
|
45
|
+
chatId,
|
|
46
|
+
text: part,
|
|
47
|
+
format,
|
|
48
|
+
...resultDeliveryOptions,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (delivery.status === "success" && delivery.footerText) {
|
|
52
|
+
await sendBotText({
|
|
53
|
+
api,
|
|
54
|
+
chatId,
|
|
55
|
+
text: delivery.footerText,
|
|
56
|
+
format: "raw",
|
|
57
|
+
...getSilentDeliveryOptions(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InputFile } from "grammy";
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { formatFileSize } from "
|
|
4
|
+
import { formatFileSize } from "../../app/services/file-download-service.js";
|
|
5
5
|
import { logger } from "../../utils/logger.js";
|
|
6
6
|
import { t } from "../../i18n/index.js";
|
|
7
7
|
const MAX_FILE_SIZE_MB = 50;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { normalizeMarkdownForTelegramRendering } from "../render/markdown-normalizer.js";
|
|
4
|
+
import { convertToTelegramMarkdownV2 } from "../render/markdown-to-telegram-v2.js";
|
|
5
|
+
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
6
|
+
const MARKDOWN_V2_RESERVED_CHARS = /([_\*\[\]\(\)~`>#+\-=|{}.!\\])/g;
|
|
7
|
+
function endsWithOddTrailingBackslashes(text, start, end) {
|
|
8
|
+
let backslashCount = 0;
|
|
9
|
+
for (let index = end - 1; index >= start; index--) {
|
|
10
|
+
if (text[index] !== "\\") {
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
backslashCount += 1;
|
|
14
|
+
}
|
|
15
|
+
return backslashCount % 2 === 1;
|
|
16
|
+
}
|
|
17
|
+
function resolveSplitEndIndex(text, currentIndex, maxLength, options) {
|
|
18
|
+
const hardLimit = Math.min(text.length, currentIndex + maxLength);
|
|
19
|
+
if (hardLimit >= text.length) {
|
|
20
|
+
return text.length;
|
|
21
|
+
}
|
|
22
|
+
let endIndex = hardLimit;
|
|
23
|
+
const breakPoint = text.lastIndexOf("\n", endIndex);
|
|
24
|
+
if (breakPoint > currentIndex) {
|
|
25
|
+
endIndex = breakPoint + 1;
|
|
26
|
+
}
|
|
27
|
+
if (!options?.avoidTrailingMarkdownEscape) {
|
|
28
|
+
return endIndex;
|
|
29
|
+
}
|
|
30
|
+
while (endIndex > currentIndex && endsWithOddTrailingBackslashes(text, currentIndex, endIndex)) {
|
|
31
|
+
endIndex -= 1;
|
|
32
|
+
}
|
|
33
|
+
return endIndex > currentIndex ? endIndex : hardLimit;
|
|
34
|
+
}
|
|
35
|
+
function splitText(text, maxLength, options) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
let currentIndex = 0;
|
|
38
|
+
while (currentIndex < text.length) {
|
|
39
|
+
const endIndex = resolveSplitEndIndex(text, currentIndex, maxLength, options);
|
|
40
|
+
if (endIndex <= currentIndex) {
|
|
41
|
+
const fallbackEnd = Math.min(text.length, currentIndex + 1);
|
|
42
|
+
parts.push(text.slice(currentIndex, fallbackEnd));
|
|
43
|
+
currentIndex = fallbackEnd;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
parts.push(text.slice(currentIndex, endIndex));
|
|
47
|
+
currentIndex = endIndex;
|
|
48
|
+
}
|
|
49
|
+
return parts;
|
|
50
|
+
}
|
|
51
|
+
export function formatSummary(text) {
|
|
52
|
+
return formatSummaryWithMode(text, config.bot.messageFormatMode);
|
|
53
|
+
}
|
|
54
|
+
export function escapePlainTextForTelegramMarkdownV2(text) {
|
|
55
|
+
return text.replace(MARKDOWN_V2_RESERVED_CHARS, "\\$1");
|
|
56
|
+
}
|
|
57
|
+
function formatMarkdownForTelegram(text) {
|
|
58
|
+
try {
|
|
59
|
+
const preprocessed = normalizeMarkdownForTelegramRendering(text);
|
|
60
|
+
return escapeMarkdownV2PipesOutsideCode(convertToTelegramMarkdownV2(preprocessed));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
logger.warn("[Formatter] Failed to convert markdown summary, falling back to raw text", error);
|
|
64
|
+
return text;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function escapeMarkdownV2PipesOutsideCode(text) {
|
|
68
|
+
let result = "";
|
|
69
|
+
let index = 0;
|
|
70
|
+
let inInlineCode = false;
|
|
71
|
+
let inCodeFence = false;
|
|
72
|
+
while (index < text.length) {
|
|
73
|
+
if (text.startsWith("```", index)) {
|
|
74
|
+
result += "```";
|
|
75
|
+
index += 3;
|
|
76
|
+
inCodeFence = !inCodeFence;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const char = text[index];
|
|
80
|
+
if (!inCodeFence && char === "`") {
|
|
81
|
+
inInlineCode = !inInlineCode;
|
|
82
|
+
result += char;
|
|
83
|
+
index += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!inCodeFence && !inInlineCode && char === "|" && text[index - 1] !== "\\") {
|
|
87
|
+
result += "\\|";
|
|
88
|
+
index += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
result += char;
|
|
92
|
+
index += 1;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
export function formatSummaryWithMode(text, mode, maxLength = TELEGRAM_MESSAGE_LIMIT) {
|
|
97
|
+
if (!text || text.trim().length === 0) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const normalizedMaxLength = Math.max(1, Math.floor(maxLength));
|
|
101
|
+
const rawTextLimit = mode === "raw" ? Math.max(1, normalizedMaxLength - "```\n\n```".length) : normalizedMaxLength;
|
|
102
|
+
const parts = splitText(text, rawTextLimit);
|
|
103
|
+
const formattedParts = [];
|
|
104
|
+
for (const part of parts) {
|
|
105
|
+
const trimmed = part.trim();
|
|
106
|
+
if (!trimmed) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (mode === "markdown") {
|
|
110
|
+
const converted = formatMarkdownForTelegram(trimmed);
|
|
111
|
+
const convertedParts = splitText(converted, normalizedMaxLength, {
|
|
112
|
+
avoidTrailingMarkdownEscape: true,
|
|
113
|
+
});
|
|
114
|
+
for (const convertedPart of convertedParts) {
|
|
115
|
+
const normalizedPart = convertedPart.trim();
|
|
116
|
+
if (normalizedPart) {
|
|
117
|
+
formattedParts.push(normalizedPart);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (parts.length > 1) {
|
|
123
|
+
formattedParts.push(`\`\`\`\n${trimmed}\n\`\`\``);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
formattedParts.push(trimmed);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return formattedParts;
|
|
130
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { interactionManager } from "
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { interactionManager } from "../../app/managers/interaction-manager.js";
|
|
2
|
+
import { foregroundSessionState } from "../../app/managers/foreground-session-state-manager.js";
|
|
3
|
+
import { attachManager } from "../../app/managers/attach-manager.js";
|
|
4
|
+
const BUSY_ALLOWED_COMMANDS = ["/abort", "/detach", "/status", "/help"];
|
|
5
|
+
const BUSY_ALLOWED_COMMAND_SET = new Set(BUSY_ALLOWED_COMMANDS);
|
|
6
|
+
function isBusyAllowedCommand(command) {
|
|
7
|
+
return Boolean(command && BUSY_ALLOWED_COMMAND_SET.has(command));
|
|
8
|
+
}
|
|
9
|
+
function allowsBusyInteraction(kind) {
|
|
10
|
+
return kind === "question" || kind === "permission";
|
|
11
|
+
}
|
|
5
12
|
function normalizeIncomingCommand(text) {
|
|
6
13
|
const trimmed = text.trim();
|
|
7
14
|
if (!trimmed.startsWith("/")) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolveInteractionGuardDecision } from "
|
|
2
|
-
import { reconcileForegroundBusyState } from "
|
|
1
|
+
import { resolveInteractionGuardDecision } from "./interaction-guard-decision.js";
|
|
2
|
+
import { reconcileForegroundBusyState } from "../../app/services/run-control-service.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
5
5
|
function getInteractionBlockedMessage(reason, interactionKind) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractCommandName, isKnownCommand } from "../utils
|
|
1
|
+
import { extractCommandName, isKnownCommand } from "../routers/command-utils.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
4
|
export async function unknownCommandMiddleware(ctx, next) {
|