remoat 0.2.8 โ 0.2.10
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 +1 -0
- package/README.md +5 -1
- package/dist/bin/commands/open.js +2 -1
- package/dist/bin/commands/open.js.map +1 -1
- package/dist/bot/index.js +468 -70
- package/dist/bot/index.js.map +1 -1
- package/dist/services/assistantDomExtractor.d.ts +2 -1
- package/dist/services/assistantDomExtractor.js +87 -18
- package/dist/services/assistantDomExtractor.js.map +1 -1
- package/dist/services/cdpBridgeManager.js +14 -12
- package/dist/services/cdpBridgeManager.js.map +1 -1
- package/dist/services/cdpConnectionPool.d.ts +7 -1
- package/dist/services/cdpConnectionPool.js +14 -1
- package/dist/services/cdpConnectionPool.js.map +1 -1
- package/dist/services/cdpService.d.ts +19 -0
- package/dist/services/cdpService.js +84 -9
- package/dist/services/cdpService.js.map +1 -1
- package/dist/services/responseMonitor.d.ts +10 -0
- package/dist/services/responseMonitor.js +69 -17
- package/dist/services/responseMonitor.js.map +1 -1
- package/dist/ui/planUi.d.ts +27 -0
- package/dist/ui/planUi.js +140 -0
- package/dist/ui/planUi.js.map +1 -0
- package/locales/en.json +1 -1
- package/locales/ja.json +1 -1
- package/package.json +1 -1
package/dist/bot/index.js
CHANGED
|
@@ -27,9 +27,9 @@ const antigravityLauncher_1 = require("../services/antigravityLauncher");
|
|
|
27
27
|
const pathUtils_1 = require("../utils/pathUtils");
|
|
28
28
|
const promptDispatcher_1 = require("../services/promptDispatcher");
|
|
29
29
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
30
|
-
const
|
|
30
|
+
const assistantDomExtractor_1 = require("../services/assistantDomExtractor");
|
|
31
31
|
const telegramFormatter_1 = require("../utils/telegramFormatter");
|
|
32
|
-
|
|
32
|
+
// ProcessLogBuffer no longer used โ progress display uses ordered event stream
|
|
33
33
|
const imageHandler_1 = require("../utils/imageHandler");
|
|
34
34
|
const voiceHandler_1 = require("../utils/voiceHandler");
|
|
35
35
|
const modeUi_1 = require("../ui/modeUi");
|
|
@@ -39,6 +39,7 @@ const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
|
39
39
|
const screenshotUi_1 = require("../ui/screenshotUi");
|
|
40
40
|
const projectListUi_1 = require("../ui/projectListUi");
|
|
41
41
|
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
42
|
+
const planUi_1 = require("../ui/planUi");
|
|
42
43
|
const PHASE_ICONS = {
|
|
43
44
|
sending: '๐ก',
|
|
44
45
|
thinking: '๐ง ',
|
|
@@ -68,7 +69,7 @@ function stripHtmlForFile(html) {
|
|
|
68
69
|
// Links
|
|
69
70
|
text = text.replace(/<a\s+href="([^"]*)">([\s\S]*?)<\/a>/gi, '[$2]($1)');
|
|
70
71
|
// Blockquotes
|
|
71
|
-
text = text.replace(/<blockquote
|
|
72
|
+
text = text.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_m, content) => content.split('\n').map((l) => `> ${l}`).join('\n'));
|
|
72
73
|
// Strip remaining tags
|
|
73
74
|
text = text.replace(/<[^>]+>/g, '');
|
|
74
75
|
// Decode entities
|
|
@@ -78,6 +79,10 @@ function stripHtmlForFile(html) {
|
|
|
78
79
|
return text;
|
|
79
80
|
}
|
|
80
81
|
const userStopRequestedChannels = new Set();
|
|
82
|
+
/** Channels where the user is expected to type plan edit instructions */
|
|
83
|
+
const planEditPendingChannels = new Map();
|
|
84
|
+
/** Cached plan content pages per channel */
|
|
85
|
+
const planContentCache = new Map();
|
|
81
86
|
function channelKey(ch) {
|
|
82
87
|
return ch.threadId ? `${ch.chatId}:${ch.threadId}` : String(ch.chatId);
|
|
83
88
|
}
|
|
@@ -137,7 +142,9 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
137
142
|
/** Send a potentially long response, splitting into chunks and attaching a .md file if needed. */
|
|
138
143
|
const sendChunkedResponse = async (title, footer, rawBody, isAlreadyHtml) => {
|
|
139
144
|
const formattedBody = isAlreadyHtml ? rawBody : (0, telegramFormatter_1.formatForTelegram)(rawBody);
|
|
140
|
-
const
|
|
145
|
+
const titleLine = title ? `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n` : '';
|
|
146
|
+
const footerLine = footer ? `\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>` : '';
|
|
147
|
+
const fullMsg = `${titleLine}${formattedBody}${footerLine}`;
|
|
141
148
|
if (fullMsg.length <= TELEGRAM_MSG_LIMIT) {
|
|
142
149
|
await upsertLiveResponse(title, rawBody, footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml, skipTruncation: true });
|
|
143
150
|
return;
|
|
@@ -149,10 +156,12 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
149
156
|
for (let pi = 0; pi < inlineCount; pi++) {
|
|
150
157
|
const partLabel = hasFile ? `(${pi + 1}/${inlineCount}+file)` : `(${pi + 1}/${total})`;
|
|
151
158
|
if (pi === 0) {
|
|
152
|
-
|
|
159
|
+
const firstTitle = title ? `${title} ${partLabel}` : partLabel;
|
|
160
|
+
await upsertLiveResponse(firstTitle, bodyChunks[pi], footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml: true, skipTruncation: true });
|
|
153
161
|
}
|
|
154
162
|
else {
|
|
155
|
-
|
|
163
|
+
const partFooter = footer ? `${(0, telegramFormatter_1.escapeHtml)(footer)} ${partLabel}` : partLabel;
|
|
164
|
+
await sendMsg(`${bodyChunks[pi]}\n\n<i>${partFooter}</i>`);
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
167
|
if (hasFile) {
|
|
@@ -177,43 +186,102 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
177
186
|
const localMode = modeService.getCurrentMode();
|
|
178
187
|
const modeName = modeService_1.MODE_UI_NAMES[localMode] || localMode;
|
|
179
188
|
const currentModel = (await cdp.getCurrentModel()) || modelService.getCurrentModel();
|
|
180
|
-
|
|
189
|
+
const modelLabel = `${currentModel}`;
|
|
190
|
+
// Initialize live progress message (replaces separate "Sending" embed)
|
|
191
|
+
let liveActivityMsgId = null;
|
|
192
|
+
try {
|
|
193
|
+
const sendingText = `<b>${PHASE_ICONS.sending} ${(0, telegramFormatter_1.escapeHtml)(modeName)} ยท ${(0, telegramFormatter_1.escapeHtml)(modelLabel)}</b>\n\n<i>Sending...</i>`;
|
|
194
|
+
const sendingMsg = await api.sendMessage(channel.chatId, sendingText, { parse_mode: 'HTML', message_thread_id: channel.threadId });
|
|
195
|
+
liveActivityMsgId = sendingMsg.message_id;
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
logger_1.logger.error('[sendPrompt] Failed to send initial status:', e);
|
|
199
|
+
}
|
|
181
200
|
let isFinalized = false;
|
|
182
201
|
let elapsedTimer = null;
|
|
183
202
|
let lastProgressText = '';
|
|
184
|
-
let lastActivityLogText = '';
|
|
185
203
|
const LIVE_RESPONSE_MAX_LEN = 3800;
|
|
186
|
-
const
|
|
187
|
-
const
|
|
204
|
+
const MAX_PROGRESS_BODY = 3500;
|
|
205
|
+
const MAX_PROGRESS_ENTRIES = 60;
|
|
188
206
|
let liveResponseMsgId = null;
|
|
189
|
-
let liveActivityMsgId = null;
|
|
190
207
|
let lastLiveResponseKey = '';
|
|
191
208
|
let lastLiveActivityKey = '';
|
|
192
209
|
let liveResponseUpdateVersion = 0;
|
|
193
210
|
let liveActivityUpdateVersion = 0;
|
|
194
|
-
const
|
|
211
|
+
const progressLog = [];
|
|
212
|
+
let thinkingActive = false;
|
|
213
|
+
const thinkingContentParts = [];
|
|
214
|
+
let lastThoughtLabel = '';
|
|
215
|
+
/** Check if text is junk (numbers, very short, not meaningful) */
|
|
216
|
+
const isJunkEntry = (text) => {
|
|
217
|
+
const t = text.trim();
|
|
218
|
+
if (t.length < 5)
|
|
219
|
+
return true;
|
|
220
|
+
if (/^\d+$/.test(t))
|
|
221
|
+
return true;
|
|
222
|
+
// Single word under 8 chars without context (e.g. "Analyzed" alone)
|
|
223
|
+
if (!/\s/.test(t) && t.length < 8)
|
|
224
|
+
return true;
|
|
225
|
+
return false;
|
|
226
|
+
};
|
|
227
|
+
/** Format a single activity line โ collapse multi-line text into one line */
|
|
228
|
+
const formatActivityLine = (raw) => {
|
|
229
|
+
// Collapse newlines into spaces so file references after verbs aren't lost
|
|
230
|
+
// e.g. "Analyzed\npackage.json#L1-75" โ "Analyzed package.json#L1-75"
|
|
231
|
+
const collapsed = (raw || '').replace(/\n+/g, ' ').replace(/\s{2,}/g, ' ').trim();
|
|
232
|
+
if (!collapsed || isJunkEntry(collapsed))
|
|
233
|
+
return '';
|
|
234
|
+
return (0, telegramFormatter_1.escapeHtml)(collapsed.slice(0, 120));
|
|
235
|
+
};
|
|
236
|
+
/** Trim progress log to stay within size limits */
|
|
237
|
+
const trimProgressLog = () => {
|
|
238
|
+
while (progressLog.length > MAX_PROGRESS_ENTRIES)
|
|
239
|
+
progressLog.shift();
|
|
240
|
+
};
|
|
241
|
+
/** Build the progress message body from the ordered event stream */
|
|
242
|
+
const buildProgressBody = () => {
|
|
243
|
+
const lines = [];
|
|
244
|
+
for (const e of progressLog) {
|
|
245
|
+
switch (e.kind) {
|
|
246
|
+
case 'thought':
|
|
247
|
+
lines.push(`๐ญ <i>${(0, telegramFormatter_1.escapeHtml)(e.text)}</i>`);
|
|
248
|
+
break;
|
|
249
|
+
case 'thought-content':
|
|
250
|
+
lines.push(`<i>${(0, telegramFormatter_1.escapeHtml)(e.text)}</i>`);
|
|
251
|
+
break;
|
|
252
|
+
case 'activity':
|
|
253
|
+
lines.push(e.text); // already HTML-escaped
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (thinkingActive) {
|
|
258
|
+
lines.push('๐ญ <i>Thinking...</i>');
|
|
259
|
+
}
|
|
260
|
+
// Use \n\n for spacing between entries (like Antigravity's line gap)
|
|
261
|
+
let body = lines.join('\n\n');
|
|
262
|
+
// Trim from beginning if too long, keeping most recent events
|
|
263
|
+
if (body.length > MAX_PROGRESS_BODY) {
|
|
264
|
+
body = '...\n\n' + body.slice(-MAX_PROGRESS_BODY + 5);
|
|
265
|
+
}
|
|
266
|
+
return body || '<i>Generating...</i>';
|
|
267
|
+
};
|
|
268
|
+
/** Build full progress message with title + body + footer */
|
|
269
|
+
const buildProgressMessage = (title, footer) => {
|
|
270
|
+
const body = buildProgressBody();
|
|
271
|
+
const footerLine = footer ? `\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>` : '';
|
|
272
|
+
return `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${body}${footerLine}`;
|
|
273
|
+
};
|
|
195
274
|
const buildLiveResponseText = (title, rawText, footer, isAlreadyHtml = false, skipTruncation = false) => {
|
|
196
275
|
const normalized = (rawText || '').trim();
|
|
197
276
|
const body = normalized
|
|
198
277
|
? (isAlreadyHtml ? normalized : (0, telegramFormatter_1.formatForTelegram)(normalized))
|
|
199
|
-
: (0, i18n_1.t)('
|
|
278
|
+
: (0, i18n_1.t)('Generating...');
|
|
200
279
|
const truncated = (!skipTruncation && body.length > LIVE_RESPONSE_MAX_LEN)
|
|
201
280
|
? '...(beginning truncated)\n' + body.slice(-LIVE_RESPONSE_MAX_LEN + 30)
|
|
202
281
|
: body;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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);
|
|
282
|
+
const titleLine = title ? `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n` : '';
|
|
283
|
+
const footerLine = footer ? `\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>` : '';
|
|
284
|
+
return `${titleLine}${truncated}${footerLine}`;
|
|
217
285
|
};
|
|
218
286
|
const upsertLiveResponse = (title, rawText, footer, opts) => enqueueResponse(async () => {
|
|
219
287
|
if (opts?.skipWhenFinalized && isFinalized)
|
|
@@ -232,16 +300,18 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
232
300
|
liveResponseMsgId = await sendMsg(text);
|
|
233
301
|
}
|
|
234
302
|
}, 'upsert-response');
|
|
235
|
-
|
|
303
|
+
/** Refresh progress message using the ordered event stream */
|
|
304
|
+
const refreshProgress = (title, footer, opts) => enqueueActivity(async () => {
|
|
236
305
|
if (opts?.skipWhenFinalized && isFinalized)
|
|
237
306
|
return;
|
|
238
307
|
if (opts?.expectedVersion !== undefined && opts.expectedVersion !== liveActivityUpdateVersion)
|
|
239
308
|
return;
|
|
240
|
-
const text =
|
|
241
|
-
|
|
242
|
-
|
|
309
|
+
const text = buildProgressMessage(title, footer);
|
|
310
|
+
// Use progress body hash for dedup
|
|
311
|
+
const bodySnap = progressLog.length + '|' + thinkingActive + '|' + title + '|' + footer;
|
|
312
|
+
if (bodySnap === lastLiveActivityKey && liveActivityMsgId)
|
|
243
313
|
return;
|
|
244
|
-
lastLiveActivityKey =
|
|
314
|
+
lastLiveActivityKey = bodySnap;
|
|
245
315
|
if (liveActivityMsgId) {
|
|
246
316
|
await editMsg(liveActivityMsgId, text);
|
|
247
317
|
}
|
|
@@ -249,6 +319,18 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
249
319
|
liveActivityMsgId = await sendMsg(text);
|
|
250
320
|
}
|
|
251
321
|
}, 'upsert-activity');
|
|
322
|
+
/** Direct message update for special cases (completion, quota, timeout) */
|
|
323
|
+
const setProgressMessage = (htmlContent, opts) => enqueueActivity(async () => {
|
|
324
|
+
if (opts?.expectedVersion !== undefined && opts.expectedVersion !== liveActivityUpdateVersion)
|
|
325
|
+
return;
|
|
326
|
+
lastLiveActivityKey = htmlContent.slice(0, 200);
|
|
327
|
+
if (liveActivityMsgId) {
|
|
328
|
+
await editMsg(liveActivityMsgId, htmlContent);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
liveActivityMsgId = await sendMsg(htmlContent);
|
|
332
|
+
}
|
|
333
|
+
}, 'upsert-activity');
|
|
252
334
|
const sendGeneratedImages = async (responseText) => {
|
|
253
335
|
const imageIntentPattern = /(image|images|png|jpg|jpeg|gif|webp|illustration|diagram|render)/i;
|
|
254
336
|
const imageUrlPattern = /https?:\/\/\S+\.(png|jpg|jpeg|gif|webp)/i;
|
|
@@ -316,7 +398,15 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
316
398
|
return;
|
|
317
399
|
}
|
|
318
400
|
const startTime = Date.now();
|
|
319
|
-
|
|
401
|
+
const progressTitle = () => `${PHASE_ICONS.thinking} ${modelLabel}`;
|
|
402
|
+
const progressFooter = () => `โฑ๏ธ ${Math.round((Date.now() - startTime) / 1000)}s`;
|
|
403
|
+
/** Trigger a progress message refresh */
|
|
404
|
+
const triggerProgressRefresh = () => {
|
|
405
|
+
liveActivityUpdateVersion += 1;
|
|
406
|
+
const v = liveActivityUpdateVersion;
|
|
407
|
+
refreshProgress(progressTitle(), progressFooter(), { expectedVersion: v, skipWhenFinalized: true }).catch(() => { });
|
|
408
|
+
};
|
|
409
|
+
await refreshProgress(progressTitle(), progressFooter());
|
|
320
410
|
monitor = new responseMonitor_1.ResponseMonitor({
|
|
321
411
|
cdpService: cdp,
|
|
322
412
|
pollIntervalMs: 2000,
|
|
@@ -326,12 +416,59 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
326
416
|
onProcessLog: (logText) => {
|
|
327
417
|
if (isFinalized)
|
|
328
418
|
return;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
419
|
+
const trimmed = (logText || '').trim();
|
|
420
|
+
if (!trimmed || isJunkEntry(trimmed))
|
|
421
|
+
return;
|
|
422
|
+
const formatted = formatActivityLine(trimmed);
|
|
423
|
+
if (formatted) {
|
|
424
|
+
progressLog.push({ kind: 'activity', text: formatted });
|
|
425
|
+
trimProgressLog();
|
|
426
|
+
triggerProgressRefresh();
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
onThinkingLog: (thinkingText) => {
|
|
430
|
+
if (isFinalized)
|
|
431
|
+
return;
|
|
432
|
+
const trimmed = (thinkingText || '').trim();
|
|
433
|
+
if (!trimmed)
|
|
434
|
+
return;
|
|
435
|
+
logger_1.logger.debug('[Bot] onThinkingLog received:', trimmed.slice(0, 100));
|
|
436
|
+
const stripped = trimmed.replace(/^[^a-zA-Z]+/, '');
|
|
437
|
+
if (/^thinking\.{0,3}$/i.test(stripped)) {
|
|
438
|
+
// Transient "Thinking..." โ just set flag, don't add entry
|
|
439
|
+
thinkingActive = true;
|
|
440
|
+
}
|
|
441
|
+
else if (/^thought for\s/i.test(stripped)) {
|
|
442
|
+
// Completed thinking cycle: "Thought for 1s"
|
|
443
|
+
thinkingActive = false;
|
|
444
|
+
lastThoughtLabel = trimmed;
|
|
445
|
+
progressLog.push({ kind: 'thought', text: trimmed });
|
|
446
|
+
trimProgressLog();
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// Thinking content โ merge as summary with most recent 'thought' entry
|
|
450
|
+
thinkingContentParts.push(trimmed);
|
|
451
|
+
const firstLine = trimmed.split('\n')[0].trim();
|
|
452
|
+
const heading = firstLine.length > 60 ? firstLine.slice(0, 57) + '...' : firstLine;
|
|
453
|
+
// Find most recent thought entry that doesn't yet have content attached
|
|
454
|
+
let merged = false;
|
|
455
|
+
for (let i = progressLog.length - 1; i >= 0; i--) {
|
|
456
|
+
if (progressLog[i].kind === 'thought') {
|
|
457
|
+
// Only merge if no content heading attached yet (no " โ ")
|
|
458
|
+
if (!progressLog[i].text.includes(' โ ')) {
|
|
459
|
+
progressLog[i].text += ` โ ${heading}`;
|
|
460
|
+
merged = true;
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (!merged && heading.length > 10) {
|
|
466
|
+
// No thought label to merge into โ show as standalone content
|
|
467
|
+
progressLog.push({ kind: 'thought-content', text: heading });
|
|
468
|
+
trimProgressLog();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
triggerProgressRefresh();
|
|
335
472
|
},
|
|
336
473
|
onProgress: (text) => {
|
|
337
474
|
if (isFinalized)
|
|
@@ -342,6 +479,8 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
342
479
|
lastProgressText = separated.output;
|
|
343
480
|
},
|
|
344
481
|
onComplete: async (finalText, meta) => {
|
|
482
|
+
if (isFinalized)
|
|
483
|
+
return; // Guard: prevent duplicate completion
|
|
345
484
|
isFinalized = true;
|
|
346
485
|
if (elapsedTimer) {
|
|
347
486
|
clearInterval(elapsedTimer);
|
|
@@ -357,11 +496,11 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
357
496
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
358
497
|
const isQuotaError = monitor.getPhase() === 'quotaReached' || monitor.getQuotaDetected();
|
|
359
498
|
if (isQuotaError) {
|
|
360
|
-
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
361
499
|
liveActivityUpdateVersion += 1;
|
|
362
|
-
|
|
500
|
+
thinkingActive = false;
|
|
501
|
+
await setProgressMessage(`<b>โ ๏ธ ${(0, telegramFormatter_1.escapeHtml)(modelLabel)} ยท Quota Reached</b>\n\n${buildProgressBody()}\n\n<i>โฑ๏ธ ${elapsed}s</i>`, { expectedVersion: liveActivityUpdateVersion });
|
|
363
502
|
liveResponseUpdateVersion += 1;
|
|
364
|
-
await upsertLiveResponse('โ ๏ธ
|
|
503
|
+
await upsertLiveResponse('โ ๏ธ Quota Reached', 'Model quota limit reached. Please wait or switch to a different model.', `โฑ๏ธ ${elapsed}s`, { expectedVersion: liveResponseUpdateVersion });
|
|
365
504
|
try {
|
|
366
505
|
const payload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
367
506
|
if (payload) {
|
|
@@ -373,32 +512,137 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
373
512
|
}
|
|
374
513
|
return;
|
|
375
514
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
515
|
+
// Fresh DOM re-extraction at completion time to ensure we get the
|
|
516
|
+
// complete response โ polling may have captured partial/stale text.
|
|
517
|
+
let freshText = '';
|
|
518
|
+
let freshIsHtml = false;
|
|
519
|
+
try {
|
|
520
|
+
const contextId = cdp.getPrimaryContextId();
|
|
521
|
+
const evalParams = {
|
|
522
|
+
expression: (0, assistantDomExtractor_1.extractAssistantSegmentsPayloadScript)(),
|
|
523
|
+
returnByValue: true,
|
|
524
|
+
awaitPromise: true,
|
|
525
|
+
};
|
|
526
|
+
if (contextId !== null && contextId !== undefined)
|
|
527
|
+
evalParams.contextId = contextId;
|
|
528
|
+
const freshResult = await cdp.call('Runtime.evaluate', evalParams);
|
|
529
|
+
const freshClassified = (0, assistantDomExtractor_1.classifyAssistantSegments)(freshResult?.result?.value);
|
|
530
|
+
if (freshClassified.diagnostics.source === 'dom-structured' && freshClassified.finalOutputText.trim()) {
|
|
531
|
+
freshText = freshClassified.finalOutputText.trim();
|
|
532
|
+
freshIsHtml = true;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch (e) {
|
|
536
|
+
logger_1.logger.debug('[onComplete] Fresh structured extraction failed:', e);
|
|
537
|
+
}
|
|
538
|
+
// Pick the best text: fresh extraction > polled finalText > lastProgressText > emergency
|
|
539
|
+
const polledText = (finalText && finalText.trim().length > 0) ? finalText : lastProgressText;
|
|
540
|
+
const bestPolled = polledText && polledText.trim().length > 0 ? polledText : '';
|
|
541
|
+
// Prefer the fresh extraction if it's at least as long (more complete)
|
|
542
|
+
let finalResponseText;
|
|
543
|
+
let isAlreadyHtml;
|
|
544
|
+
if (freshText && freshText.length >= bestPolled.length) {
|
|
545
|
+
finalResponseText = freshText;
|
|
546
|
+
isAlreadyHtml = freshIsHtml;
|
|
547
|
+
}
|
|
548
|
+
else if (bestPolled) {
|
|
549
|
+
finalResponseText = bestPolled;
|
|
550
|
+
isAlreadyHtml = meta?.source === 'structured';
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const emergencyText = await tryEmergencyExtractText();
|
|
554
|
+
finalResponseText = emergencyText;
|
|
555
|
+
isAlreadyHtml = false;
|
|
556
|
+
}
|
|
380
557
|
const separated = isAlreadyHtml ? { output: finalResponseText, logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(finalResponseText);
|
|
381
558
|
const finalOutputText = separated.output || finalResponseText;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
559
|
+
// Send collapsible thinking block as a separate message before the response.
|
|
560
|
+
// Extract both label and content directly from DOM at completion time,
|
|
561
|
+
// so we don't depend on polling (2s interval) having captured thinking events.
|
|
562
|
+
try {
|
|
563
|
+
const thinkExtract = await cdp.call('Runtime.evaluate', {
|
|
564
|
+
expression: `(function() {
|
|
565
|
+
var panel = document.querySelector('.antigravity-agent-side-panel');
|
|
566
|
+
var scope = panel || document;
|
|
567
|
+
var details = scope.querySelectorAll('details');
|
|
568
|
+
var blocks = [];
|
|
569
|
+
for (var i = 0; i < details.length; i++) {
|
|
570
|
+
var d = details[i];
|
|
571
|
+
var summary = d.querySelector('summary');
|
|
572
|
+
if (!summary) continue;
|
|
573
|
+
var rawLabel = (summary.textContent || '').trim();
|
|
574
|
+
var stripped = rawLabel.replace(/^[^a-zA-Z]+/, '');
|
|
575
|
+
if (!/^(?:thought for|thinking)\\b/i.test(stripped)) continue;
|
|
576
|
+
var wasOpen = d.open;
|
|
577
|
+
if (!wasOpen) d.open = true;
|
|
578
|
+
// Try children first, then fall back to full textContent minus summary
|
|
579
|
+
var children = d.children;
|
|
580
|
+
var parts = [];
|
|
581
|
+
for (var c = 0; c < children.length; c++) {
|
|
582
|
+
if (children[c].tagName === 'SUMMARY' || children[c].tagName === 'STYLE') continue;
|
|
583
|
+
var t = (children[c].innerText || children[c].textContent || '').trim();
|
|
584
|
+
if (t && t.length >= 5) parts.push(t);
|
|
585
|
+
}
|
|
586
|
+
// Fallback: use detail's full text minus the summary text
|
|
587
|
+
if (parts.length === 0) {
|
|
588
|
+
var fullText = (d.innerText || d.textContent || '').trim();
|
|
589
|
+
var bodyText = fullText.replace(rawLabel, '').trim();
|
|
590
|
+
if (bodyText && bodyText.length >= 5) parts.push(bodyText);
|
|
591
|
+
}
|
|
592
|
+
if (!wasOpen) d.open = false;
|
|
593
|
+
blocks.push({ label: rawLabel, body: parts.join('\\n\\n') });
|
|
594
|
+
}
|
|
595
|
+
return blocks;
|
|
596
|
+
})()`,
|
|
597
|
+
returnByValue: true,
|
|
598
|
+
});
|
|
599
|
+
const thinkBlocks = Array.isArray(thinkExtract?.result?.value) ? thinkExtract.result.value : [];
|
|
600
|
+
if (thinkBlocks.length > 0) {
|
|
601
|
+
// Also incorporate poll-accumulated content if available
|
|
602
|
+
const accumulatedBody = thinkingContentParts.join('\n\n');
|
|
603
|
+
// Build combined thinking message โ merge all blocks
|
|
604
|
+
const sections = [];
|
|
605
|
+
for (const block of thinkBlocks) {
|
|
606
|
+
const label = block.label || lastThoughtLabel || 'Thinking';
|
|
607
|
+
const body = block.body || accumulatedBody || '';
|
|
608
|
+
if (body) {
|
|
609
|
+
sections.push(` ๐ญ <b>${(0, telegramFormatter_1.escapeHtml)(label)}</b>\n\n<i>${(0, telegramFormatter_1.escapeHtml)(body)}</i>`);
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
sections.push(` ๐ญ <b>${(0, telegramFormatter_1.escapeHtml)(label)}</b>`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const combined = sections.join('\n\n');
|
|
616
|
+
const maxThinkLen = TELEGRAM_MSG_LIMIT - 100;
|
|
617
|
+
const trimmed = combined.length > maxThinkLen ? combined.slice(0, maxThinkLen) + '...' : combined;
|
|
618
|
+
const thinkMsg = `<blockquote expandable>${trimmed}</blockquote>`;
|
|
619
|
+
logger_1.logger.info(`[Bot] Sending thinking block: ${thinkBlocks.length} block(s), ${combined.length} chars`);
|
|
620
|
+
await sendMsg(thinkMsg);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
logger_1.logger.info('[Bot] No thinking blocks found in DOM at completion time');
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
catch (e) {
|
|
627
|
+
logger_1.logger.error('[Bot] Failed to send thinking block:', e);
|
|
386
628
|
}
|
|
387
629
|
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
388
630
|
logger_1.logger.divider(`Output (${finalOutputText.length} chars)`);
|
|
389
631
|
console.info(finalOutputText);
|
|
390
632
|
}
|
|
391
633
|
logger_1.logger.divider();
|
|
634
|
+
// Compact progress message: show completed title + event log
|
|
392
635
|
liveActivityUpdateVersion += 1;
|
|
393
|
-
|
|
636
|
+
thinkingActive = false;
|
|
637
|
+
const completedBody = buildProgressBody();
|
|
638
|
+
await setProgressMessage(`<b>${PHASE_ICONS.complete} ${(0, telegramFormatter_1.escapeHtml)(modelLabel)} ยท ${elapsed}s</b>\n\n${completedBody}`, { expectedVersion: liveActivityUpdateVersion });
|
|
394
639
|
liveResponseUpdateVersion += 1;
|
|
395
640
|
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
await sendChunkedResponse(title, footer, finalOutputText, isAlreadyHtml);
|
|
641
|
+
const footer = `โฑ๏ธ ${elapsed}s`;
|
|
642
|
+
await sendChunkedResponse('', footer, finalOutputText, isAlreadyHtml);
|
|
399
643
|
}
|
|
400
644
|
else {
|
|
401
|
-
await upsertLiveResponse(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use /screenshot to verify.'),
|
|
645
|
+
await upsertLiveResponse(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use /screenshot to verify.'), `โฑ๏ธ ${elapsed}s`, { expectedVersion: liveResponseUpdateVersion });
|
|
402
646
|
}
|
|
403
647
|
if (options) {
|
|
404
648
|
try {
|
|
@@ -450,16 +694,14 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
450
694
|
const timeoutText = (lastText && lastText.trim().length > 0) ? lastText : lastProgressText;
|
|
451
695
|
const timeoutIsHtml = monitor.getLastExtractionSource() === 'structured';
|
|
452
696
|
const separated = timeoutIsHtml ? { output: timeoutText || '', logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(timeoutText || '');
|
|
453
|
-
const sanitizedTimeoutLogs = lastActivityLogText || processLogBuffer.snapshot();
|
|
454
697
|
const payload = separated.output && separated.output.trim().length > 0
|
|
455
698
|
? `${separated.output}\n\n[Monitor Ended] Timeout after 30 minutes.`
|
|
456
699
|
: 'Monitor ended after 30 minutes. No text was retrieved.';
|
|
457
700
|
liveResponseUpdateVersion += 1;
|
|
458
|
-
|
|
459
|
-
const timeoutFooter = `โฑ๏ธ Elapsed: ${elapsed}s | Timeout`;
|
|
460
|
-
await sendChunkedResponse(timeoutTitle, timeoutFooter, payload, timeoutIsHtml);
|
|
701
|
+
await sendChunkedResponse(`${PHASE_ICONS.timeout} Timeout`, `โฑ๏ธ ${elapsed}s`, payload, timeoutIsHtml);
|
|
461
702
|
liveActivityUpdateVersion += 1;
|
|
462
|
-
|
|
703
|
+
thinkingActive = false;
|
|
704
|
+
await setProgressMessage(`<b>${PHASE_ICONS.timeout} ${(0, telegramFormatter_1.escapeHtml)(modelLabel)} ยท ${elapsed}s</b>\n\n${buildProgressBody()}`, { expectedVersion: liveActivityUpdateVersion });
|
|
463
705
|
}
|
|
464
706
|
catch (error) {
|
|
465
707
|
logger_1.logger.error(`[sendPrompt:${monitorTraceId}] onTimeout failed:`, error);
|
|
@@ -472,10 +714,7 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
472
714
|
clearInterval(elapsedTimer);
|
|
473
715
|
return;
|
|
474
716
|
}
|
|
475
|
-
|
|
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(() => { });
|
|
717
|
+
triggerProgressRefresh();
|
|
479
718
|
}, 5000);
|
|
480
719
|
}
|
|
481
720
|
catch (e) {
|
|
@@ -520,6 +759,34 @@ const startBot = async (cliLogLevel) => {
|
|
|
520
759
|
const cleanupHandler = new cleanupCommandHandler_1.CleanupCommandHandler(chatSessionRepo, workspaceBindingRepo);
|
|
521
760
|
const bot = new grammy_1.Bot(config.telegramBotToken);
|
|
522
761
|
bridge.botApi = bot.api;
|
|
762
|
+
// Notify user on WebSocket connection lifecycle events
|
|
763
|
+
bridge.pool.on('workspace:disconnected', (projectName) => {
|
|
764
|
+
const channel = bridge.lastActiveChannel;
|
|
765
|
+
if (!channel || !bridge.botApi)
|
|
766
|
+
return;
|
|
767
|
+
bridge.botApi.sendMessage(channel.chatId, `โ ๏ธ <b>${(0, telegramFormatter_1.escapeHtml)(projectName)}</b>: Connection lost. Reconnectingโฆ`, {
|
|
768
|
+
parse_mode: 'HTML',
|
|
769
|
+
message_thread_id: channel.threadId,
|
|
770
|
+
}).catch((err) => logger_1.logger.error('[Bot] Failed to send disconnect notification:', err));
|
|
771
|
+
});
|
|
772
|
+
bridge.pool.on('workspace:reconnected', (projectName) => {
|
|
773
|
+
const channel = bridge.lastActiveChannel;
|
|
774
|
+
if (!channel || !bridge.botApi)
|
|
775
|
+
return;
|
|
776
|
+
bridge.botApi.sendMessage(channel.chatId, `โ
<b>${(0, telegramFormatter_1.escapeHtml)(projectName)}</b>: Reconnected.`, {
|
|
777
|
+
parse_mode: 'HTML',
|
|
778
|
+
message_thread_id: channel.threadId,
|
|
779
|
+
}).catch((err) => logger_1.logger.error('[Bot] Failed to send reconnect notification:', err));
|
|
780
|
+
});
|
|
781
|
+
bridge.pool.on('workspace:reconnectFailed', (projectName) => {
|
|
782
|
+
const channel = bridge.lastActiveChannel;
|
|
783
|
+
if (!channel || !bridge.botApi)
|
|
784
|
+
return;
|
|
785
|
+
bridge.botApi.sendMessage(channel.chatId, `โ <b>${(0, telegramFormatter_1.escapeHtml)(projectName)}</b>: Reconnection failed. Send a message to retry.`, {
|
|
786
|
+
parse_mode: 'HTML',
|
|
787
|
+
message_thread_id: channel.threadId,
|
|
788
|
+
}).catch((err) => logger_1.logger.error('[Bot] Failed to send reconnect-failed notification:', err));
|
|
789
|
+
});
|
|
523
790
|
const topicManager = new telegramTopicManager_1.TelegramTopicManager(bot.api, 0);
|
|
524
791
|
// Auth middleware
|
|
525
792
|
bot.use(async (ctx, next) => {
|
|
@@ -606,11 +873,14 @@ const startBot = async (cliLogLevel) => {
|
|
|
606
873
|
});
|
|
607
874
|
// /model command
|
|
608
875
|
bot.command('model', async (ctx) => {
|
|
876
|
+
const ch = getChannel(ctx);
|
|
877
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
878
|
+
const getCdp = () => resolved?.cdp ?? (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
609
879
|
const modelName = ctx.match?.trim();
|
|
610
880
|
if (modelName) {
|
|
611
|
-
const cdp = (
|
|
881
|
+
const cdp = getCdp();
|
|
612
882
|
if (!cdp) {
|
|
613
|
-
await ctx.reply('Not connected to CDP.');
|
|
883
|
+
await ctx.reply('Not connected to CDP. Send a message first to connect.');
|
|
614
884
|
return;
|
|
615
885
|
}
|
|
616
886
|
const res = await cdp.setUiModel(modelName);
|
|
@@ -622,7 +892,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
622
892
|
}
|
|
623
893
|
}
|
|
624
894
|
else {
|
|
625
|
-
await (0, modelsUi_1.sendModelsUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, { getCurrentCdp:
|
|
895
|
+
await (0, modelsUi_1.sendModelsUI)(async (text, keyboard) => { await replyHtml(ctx, text, keyboard); }, { getCurrentCdp: getCdp, fetchQuota: async () => bridge.quota.fetchQuota() });
|
|
626
896
|
}
|
|
627
897
|
});
|
|
628
898
|
// /template command
|
|
@@ -1051,7 +1321,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
1051
1321
|
}
|
|
1052
1322
|
return;
|
|
1053
1323
|
}
|
|
1054
|
-
// Planning buttons
|
|
1324
|
+
// Planning buttons (legacy parsing for backward compat)
|
|
1055
1325
|
const planningAction = (0, cdpBridgeManager_1.parsePlanningCustomId)(data);
|
|
1056
1326
|
if (planningAction) {
|
|
1057
1327
|
const projectName = planningAction.projectName ?? bridge.lastActiveWorkspace;
|
|
@@ -1072,8 +1342,12 @@ const startBot = async (cliLogLevel) => {
|
|
|
1072
1342
|
await new Promise(r => setTimeout(r, 500));
|
|
1073
1343
|
}
|
|
1074
1344
|
if (planContent) {
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1345
|
+
const chKey = channelKey(ch);
|
|
1346
|
+
const pages = (0, planUi_1.paginatePlanContent)(planContent);
|
|
1347
|
+
planContentCache.set(chKey, pages);
|
|
1348
|
+
const targetChannelStr = ch.threadId ? String(ch.threadId) : String(ch.chatId);
|
|
1349
|
+
const { text: pageText, keyboard: pageKeyboard } = (0, planUi_1.buildPlanContentUI)(pages, 0, projectName || '', targetChannelStr);
|
|
1350
|
+
await bot.api.sendMessage(ch.chatId, pageText, { parse_mode: 'HTML', message_thread_id: ch.threadId, reply_markup: pageKeyboard });
|
|
1077
1351
|
}
|
|
1078
1352
|
}
|
|
1079
1353
|
await ctx.answerCallbackQuery({ text: clicked ? 'Opened' : 'Open button not found.' });
|
|
@@ -1089,6 +1363,104 @@ const startBot = async (cliLogLevel) => {
|
|
|
1089
1363
|
}
|
|
1090
1364
|
return;
|
|
1091
1365
|
}
|
|
1366
|
+
// New plan UI buttons (View/Proceed/Edit/Refresh)
|
|
1367
|
+
if (data.startsWith(planUi_1.PLAN_VIEW_BTN + ':')) {
|
|
1368
|
+
const suffix = data.substring(planUi_1.PLAN_VIEW_BTN.length + 1);
|
|
1369
|
+
const [projectName] = suffix.split(':');
|
|
1370
|
+
const detector = projectName ? bridge.pool.getPlanningDetector(projectName) : undefined;
|
|
1371
|
+
if (!detector) {
|
|
1372
|
+
await ctx.answerCallbackQuery({ text: 'Planning detector not found.' });
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const clicked = await detector.clickOpenButton();
|
|
1376
|
+
if (clicked) {
|
|
1377
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1378
|
+
let planContent = null;
|
|
1379
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1380
|
+
planContent = await detector.extractPlanContent();
|
|
1381
|
+
if (planContent)
|
|
1382
|
+
break;
|
|
1383
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1384
|
+
}
|
|
1385
|
+
if (planContent) {
|
|
1386
|
+
const chKey = channelKey(ch);
|
|
1387
|
+
const pages = (0, planUi_1.paginatePlanContent)(planContent);
|
|
1388
|
+
planContentCache.set(chKey, pages);
|
|
1389
|
+
const targetChannelStr = ch.threadId ? String(ch.threadId) : String(ch.chatId);
|
|
1390
|
+
const { text: pageText, keyboard: pageKeyboard } = (0, planUi_1.buildPlanContentUI)(pages, 0, projectName, targetChannelStr);
|
|
1391
|
+
await bot.api.sendMessage(ch.chatId, pageText, { parse_mode: 'HTML', message_thread_id: ch.threadId, reply_markup: pageKeyboard });
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Opened' : 'Open button not found.' });
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (data.startsWith(planUi_1.PLAN_PROCEED_BTN + ':')) {
|
|
1398
|
+
const suffix = data.substring(planUi_1.PLAN_PROCEED_BTN.length + 1);
|
|
1399
|
+
const [projectName] = suffix.split(':');
|
|
1400
|
+
const detector = projectName ? bridge.pool.getPlanningDetector(projectName) : undefined;
|
|
1401
|
+
if (!detector) {
|
|
1402
|
+
await ctx.answerCallbackQuery({ text: 'Planning detector not found.' });
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const clicked = await detector.clickProceedButton();
|
|
1406
|
+
if (clicked) {
|
|
1407
|
+
planEditPendingChannels.delete(channelKey(ch));
|
|
1408
|
+
try {
|
|
1409
|
+
await ctx.editMessageReplyMarkup({ reply_markup: undefined });
|
|
1410
|
+
}
|
|
1411
|
+
catch { }
|
|
1412
|
+
}
|
|
1413
|
+
await ctx.answerCallbackQuery({ text: clicked ? 'Proceeding...' : 'Proceed button not found.' });
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
if (data.startsWith(planUi_1.PLAN_EDIT_BTN + ':')) {
|
|
1417
|
+
const suffix = data.substring(planUi_1.PLAN_EDIT_BTN.length + 1);
|
|
1418
|
+
const [projectName] = suffix.split(':');
|
|
1419
|
+
planEditPendingChannels.set(channelKey(ch), { projectName });
|
|
1420
|
+
await ctx.answerCallbackQuery({ text: 'Type your edit instructions (or /cancel).' });
|
|
1421
|
+
await bot.api.sendMessage(ch.chatId, '<b>Edit Plan</b>\n\nType your plan edit instructions below.\nSend <code>/cancel</code> to cancel.', { parse_mode: 'HTML', message_thread_id: ch.threadId });
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (data.startsWith(planUi_1.PLAN_REFRESH_BTN + ':')) {
|
|
1425
|
+
const suffix = data.substring(planUi_1.PLAN_REFRESH_BTN.length + 1);
|
|
1426
|
+
const [projectName, targetChannelStr] = suffix.split(':');
|
|
1427
|
+
const detector = projectName ? bridge.pool.getPlanningDetector(projectName) : undefined;
|
|
1428
|
+
if (!detector) {
|
|
1429
|
+
await ctx.answerCallbackQuery({ text: 'Planning detector not found.' });
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const info = detector.getLastDetectedInfo();
|
|
1433
|
+
if (info) {
|
|
1434
|
+
const { text: uiText, keyboard: uiKeyboard } = (0, planUi_1.buildPlanNotificationUI)(info, projectName, targetChannelStr || String(ch.chatId));
|
|
1435
|
+
try {
|
|
1436
|
+
await ctx.editMessageText(uiText, { parse_mode: 'HTML', reply_markup: uiKeyboard });
|
|
1437
|
+
}
|
|
1438
|
+
catch { }
|
|
1439
|
+
}
|
|
1440
|
+
await ctx.answerCallbackQuery({ text: 'Refreshed' });
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
// Plan pagination
|
|
1444
|
+
if (data.startsWith(planUi_1.PLAN_PAGE_PREFIX + ':')) {
|
|
1445
|
+
const rest = data.substring(planUi_1.PLAN_PAGE_PREFIX.length + 1);
|
|
1446
|
+
const colonIdx = rest.indexOf(':');
|
|
1447
|
+
const page = parseInt(rest.substring(0, colonIdx), 10);
|
|
1448
|
+
const suffix = rest.substring(colonIdx + 1);
|
|
1449
|
+
const [projectName, targetChannelStr] = suffix.split(':');
|
|
1450
|
+
const chKey = channelKey(ch);
|
|
1451
|
+
const pages = planContentCache.get(chKey);
|
|
1452
|
+
if (!pages || isNaN(page)) {
|
|
1453
|
+
await ctx.answerCallbackQuery({ text: 'Page not found.' });
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
const { text: pageText, keyboard: pageKeyboard } = (0, planUi_1.buildPlanContentUI)(pages, page, projectName, targetChannelStr || String(ch.chatId));
|
|
1457
|
+
try {
|
|
1458
|
+
await ctx.editMessageText(pageText, { parse_mode: 'HTML', reply_markup: pageKeyboard });
|
|
1459
|
+
}
|
|
1460
|
+
catch { }
|
|
1461
|
+
await ctx.answerCallbackQuery({ text: `Page ${page + 1}/${pages.length}` });
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1092
1464
|
// Error popup buttons
|
|
1093
1465
|
const errorAction = (0, cdpBridgeManager_1.parseErrorPopupCustomId)(data);
|
|
1094
1466
|
if (errorAction) {
|
|
@@ -1187,6 +1559,32 @@ const startBot = async (cliLogLevel) => {
|
|
|
1187
1559
|
const text = ctx.message.text.trim();
|
|
1188
1560
|
if (!text)
|
|
1189
1561
|
return;
|
|
1562
|
+
// Plan edit interception
|
|
1563
|
+
const pendingPlanEdit = planEditPendingChannels.get(key);
|
|
1564
|
+
if (pendingPlanEdit) {
|
|
1565
|
+
if (text === '/cancel') {
|
|
1566
|
+
planEditPendingChannels.delete(key);
|
|
1567
|
+
await ctx.reply('Plan edit cancelled.');
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
planEditPendingChannels.delete(key);
|
|
1571
|
+
const editPrompt = `Please revise the plan based on the following feedback:\n\n${text}`;
|
|
1572
|
+
const resolved = await resolveWorkspaceAndCdp(ch);
|
|
1573
|
+
const cdp = resolved?.cdp ?? (0, cdpBridgeManager_1.getCurrentCdp)(bridge);
|
|
1574
|
+
if (!cdp) {
|
|
1575
|
+
await ctx.reply('Not connected to CDP.');
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
await ctx.reply('Sending plan edit...');
|
|
1579
|
+
await promptDispatcher.send({
|
|
1580
|
+
channel: ch,
|
|
1581
|
+
prompt: editPrompt,
|
|
1582
|
+
cdp,
|
|
1583
|
+
inboundImages: [],
|
|
1584
|
+
options: { chatSessionService, chatSessionRepo, topicManager, titleGenerator },
|
|
1585
|
+
});
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1190
1588
|
// Check if it looks like a text command
|
|
1191
1589
|
const parsed = (0, messageParser_1.parseMessageContent)(text);
|
|
1192
1590
|
if (parsed.isCommand && parsed.commandName) {
|