remoat 0.2.9 → 0.2.11
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/dist/bin/cli.js +0 -0
- package/dist/bin/postinstall.d.ts +23 -0
- package/dist/bin/postinstall.js +64 -0
- package/dist/bin/postinstall.js.map +1 -0
- package/dist/bot/index.js +325 -94
- package/dist/bot/index.js.map +1 -1
- package/dist/services/assistantDomExtractor.js +30 -5
- package/dist/services/assistantDomExtractor.js.map +1 -1
- package/dist/services/cdpBridgeManager.js +12 -4
- 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 +14 -4
- package/dist/services/cdpService.js +89 -32
- package/dist/services/cdpService.js.map +1 -1
- package/dist/services/chatSessionService.js +1 -1
- package/dist/services/chatSessionService.js.map +1 -1
- package/dist/services/responseMonitor.d.ts +2 -0
- package/dist/services/responseMonitor.js +20 -12
- package/dist/services/responseMonitor.js.map +1 -1
- package/dist/utils/htmlToTelegramMarkdown.js +7 -4
- package/dist/utils/htmlToTelegramMarkdown.js.map +1 -1
- package/locales/en.json +1 -1
- package/locales/ja.json +1 -1
- package/package.json +1 -1
- package/dist/services/antigravityLauncher.d.ts +0 -7
- package/dist/services/antigravityLauncher.js +0 -94
- package/dist/services/antigravityLauncher.js.map +0 -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");
|
|
@@ -69,7 +69,7 @@ function stripHtmlForFile(html) {
|
|
|
69
69
|
// Links
|
|
70
70
|
text = text.replace(/<a\s+href="([^"]*)">([\s\S]*?)<\/a>/gi, '[$2]($1)');
|
|
71
71
|
// Blockquotes
|
|
72
|
-
text = text.replace(/<blockquote
|
|
72
|
+
text = text.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_m, content) => content.split('\n').map((l) => `> ${l}`).join('\n'));
|
|
73
73
|
// Strip remaining tags
|
|
74
74
|
text = text.replace(/<[^>]+>/g, '');
|
|
75
75
|
// Decode entities
|
|
@@ -142,7 +142,9 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
142
142
|
/** Send a potentially long response, splitting into chunks and attaching a .md file if needed. */
|
|
143
143
|
const sendChunkedResponse = async (title, footer, rawBody, isAlreadyHtml) => {
|
|
144
144
|
const formattedBody = isAlreadyHtml ? rawBody : (0, telegramFormatter_1.formatForTelegram)(rawBody);
|
|
145
|
-
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}`;
|
|
146
148
|
if (fullMsg.length <= TELEGRAM_MSG_LIMIT) {
|
|
147
149
|
await upsertLiveResponse(title, rawBody, footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml, skipTruncation: true });
|
|
148
150
|
return;
|
|
@@ -154,10 +156,12 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
154
156
|
for (let pi = 0; pi < inlineCount; pi++) {
|
|
155
157
|
const partLabel = hasFile ? `(${pi + 1}/${inlineCount}+file)` : `(${pi + 1}/${total})`;
|
|
156
158
|
if (pi === 0) {
|
|
157
|
-
|
|
159
|
+
const firstTitle = title ? `${title} ${partLabel}` : partLabel;
|
|
160
|
+
await upsertLiveResponse(firstTitle, bodyChunks[pi], footer, { expectedVersion: liveResponseUpdateVersion, isAlreadyHtml: true, skipTruncation: true });
|
|
158
161
|
}
|
|
159
162
|
else {
|
|
160
|
-
|
|
163
|
+
const partFooter = footer ? `${(0, telegramFormatter_1.escapeHtml)(footer)} ${partLabel}` : partLabel;
|
|
164
|
+
await sendMsg(`${bodyChunks[pi]}\n\n<i>${partFooter}</i>`);
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
if (hasFile) {
|
|
@@ -182,60 +186,102 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
182
186
|
const localMode = modeService.getCurrentMode();
|
|
183
187
|
const modeName = modeService_1.MODE_UI_NAMES[localMode] || localMode;
|
|
184
188
|
const currentModel = (await cdp.getCurrentModel()) || modelService.getCurrentModel();
|
|
185
|
-
|
|
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
|
+
}
|
|
186
200
|
let isFinalized = false;
|
|
187
201
|
let elapsedTimer = null;
|
|
188
202
|
let lastProgressText = '';
|
|
189
|
-
let lastActivityLogText = '';
|
|
190
|
-
let lastThinkingLogText = '';
|
|
191
203
|
const LIVE_RESPONSE_MAX_LEN = 3800;
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const ACTIVITY_BUDGET = 2300;
|
|
195
|
-
const processLogBuffer = new processLogBuffer_1.ProcessLogBuffer({ maxChars: LIVE_ACTIVITY_MAX_LEN, maxEntries: 120, maxEntryLength: 220 });
|
|
204
|
+
const MAX_PROGRESS_BODY = 3500;
|
|
205
|
+
const MAX_PROGRESS_ENTRIES = 60;
|
|
196
206
|
let liveResponseMsgId = null;
|
|
197
|
-
let liveActivityMsgId = null;
|
|
198
207
|
let lastLiveResponseKey = '';
|
|
199
208
|
let lastLiveActivityKey = '';
|
|
200
209
|
let liveResponseUpdateVersion = 0;
|
|
201
210
|
let liveActivityUpdateVersion = 0;
|
|
202
|
-
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
|
+
};
|
|
203
274
|
const buildLiveResponseText = (title, rawText, footer, isAlreadyHtml = false, skipTruncation = false) => {
|
|
204
275
|
const normalized = (rawText || '').trim();
|
|
205
276
|
const body = normalized
|
|
206
277
|
? (isAlreadyHtml ? normalized : (0, telegramFormatter_1.formatForTelegram)(normalized))
|
|
207
|
-
: (0, i18n_1.t)('
|
|
278
|
+
: (0, i18n_1.t)('Generating...');
|
|
208
279
|
const truncated = (!skipTruncation && body.length > LIVE_RESPONSE_MAX_LEN)
|
|
209
280
|
? '...(beginning truncated)\n' + body.slice(-LIVE_RESPONSE_MAX_LEN + 30)
|
|
210
281
|
: body;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const normalized = (rawText || '').trim();
|
|
215
|
-
const thinkingNormalized = (lastThinkingLogText || '').trim();
|
|
216
|
-
const hasThinking = thinkingNormalized.length > 10;
|
|
217
|
-
let body;
|
|
218
|
-
if (hasThinking) {
|
|
219
|
-
const thinkingTruncated = thinkingNormalized.length > THINKING_BUDGET
|
|
220
|
-
? '...' + thinkingNormalized.slice(-THINKING_BUDGET + 3)
|
|
221
|
-
: thinkingNormalized;
|
|
222
|
-
const activityBody = normalized
|
|
223
|
-
? (0, streamMessageFormatter_1.fitForSingleEmbedDescription)((0, telegramFormatter_1.formatForTelegram)(normalized), ACTIVITY_BUDGET)
|
|
224
|
-
: ACTIVITY_PLACEHOLDER;
|
|
225
|
-
body = `\u{1F9E0} <b>AI Thinking</b>\n<blockquote>${(0, telegramFormatter_1.escapeHtml)(thinkingTruncated)}</blockquote>\n\n\u{1F4CB} <b>Activity</b>\n${activityBody}`;
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
body = normalized
|
|
229
|
-
? (0, streamMessageFormatter_1.fitForSingleEmbedDescription)((0, telegramFormatter_1.formatForTelegram)(normalized), LIVE_ACTIVITY_MAX_LEN)
|
|
230
|
-
: ACTIVITY_PLACEHOLDER;
|
|
231
|
-
}
|
|
232
|
-
return `<b>${(0, telegramFormatter_1.escapeHtml)(title)}</b>\n\n${body}\n\n<i>${(0, telegramFormatter_1.escapeHtml)(footer)}</i>`;
|
|
233
|
-
};
|
|
234
|
-
const appendProcessLogs = (text) => {
|
|
235
|
-
const normalized = (text || '').trim();
|
|
236
|
-
if (!normalized)
|
|
237
|
-
return processLogBuffer.snapshot();
|
|
238
|
-
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}`;
|
|
239
285
|
};
|
|
240
286
|
const upsertLiveResponse = (title, rawText, footer, opts) => enqueueResponse(async () => {
|
|
241
287
|
if (opts?.skipWhenFinalized && isFinalized)
|
|
@@ -254,16 +300,18 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
254
300
|
liveResponseMsgId = await sendMsg(text);
|
|
255
301
|
}
|
|
256
302
|
}, 'upsert-response');
|
|
257
|
-
|
|
303
|
+
/** Refresh progress message using the ordered event stream */
|
|
304
|
+
const refreshProgress = (title, footer, opts) => enqueueActivity(async () => {
|
|
258
305
|
if (opts?.skipWhenFinalized && isFinalized)
|
|
259
306
|
return;
|
|
260
307
|
if (opts?.expectedVersion !== undefined && opts.expectedVersion !== liveActivityUpdateVersion)
|
|
261
308
|
return;
|
|
262
|
-
const text =
|
|
263
|
-
|
|
264
|
-
|
|
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)
|
|
265
313
|
return;
|
|
266
|
-
lastLiveActivityKey =
|
|
314
|
+
lastLiveActivityKey = bodySnap;
|
|
267
315
|
if (liveActivityMsgId) {
|
|
268
316
|
await editMsg(liveActivityMsgId, text);
|
|
269
317
|
}
|
|
@@ -271,6 +319,18 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
271
319
|
liveActivityMsgId = await sendMsg(text);
|
|
272
320
|
}
|
|
273
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');
|
|
274
334
|
const sendGeneratedImages = async (responseText) => {
|
|
275
335
|
const imageIntentPattern = /(image|images|png|jpg|jpeg|gif|webp|illustration|diagram|render)/i;
|
|
276
336
|
const imageUrlPattern = /https?:\/\/\S+\.(png|jpg|jpeg|gif|webp)/i;
|
|
@@ -338,7 +398,15 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
338
398
|
return;
|
|
339
399
|
}
|
|
340
400
|
const startTime = Date.now();
|
|
341
|
-
|
|
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());
|
|
342
410
|
monitor = new responseMonitor_1.ResponseMonitor({
|
|
343
411
|
cdpService: cdp,
|
|
344
412
|
pollIntervalMs: 2000,
|
|
@@ -348,27 +416,59 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
348
416
|
onProcessLog: (logText) => {
|
|
349
417
|
if (isFinalized)
|
|
350
418
|
return;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
}
|
|
357
428
|
},
|
|
358
429
|
onThinkingLog: (thinkingText) => {
|
|
359
430
|
if (isFinalized)
|
|
360
431
|
return;
|
|
361
|
-
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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();
|
|
367
447
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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();
|
|
372
472
|
},
|
|
373
473
|
onProgress: (text) => {
|
|
374
474
|
if (isFinalized)
|
|
@@ -396,11 +496,11 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
396
496
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
397
497
|
const isQuotaError = monitor.getPhase() === 'quotaReached' || monitor.getQuotaDetected();
|
|
398
498
|
if (isQuotaError) {
|
|
399
|
-
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
400
499
|
liveActivityUpdateVersion += 1;
|
|
401
|
-
|
|
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 });
|
|
402
502
|
liveResponseUpdateVersion += 1;
|
|
403
|
-
await upsertLiveResponse('⚠️
|
|
503
|
+
await upsertLiveResponse('⚠️ Quota Reached', 'Model quota limit reached. Please wait or switch to a different model.', `⏱️ ${elapsed}s`, { expectedVersion: liveResponseUpdateVersion });
|
|
404
504
|
try {
|
|
405
505
|
const payload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
406
506
|
if (payload) {
|
|
@@ -412,32 +512,137 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
412
512
|
}
|
|
413
513
|
return;
|
|
414
514
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
}
|
|
419
557
|
const separated = isAlreadyHtml ? { output: finalResponseText, logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(finalResponseText);
|
|
420
558
|
const finalOutputText = separated.output || finalResponseText;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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);
|
|
425
628
|
}
|
|
426
629
|
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
427
630
|
logger_1.logger.divider(`Output (${finalOutputText.length} chars)`);
|
|
428
631
|
console.info(finalOutputText);
|
|
429
632
|
}
|
|
430
633
|
logger_1.logger.divider();
|
|
634
|
+
// Compact progress message: show completed title + event log
|
|
431
635
|
liveActivityUpdateVersion += 1;
|
|
432
|
-
|
|
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 });
|
|
433
639
|
liveResponseUpdateVersion += 1;
|
|
434
640
|
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
await sendChunkedResponse(title, footer, finalOutputText, isAlreadyHtml);
|
|
641
|
+
const footer = `⏱️ ${elapsed}s`;
|
|
642
|
+
await sendChunkedResponse('', footer, finalOutputText, isAlreadyHtml);
|
|
438
643
|
}
|
|
439
644
|
else {
|
|
440
|
-
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 });
|
|
441
646
|
}
|
|
442
647
|
if (options) {
|
|
443
648
|
try {
|
|
@@ -489,16 +694,14 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
489
694
|
const timeoutText = (lastText && lastText.trim().length > 0) ? lastText : lastProgressText;
|
|
490
695
|
const timeoutIsHtml = monitor.getLastExtractionSource() === 'structured';
|
|
491
696
|
const separated = timeoutIsHtml ? { output: timeoutText || '', logs: '' } : (0, telegramFormatter_1.splitOutputAndLogs)(timeoutText || '');
|
|
492
|
-
const sanitizedTimeoutLogs = lastActivityLogText || processLogBuffer.snapshot();
|
|
493
697
|
const payload = separated.output && separated.output.trim().length > 0
|
|
494
698
|
? `${separated.output}\n\n[Monitor Ended] Timeout after 30 minutes.`
|
|
495
699
|
: 'Monitor ended after 30 minutes. No text was retrieved.';
|
|
496
700
|
liveResponseUpdateVersion += 1;
|
|
497
|
-
|
|
498
|
-
const timeoutFooter = `⏱️ Elapsed: ${elapsed}s | Timeout`;
|
|
499
|
-
await sendChunkedResponse(timeoutTitle, timeoutFooter, payload, timeoutIsHtml);
|
|
701
|
+
await sendChunkedResponse(`${PHASE_ICONS.timeout} Timeout`, `⏱️ ${elapsed}s`, payload, timeoutIsHtml);
|
|
500
702
|
liveActivityUpdateVersion += 1;
|
|
501
|
-
|
|
703
|
+
thinkingActive = false;
|
|
704
|
+
await setProgressMessage(`<b>${PHASE_ICONS.timeout} ${(0, telegramFormatter_1.escapeHtml)(modelLabel)} · ${elapsed}s</b>\n\n${buildProgressBody()}`, { expectedVersion: liveActivityUpdateVersion });
|
|
502
705
|
}
|
|
503
706
|
catch (error) {
|
|
504
707
|
logger_1.logger.error(`[sendPrompt:${monitorTraceId}] onTimeout failed:`, error);
|
|
@@ -511,10 +714,7 @@ async function sendPromptToAntigravity(bridge, channel, prompt, cdp, modeService
|
|
|
511
714
|
clearInterval(elapsedTimer);
|
|
512
715
|
return;
|
|
513
716
|
}
|
|
514
|
-
|
|
515
|
-
liveActivityUpdateVersion += 1;
|
|
516
|
-
const v = liveActivityUpdateVersion;
|
|
517
|
-
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();
|
|
518
718
|
}, 5000);
|
|
519
719
|
}
|
|
520
720
|
catch (e) {
|
|
@@ -559,6 +759,34 @@ const startBot = async (cliLogLevel) => {
|
|
|
559
759
|
const cleanupHandler = new cleanupCommandHandler_1.CleanupCommandHandler(chatSessionRepo, workspaceBindingRepo);
|
|
560
760
|
const bot = new grammy_1.Bot(config.telegramBotToken);
|
|
561
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
|
+
});
|
|
562
790
|
const topicManager = new telegramTopicManager_1.TelegramTopicManager(bot.api, 0);
|
|
563
791
|
// Auth middleware
|
|
564
792
|
bot.use(async (ctx, next) => {
|
|
@@ -645,11 +873,14 @@ const startBot = async (cliLogLevel) => {
|
|
|
645
873
|
});
|
|
646
874
|
// /model command
|
|
647
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);
|
|
648
879
|
const modelName = ctx.match?.trim();
|
|
649
880
|
if (modelName) {
|
|
650
|
-
const cdp = (
|
|
881
|
+
const cdp = getCdp();
|
|
651
882
|
if (!cdp) {
|
|
652
|
-
await ctx.reply('Not connected to CDP.');
|
|
883
|
+
await ctx.reply('Not connected to CDP. Send a message first to connect.');
|
|
653
884
|
return;
|
|
654
885
|
}
|
|
655
886
|
const res = await cdp.setUiModel(modelName);
|
|
@@ -661,7 +892,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
661
892
|
}
|
|
662
893
|
}
|
|
663
894
|
else {
|
|
664
|
-
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() });
|
|
665
896
|
}
|
|
666
897
|
});
|
|
667
898
|
// /template command
|