remoat 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +80 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bin/commands/doctor.d.ts +1 -0
- package/dist/bin/commands/doctor.js +211 -0
- package/dist/bin/commands/doctor.js.map +1 -0
- package/dist/bin/commands/open.d.ts +1 -0
- package/dist/bin/commands/open.js +187 -0
- package/dist/bin/commands/open.js.map +1 -0
- package/dist/bin/commands/setup.d.ts +1 -0
- package/dist/bin/commands/setup.js +267 -0
- package/dist/bin/commands/setup.js.map +1 -0
- package/dist/bin/commands/start.d.ts +2 -0
- package/dist/bin/commands/start.js +39 -0
- package/dist/bin/commands/start.js.map +1 -0
- package/dist/bot/index.d.ts +2 -0
- package/dist/bot/index.js +1393 -0
- package/dist/bot/index.js.map +1 -0
- package/dist/commands/chatCommandHandler.d.ts +20 -0
- package/dist/commands/chatCommandHandler.js +30 -0
- package/dist/commands/chatCommandHandler.js.map +1 -0
- package/dist/commands/cleanupCommandHandler.d.ts +21 -0
- package/dist/commands/cleanupCommandHandler.js +40 -0
- package/dist/commands/cleanupCommandHandler.js.map +1 -0
- package/dist/commands/joinCommandHandler.d.ts +19 -0
- package/dist/commands/joinCommandHandler.js +27 -0
- package/dist/commands/joinCommandHandler.js.map +1 -0
- package/dist/commands/messageParser.d.ts +7 -0
- package/dist/commands/messageParser.js +29 -0
- package/dist/commands/messageParser.js.map +1 -0
- package/dist/commands/slashCommandHandler.d.ts +21 -0
- package/dist/commands/slashCommandHandler.js +105 -0
- package/dist/commands/slashCommandHandler.js.map +1 -0
- package/dist/commands/workspaceCommandHandler.d.ts +16 -0
- package/dist/commands/workspaceCommandHandler.js +29 -0
- package/dist/commands/workspaceCommandHandler.js.map +1 -0
- package/dist/database/chatSessionRepository.d.ts +59 -0
- package/dist/database/chatSessionRepository.js +110 -0
- package/dist/database/chatSessionRepository.js.map +1 -0
- package/dist/database/scheduleRepository.d.ts +60 -0
- package/dist/database/scheduleRepository.js +106 -0
- package/dist/database/scheduleRepository.js.map +1 -0
- package/dist/database/templateRepository.d.ts +51 -0
- package/dist/database/templateRepository.js +90 -0
- package/dist/database/templateRepository.js.map +1 -0
- package/dist/database/workspaceBindingRepository.d.ts +48 -0
- package/dist/database/workspaceBindingRepository.js +92 -0
- package/dist/database/workspaceBindingRepository.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +5 -0
- package/dist/middleware/auth.js +14 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/sanitize.d.ts +1 -0
- package/dist/middleware/sanitize.js +18 -0
- package/dist/middleware/sanitize.js.map +1 -0
- package/dist/services/antigravityLauncher.d.ts +7 -0
- package/dist/services/antigravityLauncher.js +94 -0
- package/dist/services/antigravityLauncher.js.map +1 -0
- package/dist/services/approvalDetector.d.ts +97 -0
- package/dist/services/approvalDetector.js +394 -0
- package/dist/services/approvalDetector.js.map +1 -0
- package/dist/services/assistantDomExtractor.d.ts +49 -0
- package/dist/services/assistantDomExtractor.js +340 -0
- package/dist/services/assistantDomExtractor.js.map +1 -0
- package/dist/services/autoAcceptService.d.ts +14 -0
- package/dist/services/autoAcceptService.js +81 -0
- package/dist/services/autoAcceptService.js.map +1 -0
- package/dist/services/cdpBridgeManager.d.ts +50 -0
- package/dist/services/cdpBridgeManager.js +355 -0
- package/dist/services/cdpBridgeManager.js.map +1 -0
- package/dist/services/cdpConnectionPool.d.ts +88 -0
- package/dist/services/cdpConnectionPool.js +235 -0
- package/dist/services/cdpConnectionPool.js.map +1 -0
- package/dist/services/cdpService.d.ts +214 -0
- package/dist/services/cdpService.js +1423 -0
- package/dist/services/cdpService.js.map +1 -0
- package/dist/services/chatSessionService.d.ts +89 -0
- package/dist/services/chatSessionService.js +738 -0
- package/dist/services/chatSessionService.js.map +1 -0
- package/dist/services/errorPopupDetector.d.ts +89 -0
- package/dist/services/errorPopupDetector.js +274 -0
- package/dist/services/errorPopupDetector.js.map +1 -0
- package/dist/services/modeService.d.ts +44 -0
- package/dist/services/modeService.js +74 -0
- package/dist/services/modeService.js.map +1 -0
- package/dist/services/modelService.d.ts +36 -0
- package/dist/services/modelService.js +64 -0
- package/dist/services/modelService.js.map +1 -0
- package/dist/services/planningDetector.d.ts +87 -0
- package/dist/services/planningDetector.js +321 -0
- package/dist/services/planningDetector.js.map +1 -0
- package/dist/services/processManager.d.ts +18 -0
- package/dist/services/processManager.js +62 -0
- package/dist/services/processManager.js.map +1 -0
- package/dist/services/progressSender.d.ts +20 -0
- package/dist/services/progressSender.js +65 -0
- package/dist/services/progressSender.js.map +1 -0
- package/dist/services/promptDispatcher.d.ts +38 -0
- package/dist/services/promptDispatcher.js +42 -0
- package/dist/services/promptDispatcher.js.map +1 -0
- package/dist/services/quotaService.d.ts +21 -0
- package/dist/services/quotaService.js +191 -0
- package/dist/services/quotaService.js.map +1 -0
- package/dist/services/responseMonitor.d.ts +129 -0
- package/dist/services/responseMonitor.js +996 -0
- package/dist/services/responseMonitor.js.map +1 -0
- package/dist/services/scheduleService.d.ts +58 -0
- package/dist/services/scheduleService.js +135 -0
- package/dist/services/scheduleService.js.map +1 -0
- package/dist/services/screenshotService.d.ts +55 -0
- package/dist/services/screenshotService.js +86 -0
- package/dist/services/screenshotService.js.map +1 -0
- package/dist/services/telegramTopicManager.d.ts +40 -0
- package/dist/services/telegramTopicManager.js +103 -0
- package/dist/services/telegramTopicManager.js.map +1 -0
- package/dist/services/titleGeneratorService.d.ts +32 -0
- package/dist/services/titleGeneratorService.js +114 -0
- package/dist/services/titleGeneratorService.js.map +1 -0
- package/dist/services/updateCheckService.d.ts +16 -0
- package/dist/services/updateCheckService.js +148 -0
- package/dist/services/updateCheckService.js.map +1 -0
- package/dist/services/userMessageDetector.d.ts +57 -0
- package/dist/services/userMessageDetector.js +222 -0
- package/dist/services/userMessageDetector.js.map +1 -0
- package/dist/services/workspaceService.d.ts +33 -0
- package/dist/services/workspaceService.js +65 -0
- package/dist/services/workspaceService.js.map +1 -0
- package/dist/ui/autoAcceptUi.d.ts +6 -0
- package/dist/ui/autoAcceptUi.js +22 -0
- package/dist/ui/autoAcceptUi.js.map +1 -0
- package/dist/ui/modeUi.d.ts +12 -0
- package/dist/ui/modeUi.js +40 -0
- package/dist/ui/modeUi.js.map +1 -0
- package/dist/ui/modelsUi.d.ts +12 -0
- package/dist/ui/modelsUi.js +101 -0
- package/dist/ui/modelsUi.js.map +1 -0
- package/dist/ui/projectListUi.d.ts +11 -0
- package/dist/ui/projectListUi.js +59 -0
- package/dist/ui/projectListUi.js.map +1 -0
- package/dist/ui/screenshotUi.d.ts +6 -0
- package/dist/ui/screenshotUi.js +28 -0
- package/dist/ui/screenshotUi.js.map +1 -0
- package/dist/ui/sessionPickerUi.d.ts +8 -0
- package/dist/ui/sessionPickerUi.js +32 -0
- package/dist/ui/sessionPickerUi.js.map +1 -0
- package/dist/ui/templateUi.d.ts +5 -0
- package/dist/ui/templateUi.js +44 -0
- package/dist/ui/templateUi.js.map +1 -0
- package/dist/utils/cdpPorts.d.ts +2 -0
- package/dist/utils/cdpPorts.js +6 -0
- package/dist/utils/cdpPorts.js.map +1 -0
- package/dist/utils/config.d.ts +14 -0
- package/dist/utils/config.js +12 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/configLoader.d.ts +23 -0
- package/dist/utils/configLoader.js +153 -0
- package/dist/utils/configLoader.js.map +1 -0
- package/dist/utils/htmlToTelegramMarkdown.d.ts +6 -0
- package/dist/utils/htmlToTelegramMarkdown.js +189 -0
- package/dist/utils/htmlToTelegramMarkdown.js.map +1 -0
- package/dist/utils/i18n.d.ts +3 -0
- package/dist/utils/i18n.js +78 -0
- package/dist/utils/i18n.js.map +1 -0
- package/dist/utils/imageHandler.d.ts +35 -0
- package/dist/utils/imageHandler.js +155 -0
- package/dist/utils/imageHandler.js.map +1 -0
- package/dist/utils/lockfile.d.ts +7 -0
- package/dist/utils/lockfile.js +117 -0
- package/dist/utils/lockfile.js.map +1 -0
- package/dist/utils/logger.d.ts +23 -0
- package/dist/utils/logger.js +85 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logo.d.ts +1 -0
- package/dist/utils/logo.js +14 -0
- package/dist/utils/logo.js.map +1 -0
- package/dist/utils/metadataExtractor.d.ts +5 -0
- package/dist/utils/metadataExtractor.js +16 -0
- package/dist/utils/metadataExtractor.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +23 -0
- package/dist/utils/pathUtils.js +58 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/processLogBuffer.d.ts +17 -0
- package/dist/utils/processLogBuffer.js +108 -0
- package/dist/utils/processLogBuffer.js.map +1 -0
- package/dist/utils/streamMessageFormatter.d.ts +18 -0
- package/dist/utils/streamMessageFormatter.js +91 -0
- package/dist/utils/streamMessageFormatter.js.map +1 -0
- package/dist/utils/telegramFormatter.d.ts +37 -0
- package/dist/utils/telegramFormatter.js +445 -0
- package/dist/utils/telegramFormatter.js.map +1 -0
- package/dist/utils/voiceHandler.d.ts +23 -0
- package/dist/utils/voiceHandler.js +169 -0
- package/dist/utils/voiceHandler.js.map +1 -0
- package/locales/en.json +85 -0
- package/locales/ja.json +109 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startBot = void 0;
|
|
7
|
+
const grammy_1 = require("grammy");
|
|
8
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
9
|
+
const i18n_1 = require("../utils/i18n");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
const config_1 = require("../utils/config");
|
|
12
|
+
const configLoader_1 = require("../utils/configLoader");
|
|
13
|
+
const messageParser_1 = require("../commands/messageParser");
|
|
14
|
+
const slashCommandHandler_1 = require("../commands/slashCommandHandler");
|
|
15
|
+
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
16
|
+
const modeService_1 = require("../services/modeService");
|
|
17
|
+
const modelService_1 = require("../services/modelService");
|
|
18
|
+
const templateRepository_1 = require("../database/templateRepository");
|
|
19
|
+
const workspaceBindingRepository_1 = require("../database/workspaceBindingRepository");
|
|
20
|
+
const chatSessionRepository_1 = require("../database/chatSessionRepository");
|
|
21
|
+
const workspaceService_1 = require("../services/workspaceService");
|
|
22
|
+
const telegramTopicManager_1 = require("../services/telegramTopicManager");
|
|
23
|
+
const titleGeneratorService_1 = require("../services/titleGeneratorService");
|
|
24
|
+
const chatSessionService_1 = require("../services/chatSessionService");
|
|
25
|
+
const responseMonitor_1 = require("../services/responseMonitor");
|
|
26
|
+
const antigravityLauncher_1 = require("../services/antigravityLauncher");
|
|
27
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
28
|
+
const promptDispatcher_1 = require("../services/promptDispatcher");
|
|
29
|
+
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
30
|
+
const streamMessageFormatter_1 = require("../utils/streamMessageFormatter");
|
|
31
|
+
const telegramFormatter_1 = require("../utils/telegramFormatter");
|
|
32
|
+
const processLogBuffer_1 = require("../utils/processLogBuffer");
|
|
33
|
+
const imageHandler_1 = require("../utils/imageHandler");
|
|
34
|
+
const voiceHandler_1 = require("../utils/voiceHandler");
|
|
35
|
+
const modeUi_1 = require("../ui/modeUi");
|
|
36
|
+
const modelsUi_1 = require("../ui/modelsUi");
|
|
37
|
+
const templateUi_1 = require("../ui/templateUi");
|
|
38
|
+
const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
39
|
+
const screenshotUi_1 = require("../ui/screenshotUi");
|
|
40
|
+
const projectListUi_1 = require("../ui/projectListUi");
|
|
41
|
+
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
42
|
+
const PHASE_ICONS = {
|
|
43
|
+
sending: '📡',
|
|
44
|
+
thinking: '🧠',
|
|
45
|
+
generating: '✍️',
|
|
46
|
+
complete: '✅',
|
|
47
|
+
timeout: '⏰',
|
|
48
|
+
error: '❌',
|
|
49
|
+
};
|
|
50
|
+
const MAX_OUTBOUND_GENERATED_IMAGES = 4;
|
|
51
|
+
const TELEGRAM_MSG_LIMIT = 4096;
|
|
52
|
+
const MAX_INLINE_CHUNKS = 5;
|
|
53
|
+
/** Convert Telegram HTML back to readable Markdown for .md file attachment */
|
|
54
|
+
function stripHtmlForFile(html) {
|
|
55
|
+
let text = html;
|
|
56
|
+
// Code blocks: <pre><code class="language-X">...</code></pre> → ```X\n...\n```
|
|
57
|
+
text = text.replace(/<pre>\s*<code\s+class="language-([^"]*)">([\s\S]*?)<\/code>\s*<\/pre>/gi, (_m, lang, content) => `\n\`\`\`${lang}\n${content}\n\`\`\`\n`);
|
|
58
|
+
// Code blocks: <pre>...</pre> → ```\n...\n```
|
|
59
|
+
text = text.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_m, content) => `\n\`\`\`\n${content}\n\`\`\`\n`);
|
|
60
|
+
// Inline code
|
|
61
|
+
text = text.replace(/<code>([\s\S]*?)<\/code>/gi, '`$1`');
|
|
62
|
+
// Bold
|
|
63
|
+
text = text.replace(/<b>([\s\S]*?)<\/b>/gi, '**$1**');
|
|
64
|
+
// Italic
|
|
65
|
+
text = text.replace(/<i>([\s\S]*?)<\/i>/gi, '*$1*');
|
|
66
|
+
// Strikethrough
|
|
67
|
+
text = text.replace(/<s>([\s\S]*?)<\/s>/gi, '~~$1~~');
|
|
68
|
+
// Links
|
|
69
|
+
text = text.replace(/<a\s+href="([^"]*)">([\s\S]*?)<\/a>/gi, '[$2]($1)');
|
|
70
|
+
// Blockquotes
|
|
71
|
+
text = text.replace(/<blockquote>([\s\S]*?)<\/blockquote>/gi, (_m, content) => content.split('\n').map((l) => `> ${l}`).join('\n'));
|
|
72
|
+
// Strip remaining tags
|
|
73
|
+
text = text.replace(/<[^>]+>/g, '');
|
|
74
|
+
// Decode entities
|
|
75
|
+
text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
76
|
+
// Collapse excessive newlines
|
|
77
|
+
text = text.replace(/\n{3,}/g, '\n\n').trim();
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
const userStopRequestedChannels = new Set();
|
|
81
|
+
function channelKey(ch) {
|
|
82
|
+
return ch.threadId ? `${ch.chatId}:${ch.threadId}` : String(ch.chatId);
|
|
83
|
+
}
|
|
84
|
+
function createSerialTaskQueue(queueName, traceId) {
|
|
85
|
+
let queue = Promise.resolve();
|
|
86
|
+
let taskSeq = 0;
|
|
87
|
+
return (task, label = 'queue-task') => {
|
|
88
|
+
taskSeq += 1;
|
|
89
|
+
const seq = taskSeq;
|
|
90
|
+
queue = queue.then(async () => {
|
|
91
|
+
try {
|
|
92
|
+
await task();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger_1.logger.error(`[sendQueue:${traceId}:${queueName}] error #${seq} label=${label}:`, err?.message || err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return queue;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService, modelService, inboundImages = [], options) {
|
|
102
|
+
const api = bridge.botApi;
|
|
103
|
+
const monitorTraceId = channelKey(channel);
|
|
104
|
+
const enqueueGeneral = createSerialTaskQueue('general', monitorTraceId);
|
|
105
|
+
const enqueueResponse = createSerialTaskQueue('response', monitorTraceId);
|
|
106
|
+
const enqueueActivity = createSerialTaskQueue('activity', monitorTraceId);
|
|
107
|
+
const sendMsg = async (text) => {
|
|
108
|
+
try {
|
|
109
|
+
const truncated = text.length > TELEGRAM_MSG_LIMIT ? text.slice(0, TELEGRAM_MSG_LIMIT - 20) + '\n...(truncated)' : text;
|
|
110
|
+
const msg = await api.sendMessage(channel.chatId, truncated, {
|
|
111
|
+
parse_mode: 'HTML',
|
|
112
|
+
message_thread_id: channel.threadId,
|
|
113
|
+
});
|
|
114
|
+
return msg.message_id;
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
logger_1.logger.error('[sendMsg] Failed:', e);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const editMsg = async (msgId, text) => {
|
|
122
|
+
try {
|
|
123
|
+
const truncated = text.length > TELEGRAM_MSG_LIMIT ? text.slice(0, TELEGRAM_MSG_LIMIT - 20) + '\n...(truncated)' : text;
|
|
124
|
+
await api.editMessageText(channel.chatId, msgId, truncated, { parse_mode: 'HTML' });
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
const desc = e?.description || e?.message || '';
|
|
128
|
+
if (!desc.includes('message is not modified')) {
|
|
129
|
+
logger_1.logger.error('[editMsg] Failed:', desc);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const sendEmbed = (title, description) => enqueueGeneral(async () => {
|
|
134
|
+
const text = `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${(0, telegramFormatter_1.escapeHtml)(description)}`;
|
|
135
|
+
await sendMsg(text);
|
|
136
|
+
}, 'send-embed');
|
|
137
|
+
/** Send a potentially long response, splitting into chunks and attaching a .md file if needed. */
|
|
138
|
+
const sendChunkedResponse = async (title, footer, rawBody, isAlreadyHtml) => {
|
|
139
|
+
const formattedBody = isAlreadyHtml ? rawBody : (0, telegramFormatter_1.formatForTelegram)(rawBody);
|
|
140
|
+
const fullMsg = `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${formattedBody}\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>`;
|
|
141
|
+
if (fullMsg.length <= TELEGRAM_MSG_LIMIT) {
|
|
142
|
+
await upsertLiveResponse(title, rawBody, footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml, skipTruncation: true });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const bodyChunks = (0, telegramFormatter_1.splitTelegramHtml)(formattedBody, TELEGRAM_MSG_LIMIT - 200);
|
|
146
|
+
const inlineCount = Math.min(bodyChunks.length, MAX_INLINE_CHUNKS);
|
|
147
|
+
const hasFile = bodyChunks.length > MAX_INLINE_CHUNKS;
|
|
148
|
+
const total = hasFile ? inlineCount : bodyChunks.length;
|
|
149
|
+
for (let pi = 0; pi < inlineCount; pi++) {
|
|
150
|
+
const partLabel = hasFile ? `(${pi + 1}/${inlineCount}+file)` : `(${pi + 1}/${total})`;
|
|
151
|
+
if (pi === 0) {
|
|
152
|
+
await upsertLiveResponse(`${title} ${partLabel}`, bodyChunks[pi], footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml: true, skipTruncation: true });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
await sendMsg(`${bodyChunks[pi]}\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)} ${partLabel}</i>`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (hasFile) {
|
|
159
|
+
try {
|
|
160
|
+
const fileContent = stripHtmlForFile(formattedBody);
|
|
161
|
+
const buf = Buffer.from(fileContent, 'utf-8');
|
|
162
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
163
|
+
await api.sendDocument(channel.chatId, new grammy_1.InputFile(buf, `response-${timestamp}.md`), {
|
|
164
|
+
caption: `📄 Full response (${rawBody.length} chars)`,
|
|
165
|
+
message_thread_id: channel.threadId,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
logger_1.logger.error('[sendPrompt] Failed to send response file:', e);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
if (!cdp.isConnected()) {
|
|
174
|
+
await sendEmbed(`${PHASE_ICONS.error} Connection Error`, `Not connected to Antigravity.\nStart with \`${(0, pathUtils_1.getAntigravityCdpHint)(9223)}\`, then send a message to auto-connect.`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const localMode = modeService.getCurrentMode();
|
|
178
|
+
const modeName = modeService_1.MODE_UI_NAMES[localMode] || localMode;
|
|
179
|
+
const currentModel = (await cdp.getCurrentModel()) || modelService.getCurrentModel();
|
|
180
|
+
await sendEmbed(`${PHASE_ICONS.sending} [${modeName} - ${currentModel}] Sending...`, (0, streamMessageFormatter_1.buildModeModelLines)(modeName, currentModel, currentModel).join('\n'));
|
|
181
|
+
let isFinalized = false;
|
|
182
|
+
let elapsedTimer = null;
|
|
183
|
+
let lastProgressText = '';
|
|
184
|
+
let lastActivityLogText = '';
|
|
185
|
+
const LIVE_RESPONSE_MAX_LEN = 3800;
|
|
186
|
+
const LIVE_ACTIVITY_MAX_LEN = 3800;
|
|
187
|
+
const processLogBuffer = new processLogBuffer_1.ProcessLogBuffer({ maxChars: LIVE_ACTIVITY_MAX_LEN, maxEntries: 120, maxEntryLength: 220 });
|
|
188
|
+
let liveResponseMsgId = null;
|
|
189
|
+
let liveActivityMsgId = null;
|
|
190
|
+
let lastLiveResponseKey = '';
|
|
191
|
+
let lastLiveActivityKey = '';
|
|
192
|
+
let liveResponseUpdateVersion = 0;
|
|
193
|
+
let liveActivityUpdateVersion = 0;
|
|
194
|
+
const ACTIVITY_PLACEHOLDER = (0, i18n_1.t)('Collecting process logs...');
|
|
195
|
+
const buildLiveResponseText = (title, rawText, footer, isAlreadyHtml = false, skipTruncation = false) => {
|
|
196
|
+
const normalized = (rawText || '').trim();
|
|
197
|
+
const body = normalized
|
|
198
|
+
? (isAlreadyHtml ? normalized : (0, telegramFormatter_1.formatForTelegram)(normalized))
|
|
199
|
+
: (0, i18n_1.t)('Waiting for output...');
|
|
200
|
+
const truncated = (!skipTruncation && body.length > LIVE_RESPONSE_MAX_LEN)
|
|
201
|
+
? '...(beginning truncated)\n' + body.slice(-LIVE_RESPONSE_MAX_LEN + 30)
|
|
202
|
+
: body;
|
|
203
|
+
return `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${truncated}\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>`;
|
|
204
|
+
};
|
|
205
|
+
const buildLiveActivityText = (title, rawText, footer) => {
|
|
206
|
+
const normalized = (rawText || '').trim();
|
|
207
|
+
const body = normalized
|
|
208
|
+
? (0, streamMessageFormatter_1.fitForSingleEmbedDescription)((0, telegramFormatter_1.formatForTelegram)(normalized), LIVE_ACTIVITY_MAX_LEN)
|
|
209
|
+
: ACTIVITY_PLACEHOLDER;
|
|
210
|
+
return `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${body}\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>`;
|
|
211
|
+
};
|
|
212
|
+
const appendProcessLogs = (text) => {
|
|
213
|
+
const normalized = (text || '').trim();
|
|
214
|
+
if (!normalized)
|
|
215
|
+
return processLogBuffer.snapshot();
|
|
216
|
+
return processLogBuffer.append(normalized);
|
|
217
|
+
};
|
|
218
|
+
const upsertLiveResponse = (title, rawText, footer, opts) => enqueueResponse(async () => {
|
|
219
|
+
if (opts?.skipWhenFinalized && isFinalized)
|
|
220
|
+
return;
|
|
221
|
+
if (opts?.expectedVersion !== undefined && opts.expectedVersion !== liveResponseUpdateVersion)
|
|
222
|
+
return;
|
|
223
|
+
const text = buildLiveResponseText(title, rawText, footer, opts?.isAlreadyHtml, opts?.skipTruncation);
|
|
224
|
+
const renderKey = `${title}|${rawText.slice(0, 200)}|${footer}`;
|
|
225
|
+
if (renderKey === lastLiveResponseKey && liveResponseMsgId)
|
|
226
|
+
return;
|
|
227
|
+
lastLiveResponseKey = renderKey;
|
|
228
|
+
if (liveResponseMsgId) {
|
|
229
|
+
await editMsg(liveResponseMsgId, text);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
liveResponseMsgId = await sendMsg(text);
|
|
233
|
+
}
|
|
234
|
+
}, 'upsert-response');
|
|
235
|
+
const upsertLiveActivity = (title, rawText, footer, opts) => enqueueActivity(async () => {
|
|
236
|
+
if (opts?.skipWhenFinalized && isFinalized)
|
|
237
|
+
return;
|
|
238
|
+
if (opts?.expectedVersion !== undefined && opts.expectedVersion !== liveActivityUpdateVersion)
|
|
239
|
+
return;
|
|
240
|
+
const text = buildLiveActivityText(title, rawText, footer);
|
|
241
|
+
const renderKey = `${title}|${rawText.slice(0, 200)}|${footer}`;
|
|
242
|
+
if (renderKey === lastLiveActivityKey && liveActivityMsgId)
|
|
243
|
+
return;
|
|
244
|
+
lastLiveActivityKey = renderKey;
|
|
245
|
+
if (liveActivityMsgId) {
|
|
246
|
+
await editMsg(liveActivityMsgId, text);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
liveActivityMsgId = await sendMsg(text);
|
|
250
|
+
}
|
|
251
|
+
}, 'upsert-activity');
|
|
252
|
+
const sendGeneratedImages = async (responseText) => {
|
|
253
|
+
const imageIntentPattern = /(image|images|png|jpg|jpeg|gif|webp|illustration|diagram|render)/i;
|
|
254
|
+
const imageUrlPattern = /https?:\/\/\S+\.(png|jpg|jpeg|gif|webp)/i;
|
|
255
|
+
if (!imageIntentPattern.test(prompt) && !responseText.includes('![') && !imageUrlPattern.test(responseText))
|
|
256
|
+
return;
|
|
257
|
+
const extracted = await cdp.extractLatestResponseImages(MAX_OUTBOUND_GENERATED_IMAGES);
|
|
258
|
+
if (extracted.length === 0)
|
|
259
|
+
return;
|
|
260
|
+
for (let i = 0; i < extracted.length; i++) {
|
|
261
|
+
const file = await (0, imageHandler_1.toTelegramInputFile)(extracted[i], i);
|
|
262
|
+
if (file) {
|
|
263
|
+
try {
|
|
264
|
+
await api.sendPhoto(channel.chatId, new grammy_1.InputFile(file.buffer, file.name), {
|
|
265
|
+
caption: `🖼️ Generated image (${i + 1}/${extracted.length})`,
|
|
266
|
+
message_thread_id: channel.threadId,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
logger_1.logger.error('[sendGeneratedImages] Failed:', e);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const tryEmergencyExtractText = async () => {
|
|
276
|
+
try {
|
|
277
|
+
const contextId = cdp.getPrimaryContextId();
|
|
278
|
+
const expression = `(() => {
|
|
279
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
280
|
+
const scope = panel || document;
|
|
281
|
+
const candidateSelectors = ['.rendered-markdown', '.leading-relaxed.select-text', '.flex.flex-col.gap-y-3', '[data-message-author-role="assistant"]', '[data-message-role="assistant"]', '[class*="assistant-message"]', '[class*="message-content"]', '[class*="markdown-body"]', '.prose'];
|
|
282
|
+
const looksLikeActivity = (text) => { const n = (text || '').trim().toLowerCase(); if (!n) return true; return /^(?:analy[sz]ing|reading|writing|running|searching|planning|thinking|processing|loading|executing|testing|debugging|analyzed|read|wrote|ran)/i.test(n) && n.length <= 220; };
|
|
283
|
+
const clean = (text) => (text || '').replace(/\\r/g, '').replace(/\\n{3,}/g, '\\n\\n').trim();
|
|
284
|
+
const candidates = []; const seen = new Set();
|
|
285
|
+
for (const selector of candidateSelectors) { const nodes = scope.querySelectorAll(selector); for (const node of nodes) { if (!node || seen.has(node)) continue; seen.add(node); candidates.push(node); } }
|
|
286
|
+
for (let i = candidates.length - 1; i >= 0; i--) { const node = candidates[i]; const text = clean(node.innerText || node.textContent || ''); if (!text || text.length < 20) continue; if (looksLikeActivity(text)) continue; if (/^(good|bad)$/i.test(text)) continue; return text; }
|
|
287
|
+
return '';
|
|
288
|
+
})()`;
|
|
289
|
+
const callParams = { expression, returnByValue: true, awaitPromise: true };
|
|
290
|
+
if (contextId !== null)
|
|
291
|
+
callParams.contextId = contextId;
|
|
292
|
+
const res = await cdp.call('Runtime.evaluate', callParams);
|
|
293
|
+
const value = res?.result?.value;
|
|
294
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return '';
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
let monitor = null;
|
|
301
|
+
try {
|
|
302
|
+
let injectResult;
|
|
303
|
+
if (inboundImages.length > 0) {
|
|
304
|
+
injectResult = await cdp.injectMessageWithImageFiles(prompt, inboundImages.map(i => i.localPath));
|
|
305
|
+
if (!injectResult.ok) {
|
|
306
|
+
await sendEmbed((0, i18n_1.t)('🖼️ Attached image fallback'), (0, i18n_1.t)('Failed to attach image directly, resending via URL reference.'));
|
|
307
|
+
injectResult = await cdp.injectMessage((0, imageHandler_1.buildPromptWithAttachmentUrls)(prompt, inboundImages));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
injectResult = await cdp.injectMessage(prompt);
|
|
312
|
+
}
|
|
313
|
+
if (!injectResult.ok) {
|
|
314
|
+
isFinalized = true;
|
|
315
|
+
await sendEmbed(`${PHASE_ICONS.error} Message Injection Failed`, `Failed to send message: ${injectResult.error}`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const startTime = Date.now();
|
|
319
|
+
await upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, '', (0, i18n_1.t)('⏱️ Elapsed: 0s | Process log'));
|
|
320
|
+
monitor = new responseMonitor_1.ResponseMonitor({
|
|
321
|
+
cdpService: cdp,
|
|
322
|
+
pollIntervalMs: 2000,
|
|
323
|
+
maxDurationMs: 1800000,
|
|
324
|
+
stopGoneConfirmCount: 3,
|
|
325
|
+
onPhaseChange: () => { },
|
|
326
|
+
onProcessLog: (logText) => {
|
|
327
|
+
if (isFinalized)
|
|
328
|
+
return;
|
|
329
|
+
if (logText && logText.trim().length > 0)
|
|
330
|
+
lastActivityLogText = appendProcessLogs(logText);
|
|
331
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
332
|
+
liveActivityUpdateVersion += 1;
|
|
333
|
+
const v = liveActivityUpdateVersion;
|
|
334
|
+
upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, lastActivityLogText || ACTIVITY_PLACEHOLDER, (0, i18n_1.t)(`⏱️ Elapsed: ${elapsed}s | Process log`), { expectedVersion: v, skipWhenFinalized: true }).catch(() => { });
|
|
335
|
+
},
|
|
336
|
+
onProgress: (text) => {
|
|
337
|
+
if (isFinalized)
|
|
338
|
+
return;
|
|
339
|
+
const isStructured = monitor?.getLastExtractionSource() === 'structured';
|
|
340
|
+
const separated = isStructured ? { output: text, logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(text);
|
|
341
|
+
if (separated.output && separated.output.trim().length > 0)
|
|
342
|
+
lastProgressText = separated.output;
|
|
343
|
+
},
|
|
344
|
+
onComplete: async (finalText, meta) => {
|
|
345
|
+
isFinalized = true;
|
|
346
|
+
if (elapsedTimer) {
|
|
347
|
+
clearInterval(elapsedTimer);
|
|
348
|
+
elapsedTimer = null;
|
|
349
|
+
}
|
|
350
|
+
const wasStoppedByUser = userStopRequestedChannels.delete(channelKey(channel));
|
|
351
|
+
if (wasStoppedByUser) {
|
|
352
|
+
logger_1.logger.info(`[sendPrompt:${monitorTraceId}] Stopped by user`);
|
|
353
|
+
await sendMsg('⏹️ Generation stopped.');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
358
|
+
const isQuotaError = monitor.getPhase() === 'quotaReached' || monitor.getQuotaDetected();
|
|
359
|
+
if (isQuotaError) {
|
|
360
|
+
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
361
|
+
liveActivityUpdateVersion += 1;
|
|
362
|
+
await upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, finalLogText || ACTIVITY_PLACEHOLDER, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Process log`), { expectedVersion: liveActivityUpdateVersion });
|
|
363
|
+
liveResponseUpdateVersion += 1;
|
|
364
|
+
await upsertLiveResponse('⚠️ Model Quota Reached', 'Model quota limit reached. Please wait or switch to a different model.', (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Quota Reached`), { expectedVersion: liveResponseUpdateVersion });
|
|
365
|
+
try {
|
|
366
|
+
const payload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
367
|
+
if (payload) {
|
|
368
|
+
await api.sendMessage(channel.chatId, payload.text, { parse_mode: 'HTML', message_thread_id: channel.threadId, reply_markup: payload.keyboard });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
372
|
+
logger_1.logger.error('[Quota] Failed to send model selection UI:', e);
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const responseText = (finalText && finalText.trim().length > 0) ? finalText : lastProgressText;
|
|
377
|
+
const emergencyText = (!responseText || responseText.trim().length === 0) ? await tryEmergencyExtractText() : '';
|
|
378
|
+
const finalResponseText = responseText && responseText.trim().length > 0 ? responseText : emergencyText;
|
|
379
|
+
const isAlreadyHtml = meta?.source === 'structured';
|
|
380
|
+
const separated = isAlreadyHtml ? { output: finalResponseText, logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(finalResponseText);
|
|
381
|
+
const finalOutputText = separated.output || finalResponseText;
|
|
382
|
+
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
383
|
+
if (finalLogText && finalLogText.trim().length > 0) {
|
|
384
|
+
logger_1.logger.divider('Process Log');
|
|
385
|
+
console.info(finalLogText);
|
|
386
|
+
}
|
|
387
|
+
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
388
|
+
logger_1.logger.divider(`Output (${finalOutputText.length} chars)`);
|
|
389
|
+
console.info(finalOutputText);
|
|
390
|
+
}
|
|
391
|
+
logger_1.logger.divider();
|
|
392
|
+
liveActivityUpdateVersion += 1;
|
|
393
|
+
await upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, finalLogText || ACTIVITY_PLACEHOLDER, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Process log`), { expectedVersion: liveActivityUpdateVersion });
|
|
394
|
+
liveResponseUpdateVersion += 1;
|
|
395
|
+
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
396
|
+
const title = `${PHASE_ICONS.complete} Final Output`;
|
|
397
|
+
const footer = (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`);
|
|
398
|
+
await sendChunkedResponse(title, footer, finalOutputText, isAlreadyHtml);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
await upsertLiveResponse(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use /screenshot to verify.'), (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`), { expectedVersion: liveResponseUpdateVersion });
|
|
402
|
+
}
|
|
403
|
+
if (options) {
|
|
404
|
+
try {
|
|
405
|
+
const sessionInfo = await options.chatSessionService.getCurrentSessionInfo(cdp);
|
|
406
|
+
if (sessionInfo && sessionInfo.hasActiveChat && sessionInfo.title && sessionInfo.title !== (0, i18n_1.t)('(Untitled)')) {
|
|
407
|
+
const session = options.chatSessionRepo.findByChannelId(channelKey(channel));
|
|
408
|
+
const projectName = session
|
|
409
|
+
? bridge.pool.extractProjectName(session.workspacePath)
|
|
410
|
+
: cdp.getCurrentWorkspaceName();
|
|
411
|
+
if (projectName) {
|
|
412
|
+
(0, cdpBridgeManager_1.registerApprovalSessionChannel)(bridge, projectName, sessionInfo.title, channel);
|
|
413
|
+
}
|
|
414
|
+
if (session && session.displayName !== sessionInfo.title) {
|
|
415
|
+
const newName = options.titleGenerator.sanitizeForChannelName(sessionInfo.title);
|
|
416
|
+
const formattedName = `${session.sessionNumber}-${newName}`;
|
|
417
|
+
const threadId = session.channelId.includes(':')
|
|
418
|
+
? Number(session.channelId.split(':')[1])
|
|
419
|
+
: undefined;
|
|
420
|
+
if (threadId) {
|
|
421
|
+
try {
|
|
422
|
+
options.topicManager.setChatId(Number(session.channelId.split(':')[0]));
|
|
423
|
+
await options.topicManager.renameTopic(threadId, formattedName);
|
|
424
|
+
}
|
|
425
|
+
catch { /* topic rename optional */ }
|
|
426
|
+
}
|
|
427
|
+
options.chatSessionRepo.updateDisplayName(channelKey(channel), sessionInfo.title);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
logger_1.logger.error('[Rename] Failed:', e);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
await sendGeneratedImages(finalOutputText || '');
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
logger_1.logger.error(`[sendPrompt:${monitorTraceId}] onComplete failed:`, error);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
onTimeout: async (lastText) => {
|
|
442
|
+
isFinalized = true;
|
|
443
|
+
if (elapsedTimer) {
|
|
444
|
+
clearInterval(elapsedTimer);
|
|
445
|
+
elapsedTimer = null;
|
|
446
|
+
}
|
|
447
|
+
userStopRequestedChannels.delete(channelKey(channel));
|
|
448
|
+
try {
|
|
449
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
450
|
+
const timeoutText = (lastText && lastText.trim().length > 0) ? lastText : lastProgressText;
|
|
451
|
+
const timeoutIsHtml = monitor.getLastExtractionSource() === 'structured';
|
|
452
|
+
const separated = timeoutIsHtml ? { output: timeoutText || '', logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(timeoutText || '');
|
|
453
|
+
const sanitizedTimeoutLogs = lastActivityLogText || processLogBuffer.snapshot();
|
|
454
|
+
const payload = separated.output && separated.output.trim().length > 0
|
|
455
|
+
? `${separated.output}\n\n[Monitor Ended] Timeout after 30 minutes.`
|
|
456
|
+
: 'Monitor ended after 30 minutes. No text was retrieved.';
|
|
457
|
+
liveResponseUpdateVersion += 1;
|
|
458
|
+
const timeoutTitle = `${PHASE_ICONS.timeout} Timeout`;
|
|
459
|
+
const timeoutFooter = `⏱️ Elapsed: ${elapsed}s | Timeout`;
|
|
460
|
+
await sendChunkedResponse(timeoutTitle, timeoutFooter, payload, timeoutIsHtml);
|
|
461
|
+
liveActivityUpdateVersion += 1;
|
|
462
|
+
await upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, sanitizedTimeoutLogs || ACTIVITY_PLACEHOLDER, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Process log`), { expectedVersion: liveActivityUpdateVersion });
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
logger_1.logger.error(`[sendPrompt:${monitorTraceId}] onTimeout failed:`, error);
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
await monitor.start();
|
|
470
|
+
elapsedTimer = setInterval(() => {
|
|
471
|
+
if (isFinalized) {
|
|
472
|
+
clearInterval(elapsedTimer);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
476
|
+
liveActivityUpdateVersion += 1;
|
|
477
|
+
const v = liveActivityUpdateVersion;
|
|
478
|
+
upsertLiveActivity(`${PHASE_ICONS.thinking} Process Log`, lastActivityLogText || ACTIVITY_PLACEHOLDER, (0, i18n_1.t)(`⏱️ Elapsed: ${elapsed}s | Process log`), { expectedVersion: v, skipWhenFinalized: true }).catch(() => { });
|
|
479
|
+
}, 5000);
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
isFinalized = true;
|
|
483
|
+
userStopRequestedChannels.delete(channelKey(channel));
|
|
484
|
+
if (elapsedTimer) {
|
|
485
|
+
clearInterval(elapsedTimer);
|
|
486
|
+
}
|
|
487
|
+
if (monitor) {
|
|
488
|
+
await monitor.stop().catch(() => { });
|
|
489
|
+
}
|
|
490
|
+
await sendEmbed(`${PHASE_ICONS.error} Error`, (0, i18n_1.t)(`Error occurred during processing: ${e.message}`));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// =============================================================================
|
|
494
|
+
// Bot main entry point
|
|
495
|
+
// =============================================================================
|
|
496
|
+
const startBot = async (cliLogLevel) => {
|
|
497
|
+
const config = (0, config_1.loadConfig)();
|
|
498
|
+
logger_1.logger.setLogLevel(cliLogLevel ?? config.logLevel);
|
|
499
|
+
const dbPath = process.env.NODE_ENV === 'test' ? ':memory:' : configLoader_1.ConfigLoader.getDefaultDbPath();
|
|
500
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
501
|
+
db.pragma('journal_mode = WAL');
|
|
502
|
+
const modeService = new modeService_1.ModeService();
|
|
503
|
+
const modelService = new modelService_1.ModelService();
|
|
504
|
+
const templateRepo = new templateRepository_1.TemplateRepository(db);
|
|
505
|
+
const workspaceBindingRepo = new workspaceBindingRepository_1.WorkspaceBindingRepository(db);
|
|
506
|
+
const chatSessionRepo = new chatSessionRepository_1.ChatSessionRepository(db);
|
|
507
|
+
const workspaceService = new workspaceService_1.WorkspaceService(config.workspaceBaseDir);
|
|
508
|
+
await (0, antigravityLauncher_1.ensureAntigravityRunning)();
|
|
509
|
+
const bridge = (0, cdpBridgeManager_1.initCdpBridge)(config.autoApproveFileEdits);
|
|
510
|
+
bridge.botToken = config.telegramBotToken;
|
|
511
|
+
const chatSessionService = new chatSessionService_1.ChatSessionService();
|
|
512
|
+
const titleGenerator = new titleGeneratorService_1.TitleGeneratorService();
|
|
513
|
+
const promptDispatcher = new promptDispatcher_1.PromptDispatcher({
|
|
514
|
+
bridge,
|
|
515
|
+
modeService,
|
|
516
|
+
modelService,
|
|
517
|
+
sendPromptImpl: sendPromptToAntigravity,
|
|
518
|
+
});
|
|
519
|
+
const slashCommandHandler = new slashCommandHandler_1.SlashCommandHandler(templateRepo);
|
|
520
|
+
const cleanupHandler = new cleanupCommandHandler_1.CleanupCommandHandler(chatSessionRepo, workspaceBindingRepo);
|
|
521
|
+
const bot = new grammy_1.Bot(config.telegramBotToken);
|
|
522
|
+
bridge.botApi = bot.api;
|
|
523
|
+
const topicManager = new telegramTopicManager_1.TelegramTopicManager(bot.api, 0);
|
|
524
|
+
// Auth middleware
|
|
525
|
+
bot.use(async (ctx, next) => {
|
|
526
|
+
const userId = String(ctx.from?.id ?? '');
|
|
527
|
+
if (!config.allowedUserIds.includes(userId)) {
|
|
528
|
+
if (ctx.callbackQuery) {
|
|
529
|
+
await ctx.answerCallbackQuery({ text: 'You do not have permission.' });
|
|
530
|
+
}
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
await next();
|
|
534
|
+
});
|
|
535
|
+
// Helper to build TelegramChannel from context
|
|
536
|
+
const getChannel = (ctx) => ({
|
|
537
|
+
chatId: ctx.chat.id,
|
|
538
|
+
threadId: ctx.message?.message_thread_id ?? undefined,
|
|
539
|
+
});
|
|
540
|
+
const getChannelFromCb = (ctx) => ({
|
|
541
|
+
chatId: ctx.chat.id,
|
|
542
|
+
threadId: ctx.callbackQuery?.message?.message_thread_id ?? undefined,
|
|
543
|
+
});
|
|
544
|
+
const resolveWorkspaceAndCdp = async (ch) => {
|
|
545
|
+
const key = channelKey(ch);
|
|
546
|
+
const binding = workspaceBindingRepo.findByChannelId(key);
|
|
547
|
+
if (!binding)
|
|
548
|
+
return null;
|
|
549
|
+
const workspacePath = workspaceService.getWorkspacePath(binding.workspacePath);
|
|
550
|
+
try {
|
|
551
|
+
const cdp = await bridge.pool.getOrConnect(workspacePath);
|
|
552
|
+
const projectName = bridge.pool.extractProjectName(workspacePath);
|
|
553
|
+
bridge.lastActiveWorkspace = projectName;
|
|
554
|
+
bridge.lastActiveChannel = ch;
|
|
555
|
+
(0, cdpBridgeManager_1.registerApprovalWorkspaceChannel)(bridge, projectName, ch);
|
|
556
|
+
(0, cdpBridgeManager_1.ensureApprovalDetector)(bridge, cdp, projectName);
|
|
557
|
+
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(bridge, cdp, projectName);
|
|
558
|
+
(0, cdpBridgeManager_1.ensurePlanningDetector)(bridge, cdp, projectName);
|
|
559
|
+
return { cdp, projectName, workspacePath };
|
|
560
|
+
}
|
|
561
|
+
catch (e) {
|
|
562
|
+
logger_1.logger.error(`[resolveWorkspaceAndCdp] Connection failed:`, e);
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const replyHtml = async (ctx, text, keyboard) => {
|
|
567
|
+
await ctx.reply(text, {
|
|
568
|
+
parse_mode: 'HTML',
|
|
569
|
+
reply_markup: keyboard,
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
// /start command
|
|
573
|
+
bot.command('start', async (ctx) => {
|
|
574
|
+
await replyHtml(ctx, `<b>Remoat Online</b>\n\n` +
|
|
575
|
+
`Use /help for available commands.\n` +
|
|
576
|
+
`Send any text message to forward it to Antigravity.`);
|
|
577
|
+
});
|
|
578
|
+
// /help command
|
|
579
|
+
bot.command('help', async (ctx) => {
|
|
580
|
+
await replyHtml(ctx, `<b>📖 Remoat Commands</b>\n\n` +
|
|
581
|
+
`<b>💬 Chat</b>\n` +
|
|
582
|
+
`/new — Start a new chat session\n` +
|
|
583
|
+
`/chat — Show current session info\n\n` +
|
|
584
|
+
`<b>⏹️ Control</b>\n` +
|
|
585
|
+
`/stop — Interrupt active LLM generation\n` +
|
|
586
|
+
`/screenshot — Capture Antigravity screen\n\n` +
|
|
587
|
+
`<b>⚙️ Settings</b>\n` +
|
|
588
|
+
`/mode — Display and change execution mode\n` +
|
|
589
|
+
`/model — Display and change LLM model\n\n` +
|
|
590
|
+
`<b>📁 Projects</b>\n` +
|
|
591
|
+
`/project — Display project list\n\n` +
|
|
592
|
+
`<b>📝 Templates</b>\n` +
|
|
593
|
+
`/template — Show templates\n` +
|
|
594
|
+
`/template_add — Register a template\n` +
|
|
595
|
+
`/template_delete — Delete a template\n\n` +
|
|
596
|
+
`<b>🔧 System</b>\n` +
|
|
597
|
+
`/status — Display overall bot status\n` +
|
|
598
|
+
`/autoaccept — Toggle auto-approve mode\n` +
|
|
599
|
+
`/cleanup [days] — Clean up inactive sessions\n` +
|
|
600
|
+
`/ping — Check latency\n\n` +
|
|
601
|
+
`<i>Text messages are sent directly to Antigravity</i>`);
|
|
602
|
+
});
|
|
603
|
+
// /mode command
|
|
604
|
+
bot.command('mode', async (ctx) => {
|
|
605
|
+
await (0, modeUi_1.sendModeUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, modeService, { getCurrentCdp: () => (0, cdpBridgeManager_1.getCurrentCdp)(bridge) });
|
|
606
|
+
});
|
|
607
|
+
// /model command
|
|
608
|
+
bot.command('model', async (ctx) => {
|
|
609
|
+
const modelName = ctx.match?.trim();
|
|
610
|
+
if (modelName) {
|
|
611
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
612
|
+
if (!cdp) {
|
|
613
|
+
await ctx.reply('Not connected to CDP.');
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const res = await cdp.setUiModel(modelName);
|
|
617
|
+
if (res.ok) {
|
|
618
|
+
await ctx.reply(`Model changed to <b>${(0, telegramFormatter_1.escapeHtml)(res.model || modelName)}</b>.`, { parse_mode: 'HTML' });
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
await ctx.reply(res.error || 'Failed to change model.');
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
await (0, modelsUi_1.sendModelsUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, { getCurrentCdp: () => (0, cdpBridgeManager_1.getCurrentCdp)(bridge), fetchQuota: async () => bridge.quota.fetchQuota() });
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
// /template command
|
|
629
|
+
bot.command('template', async (ctx) => {
|
|
630
|
+
const templates = templateRepo.findAll();
|
|
631
|
+
await (0, templateUi_1.sendTemplateUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, templates);
|
|
632
|
+
});
|
|
633
|
+
// /template_add command
|
|
634
|
+
bot.command('template_add', async (ctx) => {
|
|
635
|
+
const args = (ctx.match || '').trim();
|
|
636
|
+
const parts = args.split(/\s+/);
|
|
637
|
+
if (parts.length < 2) {
|
|
638
|
+
await ctx.reply('Usage: /template_add <name> <prompt>');
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const name = parts[0];
|
|
642
|
+
const prompt = parts.slice(1).join(' ');
|
|
643
|
+
const result = await slashCommandHandler.handleCommand('template', ['add', name, prompt]);
|
|
644
|
+
await ctx.reply(result.message);
|
|
645
|
+
});
|
|
646
|
+
// /template_delete command
|
|
647
|
+
bot.command('template_delete', async (ctx) => {
|
|
648
|
+
const name = (ctx.match || '').trim();
|
|
649
|
+
if (!name) {
|
|
650
|
+
await ctx.reply('Usage: /template_delete <name>');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const result = await slashCommandHandler.handleCommand('template', ['delete', name]);
|
|
654
|
+
await ctx.reply(result.message);
|
|
655
|
+
});
|
|
656
|
+
// /status command
|
|
657
|
+
bot.command('status', async (ctx) => {
|
|
658
|
+
const activeNames = bridge.pool.getActiveWorkspaceNames();
|
|
659
|
+
const currentMode = modeService.getCurrentMode();
|
|
660
|
+
const autoAcceptStatus = bridge.autoAccept.isEnabled() ? '🟢 ON' : '⚪ OFF';
|
|
661
|
+
let text = `<b>🔧 Bot Status</b>\n\n`;
|
|
662
|
+
text += `<b>CDP:</b> ${activeNames.length > 0 ? `🟢 ${activeNames.length} project(s) connected` : '⚪ Disconnected'}\n`;
|
|
663
|
+
text += `<b>Mode:</b> ${(0, telegramFormatter_1.escapeHtml)(modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode)}\n`;
|
|
664
|
+
text += `<b>Auto Approve:</b> ${autoAcceptStatus}\n`;
|
|
665
|
+
if (activeNames.length > 0) {
|
|
666
|
+
text += `\n<b>Connected Projects:</b>\n`;
|
|
667
|
+
for (const name of activeNames) {
|
|
668
|
+
const cdp = bridge.pool.getConnected(name);
|
|
669
|
+
const contexts = cdp ? cdp.getContexts().length : 0;
|
|
670
|
+
text += `• <b>${(0, telegramFormatter_1.escapeHtml)(name)}</b> — Contexts: ${contexts}\n`;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
text += `\nSend a message to auto-connect to a project.`;
|
|
675
|
+
}
|
|
676
|
+
await replyHtml(ctx, text);
|
|
677
|
+
});
|
|
678
|
+
// /autoaccept command
|
|
679
|
+
bot.command('autoaccept', async (ctx) => {
|
|
680
|
+
const requestedMode = (ctx.match || '').trim();
|
|
681
|
+
if (requestedMode === 'on' || requestedMode === 'off') {
|
|
682
|
+
const result = bridge.autoAccept.handle(requestedMode);
|
|
683
|
+
await ctx.reply(result.message);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
await (0, autoAcceptUi_1.sendAutoAcceptUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, bridge.autoAccept);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
// /cleanup command
|
|
690
|
+
bot.command('cleanup', async (ctx) => {
|
|
691
|
+
const days = Math.max(1, parseInt((ctx.match || '').trim(), 10) || 7);
|
|
692
|
+
const guildId = String(ctx.chat.id);
|
|
693
|
+
const inactive = cleanupHandler.findInactiveSessions(guildId, days);
|
|
694
|
+
if (inactive.length === 0) {
|
|
695
|
+
await replyHtml(ctx, `No inactive sessions older than <b>${days}</b> day(s).`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const list = inactive.slice(0, 20).map(({ binding, session }) => {
|
|
699
|
+
const label = session?.displayName ?? binding.workspacePath;
|
|
700
|
+
return `• ${(0, telegramFormatter_1.escapeHtml)(label)}`;
|
|
701
|
+
}).join('\n');
|
|
702
|
+
const extra = inactive.length > 20 ? `\n…and ${inactive.length - 20} more` : '';
|
|
703
|
+
const keyboard = new grammy_1.InlineKeyboard()
|
|
704
|
+
.text('📦 Archive', `${cleanupCommandHandler_1.CLEANUP_ARCHIVE_BTN}:${days}`)
|
|
705
|
+
.text('🗑 Delete', `${cleanupCommandHandler_1.CLEANUP_DELETE_BTN}:${days}`)
|
|
706
|
+
.text('❌ Cancel', cleanupCommandHandler_1.CLEANUP_CANCEL_BTN);
|
|
707
|
+
await replyHtml(ctx, `<b>🧹 Cleanup</b>\n\n` +
|
|
708
|
+
`Found <b>${inactive.length}</b> session(s) older than <b>${days}</b> day(s):\n\n` +
|
|
709
|
+
`${list}${extra}\n\n` +
|
|
710
|
+
`Choose an action:`, keyboard);
|
|
711
|
+
});
|
|
712
|
+
// /screenshot command
|
|
713
|
+
bot.command('screenshot', async (ctx) => {
|
|
714
|
+
await (0, screenshotUi_1.handleScreenshot)(async (input, caption) => { await ctx.replyWithPhoto(input, { caption }); }, async (text) => { await ctx.reply(text); }, (0, cdpBridgeManager_1.getCurrentCdp)(bridge));
|
|
715
|
+
});
|
|
716
|
+
// /stop command
|
|
717
|
+
bot.command('stop', async (ctx) => {
|
|
718
|
+
const ch = getChannel(ctx);
|
|
719
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
720
|
+
const cdp = resolved?.cdp ?? (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
721
|
+
if (!cdp) {
|
|
722
|
+
await ctx.reply('⚠️ Not connected to CDP.');
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const contextId = cdp.getPrimaryContextId();
|
|
727
|
+
const callParams = { expression: responseMonitor_1.RESPONSE_SELECTORS.CLICK_STOP_BUTTON, returnByValue: true, awaitPromise: false };
|
|
728
|
+
if (contextId !== null)
|
|
729
|
+
callParams.contextId = contextId;
|
|
730
|
+
const result = await cdp.call('Runtime.evaluate', callParams);
|
|
731
|
+
const value = result?.result?.value;
|
|
732
|
+
if (value?.ok) {
|
|
733
|
+
const ch = getChannel(ctx);
|
|
734
|
+
userStopRequestedChannels.add(channelKey(ch));
|
|
735
|
+
await replyHtml(ctx, `<b>⏹️ Generation Interrupted</b>\nAI response generation was safely stopped.`);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
await replyHtml(ctx, `<b>⚠️ Could Not Stop</b>\n${(0, telegramFormatter_1.escapeHtml)(value?.error || 'Stop button not found.')}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
catch (e) {
|
|
742
|
+
await ctx.reply(`❌ Error during stop: ${e.message}`);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
// /project command
|
|
746
|
+
bot.command('project', async (ctx) => {
|
|
747
|
+
const workspaces = workspaceService.scanWorkspaces();
|
|
748
|
+
const { text, keyboard } = (0, projectListUi_1.buildProjectListUI)(workspaces, 0);
|
|
749
|
+
await replyHtml(ctx, text, keyboard);
|
|
750
|
+
});
|
|
751
|
+
// /new command
|
|
752
|
+
bot.command('new', async (ctx) => {
|
|
753
|
+
const ch = getChannel(ctx);
|
|
754
|
+
const key = channelKey(ch);
|
|
755
|
+
const session = chatSessionRepo.findByChannelId(key);
|
|
756
|
+
const binding = workspaceBindingRepo.findByChannelId(key);
|
|
757
|
+
const workspaceName = session?.workspacePath ?? binding?.workspacePath;
|
|
758
|
+
if (!workspaceName) {
|
|
759
|
+
await ctx.reply('⚠️ No project is bound to this chat. Use /project to select one.');
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const workspacePath = workspaceService.getWorkspacePath(workspaceName);
|
|
763
|
+
let cdp;
|
|
764
|
+
try {
|
|
765
|
+
cdp = await bridge.pool.getOrConnect(workspacePath);
|
|
766
|
+
}
|
|
767
|
+
catch (e) {
|
|
768
|
+
await ctx.reply(`⚠️ Failed to connect: ${e.message}`);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const chatResult = await chatSessionService.startNewChat(cdp);
|
|
773
|
+
if (chatResult.ok) {
|
|
774
|
+
await replyHtml(ctx, `<b>💬 New Chat Started</b>\nSend your message now.`);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
await ctx.reply(`⚠️ Could not start new chat: ${chatResult.error}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch (e) {
|
|
781
|
+
await ctx.reply(`⚠️ Error: ${e.message}`);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
// /chat command
|
|
785
|
+
bot.command('chat', async (ctx) => {
|
|
786
|
+
const ch = getChannel(ctx);
|
|
787
|
+
const key = channelKey(ch);
|
|
788
|
+
const session = chatSessionRepo.findByChannelId(key);
|
|
789
|
+
if (!session) {
|
|
790
|
+
const activeNames = bridge.pool.getActiveWorkspaceNames();
|
|
791
|
+
const anyCdp = activeNames.length > 0 ? bridge.pool.getConnected(activeNames[0]) : null;
|
|
792
|
+
const info = anyCdp
|
|
793
|
+
? await chatSessionService.getCurrentSessionInfo(anyCdp)
|
|
794
|
+
: { title: '(CDP Disconnected)', hasActiveChat: false };
|
|
795
|
+
await replyHtml(ctx, `<b>💬 Chat Session Info</b>\n\n` +
|
|
796
|
+
`<b>Title:</b> ${(0, telegramFormatter_1.escapeHtml)(info.title)}\n` +
|
|
797
|
+
`<b>Status:</b> ${info.hasActiveChat ? '🟢 Active' : '⚪ Inactive'}\n\n` +
|
|
798
|
+
`<i>Use /project to bind a project first.</i>`);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const allSessions = chatSessionRepo.findByCategoryId(session.categoryId);
|
|
802
|
+
const sessionList = allSessions.map(s => {
|
|
803
|
+
const name = s.displayName || `session-${s.sessionNumber}`;
|
|
804
|
+
const current = s.channelId === key ? ' ← Current' : '';
|
|
805
|
+
return `• ${name}${current}`;
|
|
806
|
+
}).join('\n');
|
|
807
|
+
await replyHtml(ctx, `<b>💬 Chat Session Info</b>\n\n` +
|
|
808
|
+
`<b>Current:</b> #${session.sessionNumber} — ${(0, telegramFormatter_1.escapeHtml)(session.displayName || '(Unset)')}\n` +
|
|
809
|
+
`<b>Project:</b> ${(0, telegramFormatter_1.escapeHtml)(session.workspacePath)}\n` +
|
|
810
|
+
`<b>Total sessions:</b> ${allSessions.length}\n\n` +
|
|
811
|
+
`<b>Sessions:</b>\n${(0, telegramFormatter_1.escapeHtml)(sessionList)}`);
|
|
812
|
+
});
|
|
813
|
+
// /ping command
|
|
814
|
+
bot.command('ping', async (ctx) => {
|
|
815
|
+
const start = Date.now();
|
|
816
|
+
const msg = await ctx.reply('🏓 Pong!');
|
|
817
|
+
const latency = Date.now() - start;
|
|
818
|
+
await bot.api.editMessageText(ctx.chat.id, msg.message_id, `🏓 Pong! Latency: <b>${latency}ms</b>`, { parse_mode: 'HTML' });
|
|
819
|
+
});
|
|
820
|
+
// =============================================================================
|
|
821
|
+
// Callback query handler (inline keyboard buttons)
|
|
822
|
+
// =============================================================================
|
|
823
|
+
bot.on('callback_query:data', async (ctx) => {
|
|
824
|
+
const data = ctx.callbackQuery.data;
|
|
825
|
+
const ch = getChannelFromCb(ctx);
|
|
826
|
+
// Mode selection
|
|
827
|
+
if (data.startsWith('mode_select:')) {
|
|
828
|
+
const selectedMode = data.replace('mode_select:', '');
|
|
829
|
+
modeService.setMode(selectedMode);
|
|
830
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
831
|
+
if (cdp) {
|
|
832
|
+
const res = await cdp.setUiMode(selectedMode);
|
|
833
|
+
if (!res.ok)
|
|
834
|
+
logger_1.logger.warn(`[Mode] UI switch failed: ${res.error}`);
|
|
835
|
+
}
|
|
836
|
+
const { text, keyboard } = await (0, modeUi_1.buildModeUI)(modeService, { getCurrentCdp: () => (0, cdpBridgeManager_1.getCurrentCdp)(bridge) });
|
|
837
|
+
try {
|
|
838
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
839
|
+
}
|
|
840
|
+
catch { /* may fail if unchanged */ }
|
|
841
|
+
await ctx.answerCallbackQuery({ text: `Mode: ${modeService_1.MODE_DISPLAY_NAMES[selectedMode] || selectedMode}` });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Exhausted model button — show alert toast
|
|
845
|
+
if (data.startsWith('model_exhausted_')) {
|
|
846
|
+
const modelName = data.replace('model_exhausted_', '');
|
|
847
|
+
await ctx.answerCallbackQuery({ text: `⛔ ${modelName} is exhausted. Wait for quota reset or pick another model.`, show_alert: true });
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
// Model selection
|
|
851
|
+
if (data.startsWith('model_btn_')) {
|
|
852
|
+
const modelName = data.replace('model_btn_', '');
|
|
853
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
854
|
+
if (!cdp) {
|
|
855
|
+
await ctx.answerCallbackQuery({ text: 'Not connected to CDP.' });
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const res = await cdp.setUiModel(modelName);
|
|
859
|
+
if (res.ok) {
|
|
860
|
+
const payload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
861
|
+
if (payload)
|
|
862
|
+
try {
|
|
863
|
+
await ctx.editMessageText(payload.text, { parse_mode: 'HTML', reply_markup: payload.keyboard });
|
|
864
|
+
}
|
|
865
|
+
catch { }
|
|
866
|
+
await ctx.answerCallbackQuery({ text: `Model: ${res.model}` });
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
await ctx.answerCallbackQuery({ text: res.error || 'Failed to change model.' });
|
|
870
|
+
}
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
// Model refresh
|
|
874
|
+
if (data === 'model_refresh_btn') {
|
|
875
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
876
|
+
if (!cdp) {
|
|
877
|
+
await ctx.answerCallbackQuery({ text: 'Not connected.' });
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const payload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
881
|
+
if (payload)
|
|
882
|
+
try {
|
|
883
|
+
await ctx.editMessageText(payload.text, { parse_mode: 'HTML', reply_markup: payload.keyboard });
|
|
884
|
+
}
|
|
885
|
+
catch { }
|
|
886
|
+
await ctx.answerCallbackQuery({ text: 'Refreshed' });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
// Auto-accept buttons
|
|
890
|
+
if (data === autoAcceptUi_1.AUTOACCEPT_BTN_ON || data === autoAcceptUi_1.AUTOACCEPT_BTN_OFF) {
|
|
891
|
+
const action = data === autoAcceptUi_1.AUTOACCEPT_BTN_ON ? 'on' : 'off';
|
|
892
|
+
bridge.autoAccept.handle(action);
|
|
893
|
+
await (0, autoAcceptUi_1.sendAutoAcceptUI)(async (text, keyboard) => { try {
|
|
894
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
895
|
+
}
|
|
896
|
+
catch { } }, bridge.autoAccept);
|
|
897
|
+
await ctx.answerCallbackQuery({ text: `Auto-accept: ${action.toUpperCase()}` });
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (data === autoAcceptUi_1.AUTOACCEPT_BTN_REFRESH) {
|
|
901
|
+
await (0, autoAcceptUi_1.sendAutoAcceptUI)(async (text, keyboard) => { try {
|
|
902
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
903
|
+
}
|
|
904
|
+
catch { } }, bridge.autoAccept);
|
|
905
|
+
await ctx.answerCallbackQuery({ text: 'Refreshed' });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
// Project selection
|
|
909
|
+
if (data.startsWith(`${projectListUi_1.PROJECT_SELECT_ID}:`)) {
|
|
910
|
+
const workspacePath = data.replace(`${projectListUi_1.PROJECT_SELECT_ID}:`, '');
|
|
911
|
+
if (!workspaceService.exists(workspacePath)) {
|
|
912
|
+
await ctx.answerCallbackQuery({ text: `Project "${workspacePath}" not found.` });
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
let key = channelKey(ch);
|
|
916
|
+
const guildId = String(ch.chatId);
|
|
917
|
+
const isForum = ctx.chat?.type === 'supergroup' && ctx.chat.is_forum === true;
|
|
918
|
+
// Auto-create topic if conditions are met
|
|
919
|
+
if (config.useTopics && isForum && !ch.threadId) {
|
|
920
|
+
try {
|
|
921
|
+
const existing = workspaceBindingRepo.findByWorkspacePathAndGuildId(workspacePath, guildId);
|
|
922
|
+
const existingTopic = existing.find(b => b.channelId.includes(':'));
|
|
923
|
+
let topicId;
|
|
924
|
+
if (existingTopic) {
|
|
925
|
+
topicId = Number(existingTopic.channelId.split(':')[1]);
|
|
926
|
+
topicManager.registerTopic(workspacePath, topicId);
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
topicManager.setChatId(ch.chatId);
|
|
930
|
+
const sanitized = topicManager.sanitizeName(workspacePath);
|
|
931
|
+
const result = await topicManager.ensureTopic(sanitized);
|
|
932
|
+
topicId = result.topicId;
|
|
933
|
+
}
|
|
934
|
+
key = `${ch.chatId}:${topicId}`;
|
|
935
|
+
// Send welcome message in the new topic
|
|
936
|
+
const fullPath = workspaceService.getWorkspacePath(workspacePath);
|
|
937
|
+
await bot.api.sendMessage(ch.chatId, `<b>📁 Project Selected</b>\n\n✅ <b>${(0, telegramFormatter_1.escapeHtml)(workspacePath)}</b>\n<code>${(0, telegramFormatter_1.escapeHtml)(fullPath)}</code>\n\nSend messages here to interact with this project.`, { parse_mode: 'HTML', message_thread_id: topicId });
|
|
938
|
+
workspaceBindingRepo.upsert({ channelId: key, workspacePath, guildId });
|
|
939
|
+
await ctx.answerCallbackQuery({ text: `Topic created for: ${workspacePath}` });
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
catch (e) {
|
|
943
|
+
logger_1.logger.warn(`[ProjectSelect] Topic creation failed, falling back: ${e.message}`);
|
|
944
|
+
// Fall through to default behavior
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
workspaceBindingRepo.upsert({ channelId: key, workspacePath, guildId });
|
|
948
|
+
const fullPath = workspaceService.getWorkspacePath(workspacePath);
|
|
949
|
+
await ctx.editMessageText(`<b>📁 Project Selected</b>\n\n✅ <b>${(0, telegramFormatter_1.escapeHtml)(workspacePath)}</b>\n<code>${(0, telegramFormatter_1.escapeHtml)(fullPath)}</code>\n\nSend messages here to interact with this project.`, { parse_mode: 'HTML' });
|
|
950
|
+
await ctx.answerCallbackQuery({ text: `Selected: ${workspacePath}` });
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
// Project page navigation
|
|
954
|
+
if (data.startsWith(`${projectListUi_1.PROJECT_PAGE_PREFIX}:`)) {
|
|
955
|
+
const page = (0, projectListUi_1.parseProjectPageId)(data);
|
|
956
|
+
if (!isNaN(page)) {
|
|
957
|
+
const workspaces = workspaceService.scanWorkspaces();
|
|
958
|
+
const { text, keyboard } = (0, projectListUi_1.buildProjectListUI)(workspaces, page);
|
|
959
|
+
try {
|
|
960
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
961
|
+
}
|
|
962
|
+
catch { }
|
|
963
|
+
}
|
|
964
|
+
await ctx.answerCallbackQuery();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
// Template button
|
|
968
|
+
if (data.startsWith(templateUi_1.TEMPLATE_BTN_PREFIX)) {
|
|
969
|
+
const templateId = (0, templateUi_1.parseTemplateButtonId)(data);
|
|
970
|
+
if (isNaN(templateId)) {
|
|
971
|
+
await ctx.answerCallbackQuery({ text: 'Invalid template.' });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const template = templateRepo.findById(templateId);
|
|
975
|
+
if (!template) {
|
|
976
|
+
await ctx.answerCallbackQuery({ text: 'Template not found.' });
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
980
|
+
if (!resolved) {
|
|
981
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
982
|
+
if (!cdp) {
|
|
983
|
+
await ctx.answerCallbackQuery({ text: 'Not connected.' });
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
await promptDispatcher.send({ channel: ch, prompt: template.prompt, cdp, inboundImages: [], options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator } });
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
await promptDispatcher.send({ channel: ch, prompt: template.prompt, cdp: resolved.cdp, inboundImages: [], options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator } });
|
|
990
|
+
}
|
|
991
|
+
await ctx.answerCallbackQuery({ text: `Running: ${template.name}` });
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
// Session selection
|
|
995
|
+
if ((0, sessionPickerUi_1.isSessionSelectId)(data)) {
|
|
996
|
+
const selectedTitle = data.replace(`${sessionPickerUi_1.SESSION_SELECT_ID}:`, '');
|
|
997
|
+
const key = channelKey(ch);
|
|
998
|
+
const binding = workspaceBindingRepo.findByChannelId(key);
|
|
999
|
+
if (!binding) {
|
|
1000
|
+
await ctx.answerCallbackQuery({ text: 'No project bound.' });
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const workspacePath = workspaceService.getWorkspacePath(binding.workspacePath);
|
|
1004
|
+
try {
|
|
1005
|
+
const cdp = await bridge.pool.getOrConnect(workspacePath);
|
|
1006
|
+
const activateResult = await chatSessionService.activateSessionByTitle(cdp, selectedTitle);
|
|
1007
|
+
if (activateResult.ok) {
|
|
1008
|
+
await ctx.editMessageText(`<b>🔗 Joined Session</b>\n\n<b>${(0, telegramFormatter_1.escapeHtml)(selectedTitle)}</b>`, { parse_mode: 'HTML' });
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
await ctx.answerCallbackQuery({ text: `Failed: ${activateResult.error}` });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
catch (e) {
|
|
1015
|
+
await ctx.answerCallbackQuery({ text: `Error: ${e.message}` });
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
// Approval buttons
|
|
1020
|
+
const approvalAction = (0, cdpBridgeManager_1.parseApprovalCustomId)(data);
|
|
1021
|
+
if (approvalAction) {
|
|
1022
|
+
const projectName = approvalAction.projectName ?? bridge.lastActiveWorkspace;
|
|
1023
|
+
const detector = projectName ? bridge.pool.getApprovalDetector(projectName) : undefined;
|
|
1024
|
+
if (!detector) {
|
|
1025
|
+
await ctx.answerCallbackQuery({ text: 'Approval detector not found.' });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
let success = false;
|
|
1029
|
+
let actionLabel = '';
|
|
1030
|
+
if (approvalAction.action === 'approve') {
|
|
1031
|
+
success = await detector.approveButton();
|
|
1032
|
+
actionLabel = 'Allow';
|
|
1033
|
+
}
|
|
1034
|
+
else if (approvalAction.action === 'always_allow') {
|
|
1035
|
+
success = await detector.alwaysAllowButton();
|
|
1036
|
+
actionLabel = 'Allow Chat';
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
success = await detector.denyButton();
|
|
1040
|
+
actionLabel = 'Deny';
|
|
1041
|
+
}
|
|
1042
|
+
if (success) {
|
|
1043
|
+
try {
|
|
1044
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined });
|
|
1045
|
+
}
|
|
1046
|
+
catch { }
|
|
1047
|
+
await ctx.answerCallbackQuery({ text: `${actionLabel} executed.` });
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
await ctx.answerCallbackQuery({ text: 'Button not found.' });
|
|
1051
|
+
}
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
// Planning buttons
|
|
1055
|
+
const planningAction = (0, cdpBridgeManager_1.parsePlanningCustomId)(data);
|
|
1056
|
+
if (planningAction) {
|
|
1057
|
+
const projectName = planningAction.projectName ?? bridge.lastActiveWorkspace;
|
|
1058
|
+
const detector = projectName ? bridge.pool.getPlanningDetector(projectName) : undefined;
|
|
1059
|
+
if (!detector) {
|
|
1060
|
+
await ctx.answerCallbackQuery({ text: 'Planning detector not found.' });
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (planningAction.action === 'open') {
|
|
1064
|
+
const clicked = await detector.clickOpenButton();
|
|
1065
|
+
if (clicked) {
|
|
1066
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1067
|
+
let planContent = null;
|
|
1068
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1069
|
+
planContent = await detector.extractPlanContent();
|
|
1070
|
+
if (planContent)
|
|
1071
|
+
break;
|
|
1072
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1073
|
+
}
|
|
1074
|
+
if (planContent) {
|
|
1075
|
+
const truncated = planContent.length > 3800 ? planContent.substring(0, 3800) + '\n\n(truncated)' : planContent;
|
|
1076
|
+
await bot.api.sendMessage(ch.chatId, `<b>Plan Content</b>\n\n${(0, telegramFormatter_1.escapeHtml)(truncated)}`, { parse_mode: 'HTML', message_thread_id: ch.threadId });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Opened' : 'Open button not found.' });
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
const clicked = await detector.clickProceedButton();
|
|
1083
|
+
if (clicked)
|
|
1084
|
+
try {
|
|
1085
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined });
|
|
1086
|
+
}
|
|
1087
|
+
catch { }
|
|
1088
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Proceeding...' : 'Proceed button not found.' });
|
|
1089
|
+
}
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
// Error popup buttons
|
|
1093
|
+
const errorAction = (0, cdpBridgeManager_1.parseErrorPopupCustomId)(data);
|
|
1094
|
+
if (errorAction) {
|
|
1095
|
+
const projectName = errorAction.projectName ?? bridge.lastActiveWorkspace;
|
|
1096
|
+
const detector = projectName ? bridge.pool.getErrorPopupDetector(projectName) : undefined;
|
|
1097
|
+
if (!detector) {
|
|
1098
|
+
await ctx.answerCallbackQuery({ text: 'Error popup detector not found.' });
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (errorAction.action === 'dismiss') {
|
|
1102
|
+
const clicked = await detector.clickDismissButton();
|
|
1103
|
+
if (clicked)
|
|
1104
|
+
try {
|
|
1105
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined });
|
|
1106
|
+
}
|
|
1107
|
+
catch { }
|
|
1108
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Dismissed' : 'Button not found.' });
|
|
1109
|
+
}
|
|
1110
|
+
else if (errorAction.action === 'copy_debug') {
|
|
1111
|
+
const clicked = await detector.clickCopyDebugInfoButton();
|
|
1112
|
+
let clipboardOk = false;
|
|
1113
|
+
if (clicked) {
|
|
1114
|
+
await new Promise(r => setTimeout(r, 300));
|
|
1115
|
+
const clipboardContent = await detector.readClipboard();
|
|
1116
|
+
if (clipboardContent) {
|
|
1117
|
+
clipboardOk = true;
|
|
1118
|
+
const truncated = clipboardContent.length > 3800 ? clipboardContent.substring(0, 3800) + '\n(truncated)' : clipboardContent;
|
|
1119
|
+
await bot.api.sendMessage(ch.chatId, `<b>Debug Info</b>\n\n<pre>${(0, telegramFormatter_1.escapeHtml)(truncated)}</pre>`, { parse_mode: 'HTML', message_thread_id: ch.threadId });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const feedbackText = !clicked ? 'Button not found.' : clipboardOk ? 'Copied' : 'Could not read clipboard.';
|
|
1123
|
+
await ctx.answerCallbackQuery({ text: feedbackText });
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
const clicked = await detector.clickRetryButton();
|
|
1127
|
+
if (clicked)
|
|
1128
|
+
try {
|
|
1129
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined });
|
|
1130
|
+
}
|
|
1131
|
+
catch { }
|
|
1132
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Retrying...' : 'Button not found.' });
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
// Cleanup buttons
|
|
1137
|
+
if (data.startsWith(cleanupCommandHandler_1.CLEANUP_ARCHIVE_BTN) || data.startsWith(cleanupCommandHandler_1.CLEANUP_DELETE_BTN) || data === cleanupCommandHandler_1.CLEANUP_CANCEL_BTN) {
|
|
1138
|
+
if (data === cleanupCommandHandler_1.CLEANUP_CANCEL_BTN) {
|
|
1139
|
+
try {
|
|
1140
|
+
await ctx.editMessageText('Cleanup cancelled.');
|
|
1141
|
+
}
|
|
1142
|
+
catch { }
|
|
1143
|
+
await ctx.answerCallbackQuery({ text: 'Cancelled' });
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const isDelete = data.startsWith(cleanupCommandHandler_1.CLEANUP_DELETE_BTN);
|
|
1147
|
+
const callbackDays = parseInt(data.split(':')[1], 10) || 7;
|
|
1148
|
+
const guildId = String(ch.chatId);
|
|
1149
|
+
const inactive = cleanupHandler.findInactiveSessions(guildId, callbackDays);
|
|
1150
|
+
let processed = 0;
|
|
1151
|
+
for (const { binding } of inactive) {
|
|
1152
|
+
const threadId = binding.channelId.includes(':')
|
|
1153
|
+
? Number(binding.channelId.split(':')[1])
|
|
1154
|
+
: undefined;
|
|
1155
|
+
if (threadId) {
|
|
1156
|
+
try {
|
|
1157
|
+
if (isDelete) {
|
|
1158
|
+
await bot.api.deleteForumTopic(ch.chatId, threadId);
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
await bot.api.closeForumTopic(ch.chatId, threadId);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
catch (e) {
|
|
1165
|
+
logger_1.logger.warn(`[Cleanup] Topic operation failed for ${binding.channelId}: ${e.message}`);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
cleanupHandler.cleanupByChannelId(binding.channelId);
|
|
1169
|
+
processed++;
|
|
1170
|
+
}
|
|
1171
|
+
const action = isDelete ? 'deleted' : 'archived';
|
|
1172
|
+
try {
|
|
1173
|
+
await ctx.editMessageText(`✅ Cleanup complete — ${processed} session(s) ${action}.`);
|
|
1174
|
+
}
|
|
1175
|
+
catch { }
|
|
1176
|
+
await ctx.answerCallbackQuery({ text: `${processed} session(s) ${action}` });
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
await ctx.answerCallbackQuery();
|
|
1180
|
+
});
|
|
1181
|
+
// =============================================================================
|
|
1182
|
+
// Text message handler (main chat flow)
|
|
1183
|
+
// =============================================================================
|
|
1184
|
+
bot.on('message:text', async (ctx) => {
|
|
1185
|
+
const ch = getChannel(ctx);
|
|
1186
|
+
const key = channelKey(ch);
|
|
1187
|
+
const text = ctx.message.text.trim();
|
|
1188
|
+
if (!text)
|
|
1189
|
+
return;
|
|
1190
|
+
// Check if it looks like a text command
|
|
1191
|
+
const parsed = (0, messageParser_1.parseMessageContent)(text);
|
|
1192
|
+
if (parsed.isCommand && parsed.commandName) {
|
|
1193
|
+
if (parsed.commandName === 'autoaccept') {
|
|
1194
|
+
const result = bridge.autoAccept.handle(parsed.args?.[0]);
|
|
1195
|
+
await ctx.reply(result.message);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (parsed.commandName === 'screenshot') {
|
|
1199
|
+
await (0, screenshotUi_1.handleScreenshot)(async (input, caption) => { await ctx.replyWithPhoto(input, { caption }); }, async (text) => { await ctx.reply(text); }, (0, cdpBridgeManager_1.getCurrentCdp)(bridge));
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (parsed.commandName === 'status') {
|
|
1203
|
+
const activeNames = bridge.pool.getActiveWorkspaceNames();
|
|
1204
|
+
const currentMode = modeService.getCurrentMode();
|
|
1205
|
+
let statusText = `<b>🔧 Bot Status</b>\n\n`;
|
|
1206
|
+
statusText += `<b>CDP:</b> ${activeNames.length > 0 ? `🟢 ${activeNames.length} project(s)` : '⚪ Disconnected'}\n`;
|
|
1207
|
+
statusText += `<b>Mode:</b> ${(0, telegramFormatter_1.escapeHtml)(modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode)}\n`;
|
|
1208
|
+
statusText += `<b>Auto Approve:</b> ${bridge.autoAccept.isEnabled() ? '🟢 ON' : '⚪ OFF'}`;
|
|
1209
|
+
await replyHtml(ctx, statusText);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const result = await slashCommandHandler.handleCommand(parsed.commandName, parsed.args || []);
|
|
1213
|
+
await ctx.reply(result.message);
|
|
1214
|
+
if (result.prompt) {
|
|
1215
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
1216
|
+
if (cdp) {
|
|
1217
|
+
await promptDispatcher.send({
|
|
1218
|
+
channel: ch,
|
|
1219
|
+
prompt: result.prompt,
|
|
1220
|
+
cdp,
|
|
1221
|
+
inboundImages: [],
|
|
1222
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
await ctx.reply('Not connected to CDP. Send a message first to connect to a project.');
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
// Regular message — route to Antigravity
|
|
1232
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
1233
|
+
if (!resolved) {
|
|
1234
|
+
await ctx.reply('No project is configured for this chat. Use /project to select one.');
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const session = chatSessionRepo.findByChannelId(key);
|
|
1238
|
+
if (session?.displayName) {
|
|
1239
|
+
(0, cdpBridgeManager_1.registerApprovalSessionChannel)(bridge, resolved.projectName, session.displayName, ch);
|
|
1240
|
+
}
|
|
1241
|
+
if (session?.isRenamed && session.displayName) {
|
|
1242
|
+
const activationResult = await chatSessionService.activateSessionByTitle(resolved.cdp, session.displayName);
|
|
1243
|
+
if (!activationResult.ok) {
|
|
1244
|
+
await ctx.reply(`⚠️ Could not route to session (${session.displayName}).`);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
else if (session && !session.isRenamed) {
|
|
1249
|
+
try {
|
|
1250
|
+
await chatSessionService.startNewChat(resolved.cdp);
|
|
1251
|
+
}
|
|
1252
|
+
catch { /* continue anyway */ }
|
|
1253
|
+
}
|
|
1254
|
+
const userMsgDetector = bridge.pool.getUserMessageDetector?.(resolved.projectName);
|
|
1255
|
+
if (userMsgDetector)
|
|
1256
|
+
userMsgDetector.addEchoHash(text);
|
|
1257
|
+
await promptDispatcher.send({
|
|
1258
|
+
channel: ch,
|
|
1259
|
+
prompt: text,
|
|
1260
|
+
cdp: resolved.cdp,
|
|
1261
|
+
inboundImages: [],
|
|
1262
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
// Photo message handler
|
|
1266
|
+
bot.on('message:photo', async (ctx) => {
|
|
1267
|
+
const ch = getChannel(ctx);
|
|
1268
|
+
const photos = ctx.message.photo;
|
|
1269
|
+
if (!photos || photos.length === 0)
|
|
1270
|
+
return;
|
|
1271
|
+
const largest = photos[photos.length - 1];
|
|
1272
|
+
const caption = ctx.message.caption?.trim() || 'Please review the attached images and respond accordingly.';
|
|
1273
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
1274
|
+
if (!resolved) {
|
|
1275
|
+
await ctx.reply('No project configured. Use /project first.');
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const inboundImages = await (0, imageHandler_1.downloadTelegramImages)(bot.api, config.telegramBotToken, [largest], String(ctx.message.message_id));
|
|
1279
|
+
try {
|
|
1280
|
+
await promptDispatcher.send({
|
|
1281
|
+
channel: ch,
|
|
1282
|
+
prompt: caption,
|
|
1283
|
+
cdp: resolved.cdp,
|
|
1284
|
+
inboundImages,
|
|
1285
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
finally {
|
|
1289
|
+
await (0, imageHandler_1.cleanupInboundImageAttachments)(inboundImages);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
// Voice message handler (voice-to-prompt via local Whisper transcription)
|
|
1293
|
+
bot.on('message:voice', async (ctx) => {
|
|
1294
|
+
const ch = getChannel(ctx);
|
|
1295
|
+
const whisperIssue = (0, voiceHandler_1.checkWhisperAvailability)();
|
|
1296
|
+
if (whisperIssue) {
|
|
1297
|
+
await ctx.reply(whisperIssue);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
1301
|
+
if (!resolved) {
|
|
1302
|
+
await ctx.reply('No project configured. Use /project first.');
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
await ctx.reply('🎙️ Transcribing voice message...');
|
|
1306
|
+
let voicePath;
|
|
1307
|
+
try {
|
|
1308
|
+
voicePath = await (0, voiceHandler_1.downloadTelegramVoice)(bot.api, config.telegramBotToken, ctx.message.voice);
|
|
1309
|
+
}
|
|
1310
|
+
catch (error) {
|
|
1311
|
+
logger_1.logger.error('[Voice] Download failed:', error?.message || error);
|
|
1312
|
+
await ctx.reply('❌ Could not download voice message. Please try again.');
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const transcript = await (0, voiceHandler_1.transcribeVoice)(voicePath);
|
|
1316
|
+
if (!transcript) {
|
|
1317
|
+
await ctx.reply('❌ Could not transcribe voice message. Please try again or type your prompt.');
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
// Check if transcription is a slash command
|
|
1321
|
+
const parsed = (0, messageParser_1.parseMessageContent)(transcript);
|
|
1322
|
+
if (parsed.isCommand && parsed.commandName) {
|
|
1323
|
+
const result = await slashCommandHandler.handleCommand(parsed.commandName, parsed.args || []);
|
|
1324
|
+
await ctx.reply(`🎙️ "${transcript}"\n\n${result.message}`);
|
|
1325
|
+
if (result.prompt) {
|
|
1326
|
+
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
1327
|
+
if (cdp) {
|
|
1328
|
+
await promptDispatcher.send({
|
|
1329
|
+
channel: ch,
|
|
1330
|
+
prompt: result.prompt,
|
|
1331
|
+
cdp,
|
|
1332
|
+
inboundImages: [],
|
|
1333
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
await ctx.reply(`📝 "${transcript}"`);
|
|
1340
|
+
const userMsgDetector = bridge.pool.getUserMessageDetector?.(resolved.projectName);
|
|
1341
|
+
if (userMsgDetector)
|
|
1342
|
+
userMsgDetector.addEchoHash(transcript);
|
|
1343
|
+
await promptDispatcher.send({
|
|
1344
|
+
channel: ch,
|
|
1345
|
+
prompt: transcript,
|
|
1346
|
+
cdp: resolved.cdp,
|
|
1347
|
+
inboundImages: [],
|
|
1348
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
logger_1.logger.info('Starting Remoat Telegram bot...');
|
|
1352
|
+
// Graceful shutdown: close database on exit
|
|
1353
|
+
const closeDb = () => { try {
|
|
1354
|
+
db.close();
|
|
1355
|
+
}
|
|
1356
|
+
catch { /* ignore */ } };
|
|
1357
|
+
process.on('exit', closeDb);
|
|
1358
|
+
process.on('SIGINT', () => { closeDb(); process.exit(0); });
|
|
1359
|
+
process.on('SIGTERM', () => { closeDb(); process.exit(0); });
|
|
1360
|
+
bot.catch((err) => {
|
|
1361
|
+
logger_1.logger.error('Bot error:', err);
|
|
1362
|
+
});
|
|
1363
|
+
await bot.start({
|
|
1364
|
+
onStart: async (botInfo) => {
|
|
1365
|
+
logger_1.logger.info(`Bot started as @${botInfo.username} | extractionMode=${config.extractionMode}`);
|
|
1366
|
+
try {
|
|
1367
|
+
await bot.api.setMyCommands([
|
|
1368
|
+
{ command: 'start', description: 'Welcome message' },
|
|
1369
|
+
{ command: 'help', description: 'Show all commands' },
|
|
1370
|
+
{ command: 'project', description: 'Select a project' },
|
|
1371
|
+
{ command: 'new', description: 'Start a new chat session' },
|
|
1372
|
+
{ command: 'chat', description: 'Current session info' },
|
|
1373
|
+
{ command: 'mode', description: 'Change execution mode' },
|
|
1374
|
+
{ command: 'model', description: 'Change LLM model' },
|
|
1375
|
+
{ command: 'stop', description: 'Interrupt active generation' },
|
|
1376
|
+
{ command: 'screenshot', description: 'Capture Antigravity screen' },
|
|
1377
|
+
{ command: 'template', description: 'Show prompt templates' },
|
|
1378
|
+
{ command: 'template_add', description: 'Register a template' },
|
|
1379
|
+
{ command: 'template_delete', description: 'Delete a template' },
|
|
1380
|
+
{ command: 'autoaccept', description: 'Toggle auto-approve mode' },
|
|
1381
|
+
{ command: 'status', description: 'Bot status overview' },
|
|
1382
|
+
{ command: 'ping', description: 'Check latency' },
|
|
1383
|
+
]);
|
|
1384
|
+
logger_1.logger.info('Telegram command menu registered successfully');
|
|
1385
|
+
}
|
|
1386
|
+
catch (err) {
|
|
1387
|
+
logger_1.logger.error('Failed to register command menu:', err);
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
});
|
|
1391
|
+
};
|
|
1392
|
+
exports.startBot = startBot;
|
|
1393
|
+
//# sourceMappingURL=index.js.map
|