lazy-gravity 0.10.0 → 0.12.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/README.md +7 -0
- package/dist/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +526 -24
- package/dist/commands/chatCommandHandler.js +11 -41
- package/dist/commands/registerSlashCommands.js +62 -0
- package/dist/database/scheduleRepository.js +50 -6
- package/dist/events/interactionCreateHandler.js +163 -28
- package/dist/events/messageCreateHandler.js +24 -28
- package/dist/handlers/fileChangeButtonAction.js +12 -24
- package/dist/handlers/genericActionButtonAction.js +16 -18
- package/dist/handlers/planningButtonAction.js +105 -17
- package/dist/handlers/planningModalSubmitAction.js +38 -0
- package/dist/handlers/questionSelectAction.js +8 -7
- package/dist/handlers/questionSkipAction.js +7 -6
- package/dist/platform/discord/wrappers.js +76 -0
- package/dist/services/approvalDetector.js +43 -14
- package/dist/services/artifactService.js +61 -21
- package/dist/services/assistantDomExtractor.js +23 -3
- package/dist/services/cdpBridgeManager.js +35 -2
- package/dist/services/cdpConnectionPool.js +7 -2
- package/dist/services/cdpService.js +113 -8
- package/dist/services/chatSessionService.js +15 -8
- package/dist/services/heartbeatService.js +261 -0
- package/dist/services/notificationSender.js +12 -2
- package/dist/services/planningDetector.js +1 -1
- package/dist/services/promptDispatcher.js +21 -1
- package/dist/services/questionDetector.js +128 -76
- package/dist/services/responseMonitor.js +17 -1
- package/dist/services/scheduleService.js +101 -4
- package/dist/utils/configLoader.js +8 -0
- package/dist/utils/fileOpenCache.js +2 -6
- package/dist/utils/questionActionUtils.js +22 -0
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApprovalDetector = void 0;
|
|
3
|
+
exports.ApprovalDetector = exports.DETECT_APPROVAL_SCRIPT = void 0;
|
|
4
4
|
exports.buildClickScript = buildClickScript;
|
|
5
5
|
const logger_1 = require("../utils/logger");
|
|
6
6
|
const consecutiveEmptyPollGate_1 = require("../utils/consecutiveEmptyPollGate");
|
|
@@ -9,7 +9,7 @@ const consecutiveEmptyPollGate_1 = require("../utils/consecutiveEmptyPollGate");
|
|
|
9
9
|
*
|
|
10
10
|
* Detects allow/deny button pairs and extracts descriptions with fallbacks.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
exports.DETECT_APPROVAL_SCRIPT = `(() => {
|
|
13
13
|
const ALLOW_ONCE_PATTERNS = ['allow once', 'allow one time', 'yes, allow this time', '今回のみ許可', '1回のみ許可', '一度許可'];
|
|
14
14
|
const ALWAYS_ALLOW_PATTERNS = [
|
|
15
15
|
'allow this conversation',
|
|
@@ -25,7 +25,21 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
25
25
|
const normalize = (text) => (text || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
26
26
|
|
|
27
27
|
const allButtons = Array.from(document.querySelectorAll('button, [role="button"], span.cursor-pointer, div.cursor-pointer'))
|
|
28
|
-
.filter(btn => btn.offsetParent !== null)
|
|
28
|
+
.filter(btn => btn.offsetParent !== null)
|
|
29
|
+
.reverse();
|
|
30
|
+
|
|
31
|
+
const STOP_PATTERNS_GEN = [/^stop$/, /^stop generating$/, /^stop response$/, /^停止$/, /^生成を停止$/, /^応答を停止$/];
|
|
32
|
+
const isGenerating = allButtons.some(btn => {
|
|
33
|
+
const tooltipId = btn.getAttribute('data-tooltip-id');
|
|
34
|
+
if (tooltipId === 'input-send-button-cancel-tooltip') return true;
|
|
35
|
+
const labels = [btn.textContent || '', btn.getAttribute('aria-label') || '', btn.getAttribute('title') || ''];
|
|
36
|
+
return labels.some(val => {
|
|
37
|
+
const normalized = (val || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
38
|
+
return normalized && STOP_PATTERNS_GEN.some(re => re.test(normalized));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (isGenerating) return null; // Wait for generation to finish!
|
|
29
43
|
|
|
30
44
|
let approveBtn = allButtons.find(btn => {
|
|
31
45
|
const t = normalize(btn.textContent || '');
|
|
@@ -48,7 +62,8 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
48
62
|
|| document.body;
|
|
49
63
|
|
|
50
64
|
const containerButtons = Array.from(container.querySelectorAll('button, [role="button"], span.cursor-pointer, div.cursor-pointer'))
|
|
51
|
-
.filter(btn => btn.offsetParent !== null)
|
|
65
|
+
.filter(btn => btn.offsetParent !== null)
|
|
66
|
+
.reverse();
|
|
52
67
|
|
|
53
68
|
const denyBtn = containerButtons.find(btn => {
|
|
54
69
|
const t = normalize(btn.textContent || '');
|
|
@@ -62,9 +77,9 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
62
77
|
return ALWAYS_ALLOW_PATTERNS.some(p => t.includes(p));
|
|
63
78
|
}) || null;
|
|
64
79
|
|
|
65
|
-
const approveText = (approveBtn.textContent || '').trim();
|
|
66
|
-
const alwaysAllowText = alwaysAllowBtn ? (alwaysAllowBtn.textContent || '').trim() : '';
|
|
67
|
-
const denyText = (denyBtn.textContent || '').trim();
|
|
80
|
+
const approveText = (approveBtn.innerText || approveBtn.textContent || '').trim();
|
|
81
|
+
const alwaysAllowText = alwaysAllowBtn ? (alwaysAllowBtn.innerText || alwaysAllowBtn.textContent || '').trim() : '';
|
|
82
|
+
const denyText = (denyBtn.innerText || denyBtn.textContent || '').trim();
|
|
68
83
|
|
|
69
84
|
// Description extraction (multiple fallbacks)
|
|
70
85
|
let description = '';
|
|
@@ -95,6 +110,7 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
95
110
|
}
|
|
96
111
|
if (!modal) {
|
|
97
112
|
modal = approveBtn.parentElement?.parentElement?.parentElement || approveBtn.parentElement?.parentElement;
|
|
113
|
+
if (modal === document.body || modal?.id === 'root') modal = null;
|
|
98
114
|
}
|
|
99
115
|
}
|
|
100
116
|
|
|
@@ -103,8 +119,11 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
103
119
|
const walk = (node) => {
|
|
104
120
|
if (node.nodeType === 1) {
|
|
105
121
|
// Skip buttons entirely
|
|
106
|
-
if (node.tagName === 'BUTTON' || node.getAttribute('role') === 'button'
|
|
122
|
+
if (node.tagName === 'BUTTON' || node.getAttribute('role') === 'button') return;
|
|
107
123
|
|
|
124
|
+
// Skip menu bars and sidebars
|
|
125
|
+
if (node.tagName === 'NAV' || node.getAttribute('role') === 'menubar' || node.closest('nav') || node.closest('.monaco-menu') || node.closest('.sidebar') || node.classList.contains('sidebar')) return;
|
|
126
|
+
|
|
108
127
|
const display = window.getComputedStyle(node).display;
|
|
109
128
|
if (display === 'none') return;
|
|
110
129
|
|
|
@@ -121,7 +140,11 @@ const DETECT_APPROVAL_SCRIPT = `(() => {
|
|
|
121
140
|
|
|
122
141
|
const parentText = parts.join(' ').replace(/\\n\\s*/g, '\\n').replace(/\\n{3,}/g, '\\n\\n').trim();
|
|
123
142
|
if (parentText.length > 5) {
|
|
124
|
-
|
|
143
|
+
if (parentText.length > 800 || parentText.includes('F ile\\nE dit\\nS election')) {
|
|
144
|
+
description = 'Code changes require your approval.';
|
|
145
|
+
} else {
|
|
146
|
+
description = parentText;
|
|
147
|
+
}
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
150
|
}
|
|
@@ -149,7 +172,7 @@ const EXPAND_ALWAYS_ALLOW_MENU_SCRIPT = `(() => {
|
|
|
149
172
|
];
|
|
150
173
|
|
|
151
174
|
const normalize = (text) => (text || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
152
|
-
const visibleButtons = Array.from(document.querySelectorAll('button'))
|
|
175
|
+
const visibleButtons = Array.from(document.querySelectorAll('button, [role="button"], span.cursor-pointer, div.cursor-pointer'))
|
|
153
176
|
.filter(btn => btn.offsetParent !== null);
|
|
154
177
|
|
|
155
178
|
const directAlways = visibleButtons.find(btn => {
|
|
@@ -169,7 +192,7 @@ const EXPAND_ALWAYS_ALLOW_MENU_SCRIPT = `(() => {
|
|
|
169
192
|
|| allowOnceBtn.parentElement
|
|
170
193
|
|| document.body;
|
|
171
194
|
|
|
172
|
-
const containerButtons = Array.from(container.querySelectorAll('button'))
|
|
195
|
+
const containerButtons = Array.from(container.querySelectorAll('button, [role="button"], span.cursor-pointer, div.cursor-pointer'))
|
|
173
196
|
.filter(btn => btn.offsetParent !== null);
|
|
174
197
|
|
|
175
198
|
const toggleBtn = containerButtons.find(btn => {
|
|
@@ -218,12 +241,18 @@ function buildClickScript(buttonText) {
|
|
|
218
241
|
const normalize = (text) => (text || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
219
242
|
const text = ${safeText};
|
|
220
243
|
const wanted = normalize(text);
|
|
221
|
-
const allButtons = Array.from(document.querySelectorAll('button, [role="button"], a, [class*="btn"],
|
|
244
|
+
const allButtons = Array.from(document.querySelectorAll('button, [role="button"], a.action-btn, a[class*="btn"], span.cursor-pointer, div.cursor-pointer')).reverse();
|
|
222
245
|
const target = allButtons.find(btn => {
|
|
223
246
|
const style = window.getComputedStyle(btn);
|
|
224
247
|
if (style.display === 'none' || style.visibility === 'hidden' || btn.disabled) return false;
|
|
225
|
-
const buttonText = normalize(btn.textContent || '');
|
|
248
|
+
const buttonText = normalize(btn.innerText || btn.textContent || '');
|
|
226
249
|
const ariaLabel = normalize(btn.getAttribute('aria-label') || '');
|
|
250
|
+
|
|
251
|
+
const isShort = wanted.length < 5;
|
|
252
|
+
if (isShort) {
|
|
253
|
+
return buttonText === wanted || ariaLabel === wanted;
|
|
254
|
+
}
|
|
255
|
+
|
|
227
256
|
return buttonText === wanted ||
|
|
228
257
|
ariaLabel === wanted ||
|
|
229
258
|
(buttonText.includes(wanted) && buttonText.length < wanted.length + 10) ||
|
|
@@ -317,7 +346,7 @@ class ApprovalDetector {
|
|
|
317
346
|
try {
|
|
318
347
|
const contextId = this.cdpService.getPrimaryContextId();
|
|
319
348
|
const callParams = {
|
|
320
|
-
expression: DETECT_APPROVAL_SCRIPT,
|
|
349
|
+
expression: exports.DETECT_APPROVAL_SCRIPT,
|
|
321
350
|
returnByValue: true,
|
|
322
351
|
awaitPromise: false,
|
|
323
352
|
};
|
|
@@ -69,9 +69,17 @@ const COMMON_WORDS = new Set(['the', 'and', 'for', 'with', 'from', 'this', 'that
|
|
|
69
69
|
class ArtifactService {
|
|
70
70
|
brainBasePath;
|
|
71
71
|
constructor(brainBasePath) {
|
|
72
|
-
|
|
73
|
-
brainBasePath
|
|
74
|
-
|
|
72
|
+
if (brainBasePath) {
|
|
73
|
+
this.brainBasePath = brainBasePath;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const idePath = path.join(os.homedir(), '.gemini', 'antigravity-ide', 'brain');
|
|
77
|
+
const defaultPath = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
|
78
|
+
this.brainBasePath = fs.existsSync(idePath) ? idePath : defaultPath;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
getBrainBasePath() {
|
|
82
|
+
return this.brainBasePath;
|
|
75
83
|
}
|
|
76
84
|
/**
|
|
77
85
|
* List all conversations in the brain directory (UUIDs).
|
|
@@ -202,23 +210,25 @@ class ArtifactService {
|
|
|
202
210
|
const buf = Buffer.alloc(readSize);
|
|
203
211
|
const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
204
212
|
const content = buf.slice(0, bytesRead).toString('utf-8').toLowerCase();
|
|
205
|
-
if (filterStr
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
exactMatch = true;
|
|
213
|
+
if (filterStr) {
|
|
214
|
+
const escapedFilter = filterStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
215
|
+
const regex = new RegExp(`(?:file:\\/\\/\\/[^"\\)]*?\\/|[a-zA-Z]:[\\\\/](?!.*(?:\\.gemini|brain|antigravity))[^"\\)]*?[\\\\/])${escapedFilter}\\b`, 'i');
|
|
216
|
+
if (regex.test(content)) {
|
|
217
|
+
belongsToWorkspace = true;
|
|
211
218
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
}
|
|
220
|
+
if (content.includes(needle)) {
|
|
221
|
+
exactMatch = true;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const contentTokens = new Set(content.replace(/[^\p{L}\p{N}]+/gu, ' ').split(/\s+/));
|
|
225
|
+
let currentScore = 0;
|
|
226
|
+
for (const word of uniqueNeedleWords) {
|
|
227
|
+
if (contentTokens.has(word))
|
|
228
|
+
currentScore++;
|
|
221
229
|
}
|
|
230
|
+
if (currentScore > score)
|
|
231
|
+
score = currentScore;
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
234
|
finally {
|
|
@@ -251,10 +261,11 @@ class ArtifactService {
|
|
|
251
261
|
* that has at least one artifact. Falls back to the most recent conversation
|
|
252
262
|
* overall if none have artifacts.
|
|
253
263
|
*/
|
|
254
|
-
getLatestConversationWithArtifacts() {
|
|
264
|
+
getLatestConversationWithArtifacts(workspaceFilter) {
|
|
255
265
|
const ids = this.listConversationIds();
|
|
256
266
|
if (ids.length === 0)
|
|
257
267
|
return null;
|
|
268
|
+
const filterStr = workspaceFilter ? workspaceFilter.toLowerCase() : null;
|
|
258
269
|
// Sort by directory mtime descending
|
|
259
270
|
const sorted = ids
|
|
260
271
|
.map((id) => {
|
|
@@ -267,12 +278,41 @@ class ArtifactService {
|
|
|
267
278
|
}
|
|
268
279
|
})
|
|
269
280
|
.sort((a, b) => b.mtime - a.mtime);
|
|
270
|
-
|
|
281
|
+
if (filterStr) {
|
|
282
|
+
const escapedFilter = filterStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
283
|
+
const workspaceIds = sorted.filter(({ id }) => {
|
|
284
|
+
let belongs = false;
|
|
285
|
+
const regex = new RegExp(`(?:file:\\/\\/\\/[^"\\)]*?\\/|[a-zA-Z]:[\\\\/](?!.*(?:\\.gemini|brain|antigravity))[^"\\)]*?[\\\\/])${escapedFilter}\\b`, 'i');
|
|
286
|
+
const checkFile = (filePath) => {
|
|
287
|
+
try {
|
|
288
|
+
if (fs.existsSync(filePath)) {
|
|
289
|
+
const content = fs.readFileSync(filePath, 'utf-8').toLowerCase();
|
|
290
|
+
if (regex.test(content)) {
|
|
291
|
+
belongs = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch { /* ignore */ }
|
|
296
|
+
};
|
|
297
|
+
checkFile(path.join(this.brainBasePath, id, '.system_generated', 'logs', 'transcript.jsonl'));
|
|
298
|
+
if (!belongs) {
|
|
299
|
+
checkFile(path.join(this.brainBasePath, id, 'implementation_plan.md'));
|
|
300
|
+
}
|
|
301
|
+
return belongs;
|
|
302
|
+
});
|
|
303
|
+
if (workspaceIds.length === 0)
|
|
304
|
+
return null;
|
|
305
|
+
const latestId = workspaceIds[0].id;
|
|
306
|
+
if (this.listArtifacts(latestId).length > 0) {
|
|
307
|
+
return latestId;
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
// Original fallback behavior for no filter
|
|
271
312
|
for (const { id } of sorted) {
|
|
272
313
|
if (this.listArtifacts(id).length > 0)
|
|
273
314
|
return id;
|
|
274
315
|
}
|
|
275
|
-
// Nothing has artifacts — return most recent anyway (caller handles empty list)
|
|
276
316
|
return sorted[0]?.id ?? null;
|
|
277
317
|
}
|
|
278
318
|
/**
|
|
@@ -208,9 +208,10 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
208
208
|
if (node.closest('details')) return true;
|
|
209
209
|
if (node.closest('[class*="feedback"], footer')) return true;
|
|
210
210
|
if (node.closest('.notify-user-container')) return true;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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')) {
|
|
214
215
|
return true;
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -532,6 +533,25 @@ function extractAssistantSegmentsPayloadScript() {
|
|
|
532
533
|
});
|
|
533
534
|
}
|
|
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
|
+
|
|
535
555
|
|
|
536
556
|
if (!bodyFound && segments.length === 0) return null;
|
|
537
557
|
|
|
@@ -56,7 +56,7 @@ function buildSessionRouteKey(projectName, sessionTitle) {
|
|
|
56
56
|
return `${projectName}::${normalizeSessionTitle(sessionTitle)}`;
|
|
57
57
|
}
|
|
58
58
|
const GET_CURRENT_CHAT_TITLE_SCRIPT = `(() => {
|
|
59
|
-
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
59
|
+
const panel = document.querySelector('.antigravity-agent-side-panel') || document.body;
|
|
60
60
|
if (!panel) return '';
|
|
61
61
|
const header = panel.querySelector('div[class*="border-b"]');
|
|
62
62
|
if (!header) return '';
|
|
@@ -337,6 +337,8 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
337
337
|
},
|
|
338
338
|
onApprovalRequired: async (info) => {
|
|
339
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);
|
|
340
342
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
341
343
|
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
342
344
|
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
@@ -365,6 +367,24 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
365
367
|
extraFields.push({ name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText, inline: true });
|
|
366
368
|
}
|
|
367
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
|
+
}
|
|
368
388
|
const payload = (0, notificationSender_1.buildApprovalNotification)({
|
|
369
389
|
title: (0, i18n_1.t)('Approval Required'),
|
|
370
390
|
description: info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'),
|
|
@@ -373,6 +393,8 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
373
393
|
extraFields,
|
|
374
394
|
hasAlwaysAllow: !!info.alwaysAllowText,
|
|
375
395
|
alwaysAllowText: info.alwaysAllowText,
|
|
396
|
+
walkthroughCustomId,
|
|
397
|
+
taskCustomId,
|
|
376
398
|
});
|
|
377
399
|
if (lastNotification) {
|
|
378
400
|
lastNotification.payload = payload;
|
|
@@ -503,12 +525,23 @@ function ensureErrorPopupDetector(bridge, cdp, projectName, accountName = 'defau
|
|
|
503
525
|
{ name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true },
|
|
504
526
|
],
|
|
505
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
|
+
}
|
|
506
539
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
507
540
|
logger_1.logger.error(err);
|
|
508
541
|
return null;
|
|
509
542
|
});
|
|
510
543
|
if (sent) {
|
|
511
|
-
lastNotification = { sent, payload };
|
|
544
|
+
lastNotification = { sent, payload, key: detector.lastDetectedKey };
|
|
512
545
|
}
|
|
513
546
|
},
|
|
514
547
|
});
|
|
@@ -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();
|
|
@@ -40,6 +41,7 @@ class CdpConnectionPool {
|
|
|
40
41
|
const key = buildConnectionKey(projectName, effectiveAccount);
|
|
41
42
|
const existing = this.connections.get(key);
|
|
42
43
|
if (existing && existing.isConnected()) {
|
|
44
|
+
this.lastActiveWorkspace = projectName;
|
|
43
45
|
await existing.discoverAndConnectForWorkspace(workspacePath);
|
|
44
46
|
return existing;
|
|
45
47
|
}
|
|
@@ -50,7 +52,9 @@ class CdpConnectionPool {
|
|
|
50
52
|
const connectPromise = this.createAndConnect(workspacePath, projectName, effectiveAccount);
|
|
51
53
|
this.connectingPromises.set(key, connectPromise);
|
|
52
54
|
try {
|
|
53
|
-
|
|
55
|
+
const result = await connectPromise;
|
|
56
|
+
this.lastActiveWorkspace = projectName;
|
|
57
|
+
return result;
|
|
54
58
|
}
|
|
55
59
|
finally {
|
|
56
60
|
this.connectingPromises.delete(key);
|
|
@@ -59,8 +63,9 @@ class CdpConnectionPool {
|
|
|
59
63
|
}
|
|
60
64
|
getConnected(projectName, accountName = 'default') {
|
|
61
65
|
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
62
|
-
const cdp = this.connections.get(buildConnectionKey(projectName, effectiveAccount));
|
|
66
|
+
const cdp = this.connections.get(buildConnectionKey(projectName, effectiveAccount)) || null;
|
|
63
67
|
if (cdp && cdp.isConnected()) {
|
|
68
|
+
this.lastActiveWorkspace = projectName;
|
|
64
69
|
return cdp;
|
|
65
70
|
}
|
|
66
71
|
return null;
|
|
@@ -102,6 +102,12 @@ const WORKSPACE_STATE_SCRIPT = `(() => {
|
|
|
102
102
|
if (isGenerating) break;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
if (!isGenerating && panel) {
|
|
106
|
+
const panelText = (panel.textContent || '').trim();
|
|
107
|
+
if (/Working\.\s*$/i.test(panelText)) {
|
|
108
|
+
isGenerating = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
105
111
|
|
|
106
112
|
if (!panel) {
|
|
107
113
|
return { isGenerating, sessionTitle: '', hasActiveChat: false };
|
|
@@ -462,7 +468,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
462
468
|
returnByValue: true,
|
|
463
469
|
});
|
|
464
470
|
const liveTitle = String(titleResult?.result?.value || '');
|
|
465
|
-
|
|
471
|
+
const titleParts = liveTitle.split(' - ');
|
|
472
|
+
if (titleParts[0].trim().toLowerCase() === projectName.toLowerCase()) {
|
|
466
473
|
this.currentWorkspaceName = projectName;
|
|
467
474
|
return true;
|
|
468
475
|
}
|
|
@@ -509,7 +516,12 @@ class CdpService extends events_1.EventEmitter {
|
|
|
509
516
|
logger_1.logger.debug(` - title="${p.title}" url=${p.url}`);
|
|
510
517
|
}
|
|
511
518
|
// 1. Title match (fast path)
|
|
512
|
-
const titleMatch = workbenchPages.find((t) =>
|
|
519
|
+
const titleMatch = workbenchPages.find((t) => {
|
|
520
|
+
if (!t.title)
|
|
521
|
+
return false;
|
|
522
|
+
const parts = t.title.split(' - ');
|
|
523
|
+
return parts[0].trim().toLowerCase() === projectName.toLowerCase();
|
|
524
|
+
});
|
|
513
525
|
if (titleMatch) {
|
|
514
526
|
return this.connectToPage(titleMatch, projectName);
|
|
515
527
|
}
|
|
@@ -562,9 +574,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
562
574
|
returnByValue: true,
|
|
563
575
|
});
|
|
564
576
|
const liveTitle = String(result?.result?.value || '');
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
if (normalizedLiveTitle.includes(normalizedProject)) {
|
|
577
|
+
const liveParts = liveTitle.split(' - ');
|
|
578
|
+
if (liveParts[0].trim().toLowerCase() === projectName.toLowerCase()) {
|
|
568
579
|
this.currentWorkspaceName = projectName;
|
|
569
580
|
logger_1.logger.debug(`[CdpService] Probe success: detected "${projectName}"`);
|
|
570
581
|
return true;
|
|
@@ -761,7 +772,12 @@ class CdpService extends events_1.EventEmitter {
|
|
|
761
772
|
!t.url?.includes('workbench-jetski-agent') &&
|
|
762
773
|
t.url?.includes('workbench'));
|
|
763
774
|
// Title match
|
|
764
|
-
const titleMatch = workbenchPages.find((t) =>
|
|
775
|
+
const titleMatch = workbenchPages.find((t) => {
|
|
776
|
+
if (!t.title)
|
|
777
|
+
return false;
|
|
778
|
+
const parts = t.title.split(' - ');
|
|
779
|
+
return parts[0].trim().toLowerCase() === projectName.toLowerCase();
|
|
780
|
+
});
|
|
765
781
|
if (titleMatch) {
|
|
766
782
|
return this.connectToPage(titleMatch, projectName);
|
|
767
783
|
}
|
|
@@ -1290,9 +1306,15 @@ class CdpService extends events_1.EventEmitter {
|
|
|
1290
1306
|
].some((fragment) => message.includes(fragment));
|
|
1291
1307
|
}
|
|
1292
1308
|
async injectMessageCore(text, imageFilePaths) {
|
|
1293
|
-
|
|
1309
|
+
// Check if chat input is already available
|
|
1310
|
+
let focusResult = await this.waitForChatInputReady(500);
|
|
1294
1311
|
if (!focusResult.ok) {
|
|
1295
|
-
|
|
1312
|
+
// Send Cmd+L / Ctrl+L to guarantee application-level focus on the chat panel (and open it if closed)
|
|
1313
|
+
await this.focusChatPanelViaShortcut();
|
|
1314
|
+
focusResult = await this.waitForChatInputReady();
|
|
1315
|
+
if (!focusResult.ok) {
|
|
1316
|
+
return { ok: false, error: focusResult.error || 'Chat input field not found' };
|
|
1317
|
+
}
|
|
1296
1318
|
}
|
|
1297
1319
|
// Clear any existing text in the input field before injecting.
|
|
1298
1320
|
await this.clearInputField();
|
|
@@ -1391,6 +1413,89 @@ class CdpService extends events_1.EventEmitter {
|
|
|
1391
1413
|
nativeVirtualKeyCode: 13,
|
|
1392
1414
|
});
|
|
1393
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Opens the chat panel and waits for the UI to be ready.
|
|
1418
|
+
* Can be called right after the IDE launches to ensure the panel is visible.
|
|
1419
|
+
*/
|
|
1420
|
+
async openChatPanel() {
|
|
1421
|
+
if (!await this.isConnected())
|
|
1422
|
+
return;
|
|
1423
|
+
// Ensure IDE UI is fully loaded before sending shortcut
|
|
1424
|
+
const deadline = Date.now() + 15000;
|
|
1425
|
+
let workbenchReady = false;
|
|
1426
|
+
while (Date.now() < deadline) {
|
|
1427
|
+
try {
|
|
1428
|
+
const res = await this.call('Runtime.evaluate', {
|
|
1429
|
+
expression: '!!document.querySelector(".monaco-workbench")',
|
|
1430
|
+
returnByValue: true
|
|
1431
|
+
});
|
|
1432
|
+
if (res?.result?.value) {
|
|
1433
|
+
workbenchReady = true;
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
catch {
|
|
1438
|
+
// ignore
|
|
1439
|
+
}
|
|
1440
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1441
|
+
}
|
|
1442
|
+
if (workbenchReady) {
|
|
1443
|
+
// Check if chat panel is already open to avoid toggling it closed
|
|
1444
|
+
const focusResult = await this.waitForChatInputReady(1000);
|
|
1445
|
+
if (!focusResult.ok) {
|
|
1446
|
+
await this.focusChatPanelViaShortcut();
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Send Cmd+L / Ctrl+L to focus the chat panel via shortcut.
|
|
1452
|
+
*/
|
|
1453
|
+
async focusChatPanelViaShortcut() {
|
|
1454
|
+
const modifiers = process.platform === 'darwin' ? 4 : 2; // Meta : Ctrl
|
|
1455
|
+
await this.call('Input.dispatchKeyEvent', {
|
|
1456
|
+
type: 'keyDown',
|
|
1457
|
+
key: 'l',
|
|
1458
|
+
code: 'KeyL',
|
|
1459
|
+
modifiers,
|
|
1460
|
+
windowsVirtualKeyCode: 76,
|
|
1461
|
+
nativeVirtualKeyCode: 76,
|
|
1462
|
+
});
|
|
1463
|
+
await this.call('Input.dispatchKeyEvent', {
|
|
1464
|
+
type: 'keyUp',
|
|
1465
|
+
key: 'l',
|
|
1466
|
+
code: 'KeyL',
|
|
1467
|
+
modifiers,
|
|
1468
|
+
windowsVirtualKeyCode: 76,
|
|
1469
|
+
nativeVirtualKeyCode: 76,
|
|
1470
|
+
});
|
|
1471
|
+
await new Promise(r => setTimeout(r, 100)); // wait for focus to shift
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Send Cmd+W / Ctrl+W to close the active editor.
|
|
1475
|
+
*/
|
|
1476
|
+
async closeActiveEditor() {
|
|
1477
|
+
if (!this.isConnectedFlag || !this.ws) {
|
|
1478
|
+
throw new Error('Not connected to CDP. Call connect() first.');
|
|
1479
|
+
}
|
|
1480
|
+
const modifiers = process.platform === 'darwin' ? 4 : 2; // Meta : Ctrl
|
|
1481
|
+
await this.call('Input.dispatchKeyEvent', {
|
|
1482
|
+
type: 'keyDown',
|
|
1483
|
+
key: 'w',
|
|
1484
|
+
code: 'KeyW',
|
|
1485
|
+
modifiers,
|
|
1486
|
+
windowsVirtualKeyCode: 87,
|
|
1487
|
+
nativeVirtualKeyCode: 87,
|
|
1488
|
+
});
|
|
1489
|
+
await this.call('Input.dispatchKeyEvent', {
|
|
1490
|
+
type: 'keyUp',
|
|
1491
|
+
key: 'w',
|
|
1492
|
+
code: 'KeyW',
|
|
1493
|
+
modifiers,
|
|
1494
|
+
windowsVirtualKeyCode: 87,
|
|
1495
|
+
nativeVirtualKeyCode: 87,
|
|
1496
|
+
});
|
|
1497
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1498
|
+
}
|
|
1394
1499
|
/**
|
|
1395
1500
|
* Detect file input in the UI and attach the specified files.
|
|
1396
1501
|
*/
|