lazy-gravity 0.9.2 → 0.11.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/dist/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +401 -41
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/commands/chatCommandHandler.js +43 -3
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +456 -27
- package/dist/events/messageCreateHandler.js +66 -7
- package/dist/handlers/__tests__/fileChangeButtonAction.test.js +85 -0
- package/dist/handlers/approvalButtonAction.js +2 -1
- package/dist/handlers/errorPopupButtonAction.js +2 -1
- package/dist/handlers/fileChangeButtonAction.js +57 -0
- package/dist/handlers/genericActionButtonAction.js +72 -0
- package/dist/handlers/planningButtonAction.js +126 -23
- package/dist/handlers/planningModalSubmitAction.js +38 -0
- package/dist/handlers/questionSelectAction.js +70 -0
- package/dist/handlers/questionSkipAction.js +68 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/platform/discord/wrappers.js +76 -0
- package/dist/services/approvalDetector.js +110 -35
- package/dist/services/artifactService.js +103 -37
- package/dist/services/assistantDomExtractor.js +194 -3
- package/dist/services/cdpBridgeManager.js +149 -8
- package/dist/services/cdpConnectionPool.js +22 -2
- package/dist/services/cdpService.js +134 -15
- package/dist/services/chatSessionService.js +147 -40
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +80 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +27 -1
- package/dist/services/questionDetector.js +428 -0
- package/dist/services/responseMonitor.js +45 -3
- package/dist/services/runCommandDetector.js +14 -7
- package/dist/ui/artifactsUi.js +4 -3
- package/dist/utils/consecutiveEmptyPollGate.js +21 -0
- package/dist/utils/fileOpenCache.js +29 -0
- package/dist/utils/htmlToDiscordMarkdown.js +5 -2
- package/dist/utils/pathUtils.js +12 -0
- package/dist/utils/projectResolver.js +10 -0
- package/dist/utils/questionActionUtils.js +47 -0
- package/package.json +1 -1
|
@@ -27,6 +27,12 @@ function classifyAssistantSegments(payload) {
|
|
|
27
27
|
finalOutputText: '',
|
|
28
28
|
activityLines: [],
|
|
29
29
|
feedback: [],
|
|
30
|
+
planCards: [],
|
|
31
|
+
actionButtons: [],
|
|
32
|
+
fileChanges: [],
|
|
33
|
+
citations: [],
|
|
34
|
+
fileChangesTexts: [],
|
|
35
|
+
citedFiles: [],
|
|
30
36
|
diagnostics: {
|
|
31
37
|
source: 'legacy-fallback',
|
|
32
38
|
segmentCounts: {},
|
|
@@ -41,8 +47,13 @@ function classifyAssistantSegments(payload) {
|
|
|
41
47
|
const bodyTexts = [];
|
|
42
48
|
const activityLines = [];
|
|
43
49
|
const feedbackTexts = [];
|
|
50
|
+
const planCards = [];
|
|
51
|
+
const actionButtons = [];
|
|
52
|
+
const fileChanges = [];
|
|
53
|
+
const citations = [];
|
|
44
54
|
const segmentCounts = {};
|
|
45
55
|
const allFingerprints = [];
|
|
56
|
+
const fileChangesTexts = [];
|
|
46
57
|
for (const seg of segments) {
|
|
47
58
|
segmentCounts[seg.kind] = (segmentCounts[seg.kind] ?? 0) + 1;
|
|
48
59
|
switch (seg.kind) {
|
|
@@ -65,16 +76,68 @@ function classifyAssistantSegments(payload) {
|
|
|
65
76
|
feedbackTexts.push(seg.text.trim());
|
|
66
77
|
}
|
|
67
78
|
break;
|
|
79
|
+
case 'plan-card':
|
|
80
|
+
if (seg.text && seg.text.trim()) {
|
|
81
|
+
planCards.push(seg.text.trim());
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case 'action-button':
|
|
85
|
+
if (seg.text && seg.text.trim()) {
|
|
86
|
+
actionButtons.push(seg.text.trim());
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'file-change':
|
|
90
|
+
if (seg.text && seg.text.trim()) {
|
|
91
|
+
const parts = seg.text.split('|');
|
|
92
|
+
fileChanges.push({ path: parts[0]?.trim() || '', type: parts[1]?.trim() || '' });
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case 'citation':
|
|
96
|
+
if (seg.text && seg.text.trim()) {
|
|
97
|
+
citations.push(seg.text.trim());
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
case 'file-changes':
|
|
101
|
+
if (seg.text && seg.text.trim()) {
|
|
102
|
+
fileChangesTexts.push(seg.text.trim());
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
68
105
|
}
|
|
69
106
|
}
|
|
70
107
|
// Concatenate all body segments — the CDP script may yield multiple nodes
|
|
71
108
|
// (streaming partial + final, multi-block markdown, etc.)
|
|
72
109
|
const lastBody = bodyTexts.join('\n\n') || '';
|
|
73
110
|
const finalOutputText = (0, htmlToDiscordMarkdown_1.htmlToDiscordMarkdown)(lastBody);
|
|
111
|
+
// Extract file:/// links from finalOutputText and preserve citations from Pass 4
|
|
112
|
+
const citedFiles = Array.from(new Set([...citations]));
|
|
113
|
+
const fileRegex = /\[.*?\]\((file:\/\/\/[^\)]+)\)/g;
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = fileRegex.exec(finalOutputText)) !== null) {
|
|
116
|
+
const fileUrl = match[1];
|
|
117
|
+
if (!citedFiles.includes(fileUrl)) {
|
|
118
|
+
citedFiles.push(fileUrl);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Also extract backticked inline code that looks like a filename with common extensions
|
|
122
|
+
// e.g., `implementation_plan.md`
|
|
123
|
+
const inlineCodeRegex = /`([a-zA-Z0-9_\-\.]+\.(?:md|ts|js|json|html|css|txt|csv|py|java|go|cpp|c))`[^`]?/g;
|
|
124
|
+
let inlineMatch;
|
|
125
|
+
while ((inlineMatch = inlineCodeRegex.exec(finalOutputText)) !== null) {
|
|
126
|
+
const filename = inlineMatch[1];
|
|
127
|
+
if (!citedFiles.includes(filename)) {
|
|
128
|
+
citedFiles.push(filename);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
74
131
|
return {
|
|
75
132
|
finalOutputText,
|
|
76
133
|
activityLines,
|
|
77
134
|
feedback: feedbackTexts,
|
|
135
|
+
planCards,
|
|
136
|
+
actionButtons,
|
|
137
|
+
fileChanges,
|
|
138
|
+
citations,
|
|
139
|
+
fileChangesTexts,
|
|
140
|
+
citedFiles,
|
|
78
141
|
diagnostics: {
|
|
79
142
|
source: 'dom-structured',
|
|
80
143
|
segmentCounts,
|
|
@@ -145,10 +208,18 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
145
208
|
if (node.closest('details')) return true;
|
|
146
209
|
if (node.closest('[class*="feedback"], footer')) return true;
|
|
147
210
|
if (node.closest('.notify-user-container')) return true;
|
|
148
|
-
if (node.closest('[role="dialog"]')) return true;
|
|
149
|
-
|
|
211
|
+
if (node.closest('dialog, [role="dialog"]')) return true;
|
|
212
|
+
// Guard against other approval forms leaking into the output
|
|
213
|
+
var formNode = node.closest('form');
|
|
214
|
+
if (formNode && formNode.querySelector('[data-testid="plan-card"], [class*="plan-summary"], .actions-container, .review-button')) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// allow interactive tool forms (ask_question, ask_permission)
|
|
150
219
|
if (node.closest('[data-message-author-role="user"], [data-message-role="user"]')) return true;
|
|
151
|
-
|
|
220
|
+
// We do not exclude nodes just because they contain a textarea (e.g. ask_question "Other" option).
|
|
221
|
+
// The main chat input is excluded by the "ask anything" check below.
|
|
222
|
+
if (node.tagName && node.tagName.toLowerCase() === 'textarea') return true;
|
|
152
223
|
var text = (node.innerText || '').toLowerCase();
|
|
153
224
|
if (text.includes('ask anything, @ to mention')) return true;
|
|
154
225
|
if (text.includes('0 files with changes')) return true;
|
|
@@ -158,6 +229,7 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
158
229
|
var segments = [];
|
|
159
230
|
var seen = new Set();
|
|
160
231
|
var bodyFound = false;
|
|
232
|
+
var latestMessageContainer = null;
|
|
161
233
|
|
|
162
234
|
// Pass 1: Find assistant body — last non-excluded content node (recency first)
|
|
163
235
|
var combinedSelector = selectors.join(', ');
|
|
@@ -238,6 +310,7 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
238
310
|
domPath: 'article:nth(' + i + ')'
|
|
239
311
|
});
|
|
240
312
|
bodyFound = true;
|
|
313
|
+
latestMessageContainer = node.closest('[data-message-id], .message-row, [class*="message-container"]') || node.parentNode || scope;
|
|
241
314
|
break; // Only capture the single latest assistant message
|
|
242
315
|
}
|
|
243
316
|
}
|
|
@@ -362,6 +435,124 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
362
435
|
}
|
|
363
436
|
}
|
|
364
437
|
|
|
438
|
+
// Pass 4: Extract Antigravity 2.0 structured cards and artifacts
|
|
439
|
+
var artifactScope = latestMessageContainer || scope;
|
|
440
|
+
// 4a. Plan Cards
|
|
441
|
+
var planNodes = artifactScope.querySelectorAll('[data-testid="plan-card"], [class*="plan-summary"]');
|
|
442
|
+
for (var pi2 = 0; pi2 < planNodes.length; pi2++) {
|
|
443
|
+
var pnode = planNodes[pi2];
|
|
444
|
+
var ptext = (pnode.innerText || pnode.textContent || '').trim();
|
|
445
|
+
if (ptext) {
|
|
446
|
+
segments.push({
|
|
447
|
+
kind: 'plan-card',
|
|
448
|
+
text: ptext,
|
|
449
|
+
role: 'assistant',
|
|
450
|
+
messageIndex: 0,
|
|
451
|
+
domPath: 'plan-card:nth(' + pi2 + ')'
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 4b. Action Buttons (Open, Proceed, Review, etc.) - outside feedback footers
|
|
457
|
+
var actionBtns = artifactScope.querySelectorAll('.actions-container button, [class*="action-btn"], .review-button, [class*="review-btn"]');
|
|
458
|
+
for (var abi = 0; abi < actionBtns.length; abi++) {
|
|
459
|
+
var abtnText = (actionBtns[abi].textContent || '').trim();
|
|
460
|
+
if (!abtnText) continue;
|
|
461
|
+
var lower = abtnText.toLowerCase();
|
|
462
|
+
// Ignore approval buttons since they are handled by approvalDetector
|
|
463
|
+
if (lower.includes('accept all') || lower.includes('reject all')) continue;
|
|
464
|
+
|
|
465
|
+
segments.push({
|
|
466
|
+
kind: 'action-button',
|
|
467
|
+
text: abtnText,
|
|
468
|
+
role: 'assistant',
|
|
469
|
+
messageIndex: 0,
|
|
470
|
+
domPath: 'action-btn.' + abtnText.toLowerCase().replace(/\s+/g, '-')
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 4c. File Edits
|
|
475
|
+
var fileEdits = artifactScope.querySelectorAll('[class*="file-edit-item"], .file-edit-item, .bottom-full .cursor-pointer');
|
|
476
|
+
for (var fei = 0; fei < fileEdits.length; fei++) {
|
|
477
|
+
var editNode = fileEdits[fei];
|
|
478
|
+
if (seen.has(editNode)) continue;
|
|
479
|
+
seen.add(editNode);
|
|
480
|
+
var fpath = (editNode.querySelector('.file-path, [class*="path"]') || {}).textContent || '';
|
|
481
|
+
var ftype = (editNode.querySelector('.edit-type, [class*="type"]') || {}).textContent || 'Modified';
|
|
482
|
+
|
|
483
|
+
if (!fpath) {
|
|
484
|
+
var spans = editNode.querySelectorAll('span.whitespace-nowrap.text-sm');
|
|
485
|
+
if (spans.length > 0) {
|
|
486
|
+
fpath = spans[0].textContent || '';
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!fpath) {
|
|
490
|
+
fpath = (editNode.textContent || '').trim();
|
|
491
|
+
if (fpath.endsWith(ftype)) {
|
|
492
|
+
fpath = fpath.slice(0, -ftype.length).trim();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (fpath) {
|
|
496
|
+
segments.push({
|
|
497
|
+
kind: 'file-change',
|
|
498
|
+
text: fpath.trim() + '|' + ftype.trim(),
|
|
499
|
+
role: 'assistant',
|
|
500
|
+
messageIndex: 0,
|
|
501
|
+
domPath: 'file-edit:nth(' + fei + ')'
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 4d. Citations
|
|
507
|
+
var citationNodes = artifactScope.querySelectorAll('[class*="citation"], a[href^="file://"]');
|
|
508
|
+
for (var cti = 0; cti < citationNodes.length; cti++) {
|
|
509
|
+
var cnode = citationNodes[cti];
|
|
510
|
+
var ctext = (cnode.getAttribute('href') || cnode.textContent || '').trim();
|
|
511
|
+
if (ctext) {
|
|
512
|
+
segments.push({
|
|
513
|
+
kind: 'citation',
|
|
514
|
+
text: ctext,
|
|
515
|
+
role: 'assistant',
|
|
516
|
+
messageIndex: 0,
|
|
517
|
+
domPath: 'citation:nth(' + cti + ')'
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
// 4e. File Changes Text Blocks
|
|
522
|
+
var fcbNodes = artifactScope.querySelectorAll('.file-changes-block code, [class*="file-changes"] code');
|
|
523
|
+
for (var fci = 0; fci < fcbNodes.length; fci++) {
|
|
524
|
+
var fcNode = fcbNodes[fci];
|
|
525
|
+
var fcText = (fcNode.textContent || '').trim();
|
|
526
|
+
if (fcText) {
|
|
527
|
+
segments.push({
|
|
528
|
+
kind: 'file-changes',
|
|
529
|
+
text: fcText,
|
|
530
|
+
role: 'assistant',
|
|
531
|
+
messageIndex: 0,
|
|
532
|
+
domPath: 'file-changes:nth(' + fci + ')'
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// 4f. Artifact Cards
|
|
537
|
+
var artifactCards = artifactScope.querySelectorAll('[data-testid="artifact-card"], [class*="artifact-card"]');
|
|
538
|
+
for (var aci = 0; aci < artifactCards.length; aci++) {
|
|
539
|
+
var card = artifactCards[aci];
|
|
540
|
+
var titleEl = card.querySelector('.title, [class*="title"], h3, h4, span');
|
|
541
|
+
if (titleEl) {
|
|
542
|
+
var titleText = (titleEl.textContent || '').trim();
|
|
543
|
+
if (titleText) {
|
|
544
|
+
segments.push({
|
|
545
|
+
kind: 'citation',
|
|
546
|
+
text: titleText + '.md',
|
|
547
|
+
role: 'assistant',
|
|
548
|
+
messageIndex: 0,
|
|
549
|
+
domPath: 'artifact-card:nth(' + aci + ')'
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
365
556
|
if (!bodyFound && segments.length === 0) return null;
|
|
366
557
|
|
|
367
558
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FILE_CHANGE_REJECT_ACTION_PREFIX = exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX = exports.QUESTION_SELECT_ACTION_PREFIX = void 0;
|
|
3
4
|
exports.getCurrentChatTitle = getCurrentChatTitle;
|
|
4
5
|
exports.registerApprovalWorkspaceChannel = registerApprovalWorkspaceChannel;
|
|
5
6
|
exports.registerApprovalSessionChannel = registerApprovalSessionChannel;
|
|
@@ -12,6 +13,8 @@ exports.buildErrorPopupCustomId = buildErrorPopupCustomId;
|
|
|
12
13
|
exports.parseErrorPopupCustomId = parseErrorPopupCustomId;
|
|
13
14
|
exports.buildRunCommandCustomId = buildRunCommandCustomId;
|
|
14
15
|
exports.parseRunCommandCustomId = parseRunCommandCustomId;
|
|
16
|
+
exports.buildFileChangeCustomId = buildFileChangeCustomId;
|
|
17
|
+
exports.parseFileChangeCustomId = parseFileChangeCustomId;
|
|
15
18
|
exports.initCdpBridge = initCdpBridge;
|
|
16
19
|
exports.getCurrentCdp = getCurrentCdp;
|
|
17
20
|
exports.ensureApprovalDetector = ensureApprovalDetector;
|
|
@@ -19,6 +22,7 @@ exports.ensurePlanningDetector = ensurePlanningDetector;
|
|
|
19
22
|
exports.ensureErrorPopupDetector = ensureErrorPopupDetector;
|
|
20
23
|
exports.ensureRunCommandDetector = ensureRunCommandDetector;
|
|
21
24
|
exports.ensureUserMessageDetector = ensureUserMessageDetector;
|
|
25
|
+
exports.ensureQuestionDetector = ensureQuestionDetector;
|
|
22
26
|
const i18n_1 = require("../utils/i18n");
|
|
23
27
|
const logger_1 = require("../utils/logger");
|
|
24
28
|
const notificationSender_1 = require("./notificationSender");
|
|
@@ -30,16 +34,21 @@ const planningDetector_1 = require("./planningDetector");
|
|
|
30
34
|
const runCommandDetector_1 = require("./runCommandDetector");
|
|
31
35
|
const quotaService_1 = require("./quotaService");
|
|
32
36
|
const userMessageDetector_1 = require("./userMessageDetector");
|
|
37
|
+
const questionDetector_1 = require("./questionDetector");
|
|
33
38
|
const APPROVE_ACTION_PREFIX = 'approve_action';
|
|
34
39
|
const ALWAYS_ALLOW_ACTION_PREFIX = 'always_allow_action';
|
|
35
40
|
const DENY_ACTION_PREFIX = 'deny_action';
|
|
36
41
|
const PLANNING_OPEN_ACTION_PREFIX = 'planning_open_action';
|
|
37
42
|
const PLANNING_PROCEED_ACTION_PREFIX = 'planning_proceed_action';
|
|
43
|
+
const PLANNING_REJECT_ACTION_PREFIX = 'planning_reject_action';
|
|
38
44
|
const ERROR_POPUP_DISMISS_ACTION_PREFIX = 'error_popup_dismiss_action';
|
|
39
45
|
const ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX = 'error_popup_copy_debug_action';
|
|
40
46
|
const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
41
47
|
const RUN_COMMAND_RUN_ACTION_PREFIX = 'run_command_run_action';
|
|
42
48
|
const RUN_COMMAND_REJECT_ACTION_PREFIX = 'run_command_reject_action';
|
|
49
|
+
exports.QUESTION_SELECT_ACTION_PREFIX = 'question_select_action';
|
|
50
|
+
exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX = 'ide_file_accept_all';
|
|
51
|
+
exports.FILE_CHANGE_REJECT_ACTION_PREFIX = 'ide_file_reject_all';
|
|
43
52
|
function normalizeSessionTitle(title) {
|
|
44
53
|
return title.trim().toLowerCase();
|
|
45
54
|
}
|
|
@@ -47,7 +56,7 @@ function buildSessionRouteKey(projectName, sessionTitle) {
|
|
|
47
56
|
return `${projectName}::${normalizeSessionTitle(sessionTitle)}`;
|
|
48
57
|
}
|
|
49
58
|
const GET_CURRENT_CHAT_TITLE_SCRIPT = `(() => {
|
|
50
|
-
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
59
|
+
const panel = document.querySelector('.antigravity-agent-side-panel') || document.body;
|
|
51
60
|
if (!panel) return '';
|
|
52
61
|
const header = panel.querySelector('div[class*="border-b"]');
|
|
53
62
|
if (!header) return '';
|
|
@@ -137,7 +146,9 @@ function parseApprovalCustomId(customId) {
|
|
|
137
146
|
function buildPlanningCustomId(action, projectName, channelId) {
|
|
138
147
|
const prefix = action === 'open'
|
|
139
148
|
? PLANNING_OPEN_ACTION_PREFIX
|
|
140
|
-
:
|
|
149
|
+
: action === 'proceed'
|
|
150
|
+
? PLANNING_PROCEED_ACTION_PREFIX
|
|
151
|
+
: PLANNING_REJECT_ACTION_PREFIX;
|
|
141
152
|
if (channelId && channelId.trim().length > 0) {
|
|
142
153
|
return `${prefix}:${projectName}:${channelId}`;
|
|
143
154
|
}
|
|
@@ -150,6 +161,9 @@ function parsePlanningCustomId(customId) {
|
|
|
150
161
|
if (customId === PLANNING_PROCEED_ACTION_PREFIX) {
|
|
151
162
|
return { action: 'proceed', projectName: null, channelId: null };
|
|
152
163
|
}
|
|
164
|
+
if (customId === PLANNING_REJECT_ACTION_PREFIX) {
|
|
165
|
+
return { action: 'reject', projectName: null, channelId: null };
|
|
166
|
+
}
|
|
153
167
|
if (customId.startsWith(`${PLANNING_OPEN_ACTION_PREFIX}:`)) {
|
|
154
168
|
const rest = customId.substring(`${PLANNING_OPEN_ACTION_PREFIX}:`.length);
|
|
155
169
|
const [projectName, channelId] = rest.split(':');
|
|
@@ -160,6 +174,11 @@ function parsePlanningCustomId(customId) {
|
|
|
160
174
|
const [projectName, channelId] = rest.split(':');
|
|
161
175
|
return { action: 'proceed', projectName: projectName || null, channelId: channelId || null };
|
|
162
176
|
}
|
|
177
|
+
if (customId.startsWith(`${PLANNING_REJECT_ACTION_PREFIX}:`)) {
|
|
178
|
+
const rest = customId.substring(`${PLANNING_REJECT_ACTION_PREFIX}:`.length);
|
|
179
|
+
const [projectName, channelId] = rest.split(':');
|
|
180
|
+
return { action: 'reject', projectName: projectName || null, channelId: channelId || null };
|
|
181
|
+
}
|
|
163
182
|
return null;
|
|
164
183
|
}
|
|
165
184
|
function buildErrorPopupCustomId(action, projectName, channelId) {
|
|
@@ -228,6 +247,34 @@ function parseRunCommandCustomId(customId) {
|
|
|
228
247
|
}
|
|
229
248
|
return null;
|
|
230
249
|
}
|
|
250
|
+
function buildFileChangeCustomId(action, projectName, channelId) {
|
|
251
|
+
const prefix = action === 'accept'
|
|
252
|
+
? exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX
|
|
253
|
+
: exports.FILE_CHANGE_REJECT_ACTION_PREFIX;
|
|
254
|
+
if (channelId && channelId.trim().length > 0) {
|
|
255
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
256
|
+
}
|
|
257
|
+
return `${prefix}:${projectName}`;
|
|
258
|
+
}
|
|
259
|
+
function parseFileChangeCustomId(customId) {
|
|
260
|
+
if (customId === exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX) {
|
|
261
|
+
return { action: 'accept', projectName: null, channelId: null };
|
|
262
|
+
}
|
|
263
|
+
if (customId === exports.FILE_CHANGE_REJECT_ACTION_PREFIX) {
|
|
264
|
+
return { action: 'reject', projectName: null, channelId: null };
|
|
265
|
+
}
|
|
266
|
+
if (customId.startsWith(`${exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX}:`)) {
|
|
267
|
+
const rest = customId.substring(`${exports.FILE_CHANGE_ACCEPT_ACTION_PREFIX}:`.length);
|
|
268
|
+
const [projectName, channelId] = rest.split(':');
|
|
269
|
+
return { action: 'accept', projectName: projectName || null, channelId: channelId || null };
|
|
270
|
+
}
|
|
271
|
+
if (customId.startsWith(`${exports.FILE_CHANGE_REJECT_ACTION_PREFIX}:`)) {
|
|
272
|
+
const rest = customId.substring(`${exports.FILE_CHANGE_REJECT_ACTION_PREFIX}:`.length);
|
|
273
|
+
const [projectName, channelId] = rest.split(':');
|
|
274
|
+
return { action: 'reject', projectName: projectName || null, channelId: channelId || null };
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
231
278
|
/** Initialize the CDP bridge (lazy connection: pool creation only) */
|
|
232
279
|
function initCdpBridge(autoApproveDefault, accountPorts = {}, accountUserDataDirs = {}, cdpHost = '127.0.0.1') {
|
|
233
280
|
const pool = new cdpConnectionPool_1.CdpConnectionPool({
|
|
@@ -290,6 +337,8 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
290
337
|
},
|
|
291
338
|
onApprovalRequired: async (info) => {
|
|
292
339
|
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Approval button detected (allow="${info.approveText}", deny="${info.denyText}")`);
|
|
340
|
+
// Emit approval event for bot text updating
|
|
341
|
+
cdp.emit('approval_required', info);
|
|
293
342
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
294
343
|
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
295
344
|
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
@@ -311,17 +360,47 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
311
360
|
return;
|
|
312
361
|
}
|
|
313
362
|
}
|
|
363
|
+
const extraFields = [
|
|
364
|
+
{ name: (0, i18n_1.t)('Allow button'), value: info.approveText || (0, i18n_1.t)('(None)'), inline: true },
|
|
365
|
+
];
|
|
366
|
+
if (info.alwaysAllowText) {
|
|
367
|
+
extraFields.push({ name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText, inline: true });
|
|
368
|
+
}
|
|
369
|
+
extraFields.push({ name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true });
|
|
370
|
+
// Resolve artifact review buttons if they exist
|
|
371
|
+
let walkthroughCustomId;
|
|
372
|
+
let taskCustomId;
|
|
373
|
+
if (bridge.chatSessionRepo && bridge.artifactService) {
|
|
374
|
+
const session = bridge.chatSessionRepo.findByChannelId(targetChannelId);
|
|
375
|
+
const activeConversationId = session?.conversationId;
|
|
376
|
+
if (activeConversationId) {
|
|
377
|
+
const artifacts = bridge.artifactService.listArtifacts(activeConversationId);
|
|
378
|
+
const walkthrough = artifacts.find((art) => art.filename.toLowerCase() === 'walkthrough.md');
|
|
379
|
+
if (walkthrough) {
|
|
380
|
+
walkthroughCustomId = `file_open:art:${activeConversationId}:walkthrough.md`;
|
|
381
|
+
}
|
|
382
|
+
const task = artifacts.find((art) => art.filename.toLowerCase() === 'task.md');
|
|
383
|
+
if (task) {
|
|
384
|
+
taskCustomId = `file_open:art:${activeConversationId}:task.md`;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
314
388
|
const payload = (0, notificationSender_1.buildApprovalNotification)({
|
|
315
389
|
title: (0, i18n_1.t)('Approval Required'),
|
|
316
390
|
description: info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'),
|
|
317
391
|
projectName,
|
|
318
392
|
channelId: targetChannelId,
|
|
319
|
-
extraFields
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
393
|
+
extraFields,
|
|
394
|
+
hasAlwaysAllow: !!info.alwaysAllowText,
|
|
395
|
+
alwaysAllowText: info.alwaysAllowText,
|
|
396
|
+
walkthroughCustomId,
|
|
397
|
+
taskCustomId,
|
|
324
398
|
});
|
|
399
|
+
if (lastNotification) {
|
|
400
|
+
lastNotification.payload = payload;
|
|
401
|
+
lastNotification.sent.edit(payload).catch(logger_1.logger.error);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
325
404
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
326
405
|
logger_1.logger.error(err);
|
|
327
406
|
return null;
|
|
@@ -381,7 +460,15 @@ function ensurePlanningDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
381
460
|
projectName,
|
|
382
461
|
channelId: targetChannelId,
|
|
383
462
|
extraFields,
|
|
463
|
+
hasOpenButton: info.hasOpenButton,
|
|
464
|
+
openText: info.openText,
|
|
465
|
+
proceedText: info.proceedText,
|
|
384
466
|
});
|
|
467
|
+
if (lastNotification) {
|
|
468
|
+
lastNotification.payload = payload;
|
|
469
|
+
lastNotification.sent.edit(payload).catch(logger_1.logger.error);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
385
472
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
386
473
|
logger_1.logger.error(err);
|
|
387
474
|
return null;
|
|
@@ -438,12 +525,23 @@ function ensureErrorPopupDetector(bridge, cdp, projectName, accountName = 'defau
|
|
|
438
525
|
{ name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true },
|
|
439
526
|
],
|
|
440
527
|
});
|
|
528
|
+
if (lastNotification && detector.lastDetectedKey && lastNotification.key === detector.lastDetectedKey) {
|
|
529
|
+
// Cooldown triggered for the same popup; edit existing message instead of sending new
|
|
530
|
+
const edited = await lastNotification.sent.edit(payload).catch((err) => {
|
|
531
|
+
logger_1.logger.error(err);
|
|
532
|
+
return null;
|
|
533
|
+
});
|
|
534
|
+
if (edited) {
|
|
535
|
+
lastNotification.payload = payload;
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
441
539
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
442
540
|
logger_1.logger.error(err);
|
|
443
541
|
return null;
|
|
444
542
|
});
|
|
445
543
|
if (sent) {
|
|
446
|
-
lastNotification = { sent, payload };
|
|
544
|
+
lastNotification = { sent, payload, key: detector.lastDetectedKey };
|
|
447
545
|
}
|
|
448
546
|
},
|
|
449
547
|
});
|
|
@@ -549,3 +647,46 @@ function ensureUserMessageDetector(bridge, cdp, projectName, onUserMessage, acco
|
|
|
549
647
|
bridge.pool.registerUserMessageDetector(projectName, detector, accountName);
|
|
550
648
|
logger_1.logger.debug(`[UserMessageDetector:${projectName}] Started user message detection`);
|
|
551
649
|
}
|
|
650
|
+
function ensureQuestionDetector(bridge, cdp, projectName, accountName = 'default') {
|
|
651
|
+
const existing = bridge.pool.getQuestionDetector(projectName, accountName);
|
|
652
|
+
if (existing && existing.isActive)
|
|
653
|
+
return;
|
|
654
|
+
let lastNotification = null;
|
|
655
|
+
const detector = new questionDetector_1.QuestionDetector({
|
|
656
|
+
cdpService: cdp,
|
|
657
|
+
pollIntervalMs: 2000,
|
|
658
|
+
onResolved: () => {
|
|
659
|
+
if (!lastNotification)
|
|
660
|
+
return;
|
|
661
|
+
const { sent, payload } = lastNotification;
|
|
662
|
+
lastNotification = null;
|
|
663
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Question answered'));
|
|
664
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
665
|
+
},
|
|
666
|
+
onQuestionRequired: async (info) => {
|
|
667
|
+
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
668
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
669
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
670
|
+
if (!targetChannel || !targetChannelId)
|
|
671
|
+
return;
|
|
672
|
+
const payload = (0, notificationSender_1.buildQuestionNotification)({
|
|
673
|
+
title: info.title,
|
|
674
|
+
description: info.description,
|
|
675
|
+
projectName,
|
|
676
|
+
channelId: targetChannelId,
|
|
677
|
+
options: info.options,
|
|
678
|
+
});
|
|
679
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
680
|
+
logger_1.logger.error(err);
|
|
681
|
+
return null;
|
|
682
|
+
});
|
|
683
|
+
if (sent) {
|
|
684
|
+
lastNotification = { sent, payload };
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
detector.setProjectName(projectName);
|
|
689
|
+
detector.start();
|
|
690
|
+
bridge.pool.registerQuestionDetector(projectName, detector, accountName);
|
|
691
|
+
logger_1.logger.debug(`[QuestionDetector:${projectName}] Started question detection`);
|
|
692
|
+
}
|
|
@@ -11,6 +11,7 @@ function buildConnectionKey(projectName, accountName) {
|
|
|
11
11
|
* Pool that manages independent CdpService instances per workspace/account pair.
|
|
12
12
|
*/
|
|
13
13
|
class CdpConnectionPool {
|
|
14
|
+
lastActiveWorkspace = null;
|
|
14
15
|
connections = new Map();
|
|
15
16
|
workspaceToAccount = new Map();
|
|
16
17
|
approvalDetectors = new Map();
|
|
@@ -18,6 +19,7 @@ class CdpConnectionPool {
|
|
|
18
19
|
planningDetectors = new Map();
|
|
19
20
|
runCommandDetectors = new Map();
|
|
20
21
|
userMessageDetectors = new Map();
|
|
22
|
+
questionDetectors = new Map();
|
|
21
23
|
connectingPromises = new Map();
|
|
22
24
|
cdpOptions;
|
|
23
25
|
constructor(cdpOptions = {}) {
|
|
@@ -39,6 +41,7 @@ class CdpConnectionPool {
|
|
|
39
41
|
const key = buildConnectionKey(projectName, effectiveAccount);
|
|
40
42
|
const existing = this.connections.get(key);
|
|
41
43
|
if (existing && existing.isConnected()) {
|
|
44
|
+
this.lastActiveWorkspace = projectName;
|
|
42
45
|
await existing.discoverAndConnectForWorkspace(workspacePath);
|
|
43
46
|
return existing;
|
|
44
47
|
}
|
|
@@ -49,7 +52,9 @@ class CdpConnectionPool {
|
|
|
49
52
|
const connectPromise = this.createAndConnect(workspacePath, projectName, effectiveAccount);
|
|
50
53
|
this.connectingPromises.set(key, connectPromise);
|
|
51
54
|
try {
|
|
52
|
-
|
|
55
|
+
const result = await connectPromise;
|
|
56
|
+
this.lastActiveWorkspace = projectName;
|
|
57
|
+
return result;
|
|
53
58
|
}
|
|
54
59
|
finally {
|
|
55
60
|
this.connectingPromises.delete(key);
|
|
@@ -58,8 +63,9 @@ class CdpConnectionPool {
|
|
|
58
63
|
}
|
|
59
64
|
getConnected(projectName, accountName = 'default') {
|
|
60
65
|
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
61
|
-
const cdp = this.connections.get(buildConnectionKey(projectName, effectiveAccount));
|
|
66
|
+
const cdp = this.connections.get(buildConnectionKey(projectName, effectiveAccount)) || null;
|
|
62
67
|
if (cdp && cdp.isConnected()) {
|
|
68
|
+
this.lastActiveWorkspace = projectName;
|
|
63
69
|
return cdp;
|
|
64
70
|
}
|
|
65
71
|
return null;
|
|
@@ -78,6 +84,8 @@ class CdpConnectionPool {
|
|
|
78
84
|
this.approvalDetectors.delete(key);
|
|
79
85
|
this.errorPopupDetectors.get(key)?.stop();
|
|
80
86
|
this.errorPopupDetectors.delete(key);
|
|
87
|
+
this.questionDetectors.get(key)?.stop();
|
|
88
|
+
this.questionDetectors.delete(key);
|
|
81
89
|
this.planningDetectors.get(key)?.stop();
|
|
82
90
|
this.planningDetectors.delete(key);
|
|
83
91
|
this.runCommandDetectors.get(key)?.stop();
|
|
@@ -117,6 +125,16 @@ class CdpConnectionPool {
|
|
|
117
125
|
this.planningDetectors.get(key)?.stop();
|
|
118
126
|
this.planningDetectors.set(key, detector);
|
|
119
127
|
}
|
|
128
|
+
getQuestionDetector(projectName, accountName = 'default') {
|
|
129
|
+
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
130
|
+
return this.questionDetectors.get(buildConnectionKey(projectName, effectiveAccount));
|
|
131
|
+
}
|
|
132
|
+
registerQuestionDetector(projectName, detector, accountName = 'default') {
|
|
133
|
+
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
134
|
+
const key = buildConnectionKey(projectName, effectiveAccount);
|
|
135
|
+
this.questionDetectors.get(key)?.stop();
|
|
136
|
+
this.questionDetectors.set(key, detector);
|
|
137
|
+
}
|
|
120
138
|
getPlanningDetector(projectName, accountName = 'default') {
|
|
121
139
|
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
122
140
|
return this.planningDetectors.get(buildConnectionKey(projectName, effectiveAccount));
|
|
@@ -182,6 +200,8 @@ class CdpConnectionPool {
|
|
|
182
200
|
this.errorPopupDetectors.delete(key);
|
|
183
201
|
this.planningDetectors.get(key)?.stop();
|
|
184
202
|
this.planningDetectors.delete(key);
|
|
203
|
+
this.questionDetectors.get(key)?.stop();
|
|
204
|
+
this.questionDetectors.delete(key);
|
|
185
205
|
this.runCommandDetectors.get(key)?.stop();
|
|
186
206
|
this.runCommandDetectors.delete(key);
|
|
187
207
|
this.userMessageDetectors.get(key)?.stop();
|