lazy-gravity 0.9.2 → 0.10.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/bot/index.js +232 -41
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/commands/chatCommandHandler.js +71 -1
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +324 -26
- package/dist/events/messageCreateHandler.js +53 -4
- 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 +69 -0
- package/dist/handlers/genericActionButtonAction.js +74 -0
- package/dist/handlers/planningButtonAction.js +24 -9
- package/dist/handlers/questionSelectAction.js +69 -0
- package/dist/handlers/questionSkipAction.js +67 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/services/approvalDetector.js +70 -24
- package/dist/services/artifactService.js +57 -31
- package/dist/services/assistantDomExtractor.js +174 -3
- package/dist/services/cdpBridgeManager.js +114 -6
- package/dist/services/cdpConnectionPool.js +15 -0
- package/dist/services/cdpService.js +21 -7
- package/dist/services/chatSessionService.js +133 -33
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +70 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +7 -1
- package/dist/services/questionDetector.js +376 -0
- package/dist/services/responseMonitor.js +28 -2
- 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 +33 -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 +25 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -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({
|
|
@@ -311,17 +358,27 @@ function ensureApprovalDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
311
358
|
return;
|
|
312
359
|
}
|
|
313
360
|
}
|
|
361
|
+
const extraFields = [
|
|
362
|
+
{ name: (0, i18n_1.t)('Allow button'), value: info.approveText || (0, i18n_1.t)('(None)'), inline: true },
|
|
363
|
+
];
|
|
364
|
+
if (info.alwaysAllowText) {
|
|
365
|
+
extraFields.push({ name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText, inline: true });
|
|
366
|
+
}
|
|
367
|
+
extraFields.push({ name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true });
|
|
314
368
|
const payload = (0, notificationSender_1.buildApprovalNotification)({
|
|
315
369
|
title: (0, i18n_1.t)('Approval Required'),
|
|
316
370
|
description: info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'),
|
|
317
371
|
projectName,
|
|
318
372
|
channelId: targetChannelId,
|
|
319
|
-
extraFields
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
{ name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true },
|
|
323
|
-
],
|
|
373
|
+
extraFields,
|
|
374
|
+
hasAlwaysAllow: !!info.alwaysAllowText,
|
|
375
|
+
alwaysAllowText: info.alwaysAllowText,
|
|
324
376
|
});
|
|
377
|
+
if (lastNotification) {
|
|
378
|
+
lastNotification.payload = payload;
|
|
379
|
+
lastNotification.sent.edit(payload).catch(logger_1.logger.error);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
325
382
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
326
383
|
logger_1.logger.error(err);
|
|
327
384
|
return null;
|
|
@@ -381,7 +438,15 @@ function ensurePlanningDetector(bridge, cdp, projectName, accountName = 'default
|
|
|
381
438
|
projectName,
|
|
382
439
|
channelId: targetChannelId,
|
|
383
440
|
extraFields,
|
|
441
|
+
hasOpenButton: info.hasOpenButton,
|
|
442
|
+
openText: info.openText,
|
|
443
|
+
proceedText: info.proceedText,
|
|
384
444
|
});
|
|
445
|
+
if (lastNotification) {
|
|
446
|
+
lastNotification.payload = payload;
|
|
447
|
+
lastNotification.sent.edit(payload).catch(logger_1.logger.error);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
385
450
|
const sent = await targetChannel.send(payload).catch((err) => {
|
|
386
451
|
logger_1.logger.error(err);
|
|
387
452
|
return null;
|
|
@@ -549,3 +614,46 @@ function ensureUserMessageDetector(bridge, cdp, projectName, onUserMessage, acco
|
|
|
549
614
|
bridge.pool.registerUserMessageDetector(projectName, detector, accountName);
|
|
550
615
|
logger_1.logger.debug(`[UserMessageDetector:${projectName}] Started user message detection`);
|
|
551
616
|
}
|
|
617
|
+
function ensureQuestionDetector(bridge, cdp, projectName, accountName = 'default') {
|
|
618
|
+
const existing = bridge.pool.getQuestionDetector(projectName, accountName);
|
|
619
|
+
if (existing && existing.isActive)
|
|
620
|
+
return;
|
|
621
|
+
let lastNotification = null;
|
|
622
|
+
const detector = new questionDetector_1.QuestionDetector({
|
|
623
|
+
cdpService: cdp,
|
|
624
|
+
pollIntervalMs: 2000,
|
|
625
|
+
onResolved: () => {
|
|
626
|
+
if (!lastNotification)
|
|
627
|
+
return;
|
|
628
|
+
const { sent, payload } = lastNotification;
|
|
629
|
+
lastNotification = null;
|
|
630
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Question answered'));
|
|
631
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
632
|
+
},
|
|
633
|
+
onQuestionRequired: async (info) => {
|
|
634
|
+
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
635
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
636
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
637
|
+
if (!targetChannel || !targetChannelId)
|
|
638
|
+
return;
|
|
639
|
+
const payload = (0, notificationSender_1.buildQuestionNotification)({
|
|
640
|
+
title: info.title,
|
|
641
|
+
description: info.description,
|
|
642
|
+
projectName,
|
|
643
|
+
channelId: targetChannelId,
|
|
644
|
+
options: info.options,
|
|
645
|
+
});
|
|
646
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
647
|
+
logger_1.logger.error(err);
|
|
648
|
+
return null;
|
|
649
|
+
});
|
|
650
|
+
if (sent) {
|
|
651
|
+
lastNotification = { sent, payload };
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
detector.setProjectName(projectName);
|
|
656
|
+
detector.start();
|
|
657
|
+
bridge.pool.registerQuestionDetector(projectName, detector, accountName);
|
|
658
|
+
logger_1.logger.debug(`[QuestionDetector:${projectName}] Started question detection`);
|
|
659
|
+
}
|
|
@@ -18,6 +18,7 @@ class CdpConnectionPool {
|
|
|
18
18
|
planningDetectors = new Map();
|
|
19
19
|
runCommandDetectors = new Map();
|
|
20
20
|
userMessageDetectors = new Map();
|
|
21
|
+
questionDetectors = new Map();
|
|
21
22
|
connectingPromises = new Map();
|
|
22
23
|
cdpOptions;
|
|
23
24
|
constructor(cdpOptions = {}) {
|
|
@@ -78,6 +79,8 @@ class CdpConnectionPool {
|
|
|
78
79
|
this.approvalDetectors.delete(key);
|
|
79
80
|
this.errorPopupDetectors.get(key)?.stop();
|
|
80
81
|
this.errorPopupDetectors.delete(key);
|
|
82
|
+
this.questionDetectors.get(key)?.stop();
|
|
83
|
+
this.questionDetectors.delete(key);
|
|
81
84
|
this.planningDetectors.get(key)?.stop();
|
|
82
85
|
this.planningDetectors.delete(key);
|
|
83
86
|
this.runCommandDetectors.get(key)?.stop();
|
|
@@ -117,6 +120,16 @@ class CdpConnectionPool {
|
|
|
117
120
|
this.planningDetectors.get(key)?.stop();
|
|
118
121
|
this.planningDetectors.set(key, detector);
|
|
119
122
|
}
|
|
123
|
+
getQuestionDetector(projectName, accountName = 'default') {
|
|
124
|
+
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
125
|
+
return this.questionDetectors.get(buildConnectionKey(projectName, effectiveAccount));
|
|
126
|
+
}
|
|
127
|
+
registerQuestionDetector(projectName, detector, accountName = 'default') {
|
|
128
|
+
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
129
|
+
const key = buildConnectionKey(projectName, effectiveAccount);
|
|
130
|
+
this.questionDetectors.get(key)?.stop();
|
|
131
|
+
this.questionDetectors.set(key, detector);
|
|
132
|
+
}
|
|
120
133
|
getPlanningDetector(projectName, accountName = 'default') {
|
|
121
134
|
const effectiveAccount = this.resolveAccountName(projectName, accountName);
|
|
122
135
|
return this.planningDetectors.get(buildConnectionKey(projectName, effectiveAccount));
|
|
@@ -182,6 +195,8 @@ class CdpConnectionPool {
|
|
|
182
195
|
this.errorPopupDetectors.delete(key);
|
|
183
196
|
this.planningDetectors.get(key)?.stop();
|
|
184
197
|
this.planningDetectors.delete(key);
|
|
198
|
+
this.questionDetectors.get(key)?.stop();
|
|
199
|
+
this.questionDetectors.delete(key);
|
|
185
200
|
this.runCommandDetectors.get(key)?.stop();
|
|
186
201
|
this.runCommandDetectors.delete(key);
|
|
187
202
|
this.userMessageDetectors.get(key)?.stop();
|
|
@@ -407,6 +407,9 @@ class CdpService extends events_1.EventEmitter {
|
|
|
407
407
|
getCurrentWorkspaceName() {
|
|
408
408
|
return this.currentWorkspaceName;
|
|
409
409
|
}
|
|
410
|
+
getCurrentWorkspacePath() {
|
|
411
|
+
return this.currentWorkspacePath;
|
|
412
|
+
}
|
|
410
413
|
/**
|
|
411
414
|
* Discover and connect to the workbench page for the specified workspace.
|
|
412
415
|
* Does nothing if already connected to the correct page.
|
|
@@ -628,9 +631,11 @@ class CdpService extends events_1.EventEmitter {
|
|
|
628
631
|
const detectedValue = value.value;
|
|
629
632
|
const normalizedDetected = detectedValue.toLowerCase();
|
|
630
633
|
const normalizedProject = projectName.toLowerCase();
|
|
631
|
-
const normalizedWorkspace = workspacePath.toLowerCase();
|
|
632
|
-
|
|
633
|
-
|
|
634
|
+
const normalizedWorkspace = workspacePath.toLowerCase().replace(/\\/g, '/');
|
|
635
|
+
const detectedParts = normalizedDetected.split(',').map((s) => s.trim());
|
|
636
|
+
if (detectedParts.includes(normalizedProject) ||
|
|
637
|
+
normalizedDetected.includes(normalizedWorkspace) ||
|
|
638
|
+
normalizedDetected.replace(/\\/g, '/').includes(normalizedWorkspace)) {
|
|
634
639
|
this.currentWorkspaceName = projectName;
|
|
635
640
|
logger_1.logger.debug(`[CdpService] Folder path match success: "${projectName}"`);
|
|
636
641
|
return true;
|
|
@@ -641,11 +646,20 @@ class CdpService extends events_1.EventEmitter {
|
|
|
641
646
|
expression: 'window.location.href',
|
|
642
647
|
returnByValue: true,
|
|
643
648
|
});
|
|
644
|
-
const pageUrl = (urlResult?.result?.value || '').toLowerCase();
|
|
645
|
-
const normalizedWorkspaceUri =
|
|
646
|
-
if (pageUrl.includes(normalizedWorkspaceUri)
|
|
649
|
+
const pageUrl = decodeURIComponent(urlResult?.result?.value || '').toLowerCase().replace(/\\/g, '/');
|
|
650
|
+
const normalizedWorkspaceUri = workspacePath.toLowerCase().replace(/\\/g, '/');
|
|
651
|
+
if (pageUrl.includes(normalizedWorkspaceUri)) {
|
|
652
|
+
this.currentWorkspaceName = projectName;
|
|
653
|
+
logger_1.logger.debug(`[CdpService] URL parameter match success: "${projectName}" (workspace URI)`);
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
// If we only have the project name to go on, make sure it's a discrete boundary in the URL path
|
|
657
|
+
// e.g. "folder=.../test" or "workspace=.../test". We prevent false positives like "latest" matching "test".
|
|
658
|
+
const safeProjectName = projectName.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
659
|
+
const projectBoundaryRegex = new RegExp(`[=/]${safeProjectName}(?:$|&|\\?)`);
|
|
660
|
+
if (projectBoundaryRegex.test(pageUrl)) {
|
|
647
661
|
this.currentWorkspaceName = projectName;
|
|
648
|
-
logger_1.logger.debug(`[CdpService] URL parameter match success: "${projectName}"`);
|
|
662
|
+
logger_1.logger.debug(`[CdpService] URL parameter match success: "${projectName}" (boundary regex)`);
|
|
649
663
|
return true;
|
|
650
664
|
}
|
|
651
665
|
}
|
|
@@ -3,11 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ChatSessionService = void 0;
|
|
4
4
|
exports.buildActivateViaPastConversationsScript = buildActivateViaPastConversationsScript;
|
|
5
5
|
const logger_1 = require("../utils/logger");
|
|
6
|
+
/** Shared fuzzy-matching logic for in-page scripts */
|
|
7
|
+
const FUZZY_MATCH_HELPERS_SCRIPT = `
|
|
8
|
+
const getWords = (str) => {
|
|
9
|
+
return (str || '').toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9\\u3040-\\u30ff\\u4e00-\\u9faf\\s]/g, '')
|
|
11
|
+
.split(/\\s+/)
|
|
12
|
+
.filter(w => w.length > 1 && !/^(ago|wks?|days?|mins?|hours?|hrs?|secs?|weeks?|months?|years?)$/i.test(w));
|
|
13
|
+
};
|
|
14
|
+
const wordsMatch = (t, p) => {
|
|
15
|
+
const tWords = getWords(t);
|
|
16
|
+
const pWords = getWords(p);
|
|
17
|
+
if (pWords.length === 0) return false;
|
|
18
|
+
return pWords.every(pw => tWords.some(tw => tw === pw));
|
|
19
|
+
};
|
|
20
|
+
`;
|
|
6
21
|
/** Script to get the state of the new chat button */
|
|
7
22
|
const GET_NEW_CHAT_BUTTON_SCRIPT = `(() => {
|
|
8
23
|
const btn = document.querySelector('[data-tooltip-id="new-conversation-tooltip"]');
|
|
9
24
|
if (!btn) return { found: false };
|
|
10
25
|
const cursor = window.getComputedStyle(btn).cursor;
|
|
26
|
+
if (typeof btn.scrollIntoView === 'function') {
|
|
27
|
+
btn.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
28
|
+
}
|
|
11
29
|
const rect = btn.getBoundingClientRect();
|
|
12
30
|
return {
|
|
13
31
|
found: true,
|
|
@@ -106,6 +124,9 @@ const GET_SESSION_VIEW_STATE_SCRIPT = `(() => {
|
|
|
106
124
|
const FIND_PAST_CONVERSATIONS_BUTTON_SCRIPT = `(() => {
|
|
107
125
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
108
126
|
const getRect = (el) => {
|
|
127
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
128
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
129
|
+
}
|
|
109
130
|
const rect = el.getBoundingClientRect();
|
|
110
131
|
return { found: true, x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
|
|
111
132
|
};
|
|
@@ -214,6 +235,9 @@ const FIND_SHOW_MORE_BUTTON_SCRIPT = `(() => {
|
|
|
214
235
|
if (!isVisible(el)) continue;
|
|
215
236
|
const text = (el.textContent || '').trim();
|
|
216
237
|
if (/^Show\\s+\\d+\\s+more/i.test(text)) {
|
|
238
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
239
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
240
|
+
}
|
|
217
241
|
const rect = el.getBoundingClientRect();
|
|
218
242
|
return { found: true, x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
|
|
219
243
|
}
|
|
@@ -241,11 +265,14 @@ function buildActivateChatByTitleScript(title) {
|
|
|
241
265
|
return true;
|
|
242
266
|
};
|
|
243
267
|
|
|
268
|
+
${FUZZY_MATCH_HELPERS_SCRIPT}
|
|
269
|
+
|
|
244
270
|
const nodes = Array.from(panel.querySelectorAll('button, [role="button"], a, li, div, span'))
|
|
245
271
|
.filter(isVisible);
|
|
246
272
|
|
|
247
273
|
const exact = [];
|
|
248
274
|
const includes = [];
|
|
275
|
+
const fuzzy = [];
|
|
249
276
|
for (const node of nodes) {
|
|
250
277
|
const text = normalize(node.textContent || '');
|
|
251
278
|
if (!text) continue;
|
|
@@ -253,6 +280,8 @@ function buildActivateChatByTitleScript(title) {
|
|
|
253
280
|
exact.push({ node, textLength: text.length });
|
|
254
281
|
} else if (text.includes(wanted)) {
|
|
255
282
|
includes.push({ node, textLength: text.length });
|
|
283
|
+
} else if (wordsMatch(text, wantedRaw)) {
|
|
284
|
+
fuzzy.push({ node, textLength: text.length });
|
|
256
285
|
}
|
|
257
286
|
}
|
|
258
287
|
|
|
@@ -262,10 +291,18 @@ function buildActivateChatByTitleScript(title) {
|
|
|
262
291
|
return list[0].node;
|
|
263
292
|
};
|
|
264
293
|
|
|
265
|
-
const target = pick(exact) || pick(includes);
|
|
294
|
+
const target = pick(exact) || pick(includes) || pick(fuzzy);
|
|
266
295
|
if (!target) return { ok: false, error: 'Chat title not found in side panel' };
|
|
267
|
-
|
|
268
|
-
|
|
296
|
+
const clickable = target.closest('button, [role="button"], a, li, [data-testid*="conversation"]') || target;
|
|
297
|
+
if (typeof clickable.scrollIntoView === 'function') {
|
|
298
|
+
clickable.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
299
|
+
}
|
|
300
|
+
const rect = clickable.getBoundingClientRect();
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
x: Math.round(rect.x + rect.width / 2),
|
|
304
|
+
y: Math.round(rect.y + rect.height / 2)
|
|
305
|
+
};
|
|
269
306
|
})()`;
|
|
270
307
|
}
|
|
271
308
|
/**
|
|
@@ -303,14 +340,6 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
303
340
|
];
|
|
304
341
|
};
|
|
305
342
|
const getLabelText = (el) => getLabelParts(el).filter(Boolean).join(' ');
|
|
306
|
-
// Verify the option that was actually selected really is the requested
|
|
307
|
-
// title. getLabelText concatenates several attributes, so a naive
|
|
308
|
-
// whole-string comparison can fail even for a correct pick (e.g. when
|
|
309
|
-
// textContent and aria-label duplicate the same value). Instead, check
|
|
310
|
-
// each label component individually for an exact (or loose-exact) match.
|
|
311
|
-
// If any component matches, report the requested title; otherwise report
|
|
312
|
-
// the actual visible text so the acceptance gate rejects a mere
|
|
313
|
-
// substring/loose selection.
|
|
314
343
|
const resolveMatchedTitle = (el) => {
|
|
315
344
|
const parts = getLabelParts(el);
|
|
316
345
|
for (const part of parts) {
|
|
@@ -327,6 +356,8 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
327
356
|
const clickable = el.closest('button, [role="button"], a, li, [role="option"], [data-testid*="conversation"]');
|
|
328
357
|
return clickable instanceof HTMLElement ? clickable : (el instanceof HTMLElement ? el : null);
|
|
329
358
|
};
|
|
359
|
+
${FUZZY_MATCH_HELPERS_SCRIPT}
|
|
360
|
+
|
|
330
361
|
const pickBest = (elements, patterns) => {
|
|
331
362
|
const matched = [];
|
|
332
363
|
for (const el of elements) {
|
|
@@ -345,6 +376,9 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
345
376
|
) {
|
|
346
377
|
matched.push({ el, score: Math.abs(text.length - pattern.length) });
|
|
347
378
|
break;
|
|
379
|
+
} else if (wordsMatch(text, pattern)) {
|
|
380
|
+
matched.push({ el, score: Math.abs(text.length - pattern.length) + 1000 });
|
|
381
|
+
break;
|
|
348
382
|
}
|
|
349
383
|
}
|
|
350
384
|
}
|
|
@@ -366,7 +400,13 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
366
400
|
if (!el) return false;
|
|
367
401
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
368
402
|
el.focus();
|
|
369
|
-
el.
|
|
403
|
+
const proto = el instanceof HTMLInputElement ? window.HTMLInputElement.prototype : window.HTMLTextAreaElement.prototype;
|
|
404
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
405
|
+
if (nativeSetter) {
|
|
406
|
+
nativeSetter.call(el, value);
|
|
407
|
+
} else {
|
|
408
|
+
el.value = value;
|
|
409
|
+
}
|
|
370
410
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
371
411
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
372
412
|
return true;
|
|
@@ -489,29 +529,41 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
489
529
|
clickByPatterns(['select a conversation', 'select conversation', 'conversation'], '[role="button"], button, [aria-haspopup], [data-testid*="conversation"]');
|
|
490
530
|
await wait(220);
|
|
491
531
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
532
|
+
// First, try to find it in the default visible list (works for recent chats)
|
|
533
|
+
let selectedOption = pickBest(
|
|
534
|
+
asArray(document.querySelectorAll('[role="option"], li, button, [data-testid*="conversation"]')),
|
|
535
|
+
[wanted, wantedLoose],
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
// If not found, fall back to the search input but use a shorter prefix to avoid strict-match bugs
|
|
539
|
+
if (!selectedOption) {
|
|
540
|
+
const input = findSearchInput();
|
|
541
|
+
if (input) {
|
|
542
|
+
// Use first 2 words to bypass IDE renaming/truncation issues
|
|
543
|
+
const searchPrefix = (wantedRaw || '').split(/\\s+/).slice(0, 2).join(' ');
|
|
544
|
+
setInputValue(input, searchPrefix.length > 3 ? searchPrefix : wantedRaw);
|
|
545
|
+
await wait(350);
|
|
546
|
+
selectedOption = pickBest(
|
|
547
|
+
asArray(document.querySelectorAll('[role="option"], li, button, [data-testid*="conversation"]')),
|
|
548
|
+
[wanted, wantedLoose],
|
|
549
|
+
);
|
|
550
|
+
}
|
|
496
551
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// on that same element so the click target and the verification
|
|
500
|
-
// target can never drift apart. Mirror clickByPatterns' scoped
|
|
501
|
-
// selector -> broad fallback behaviour.
|
|
502
|
-
const optionSelector = '[role="option"], li, button, [data-testid*="conversation"]';
|
|
503
|
-
const scopedOptions = asArray(document.querySelectorAll(optionSelector));
|
|
504
|
-
const broadOptions = asArray(document.querySelectorAll('button, [role="button"], a, li, div, span'));
|
|
505
|
-
const optionSource = scopedOptions.length > 0 ? scopedOptions : broadOptions;
|
|
506
|
-
const selectedOption = pickBest(optionSource, [wanted, wantedLoose]);
|
|
507
|
-
const selectedClickable = getClickable(selectedOption);
|
|
508
|
-
if (!selectedClickable) {
|
|
552
|
+
|
|
553
|
+
if (!selectedOption) {
|
|
509
554
|
return { ok: false, error: 'Conversation not found in Past Conversations' };
|
|
510
555
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
556
|
+
const clickable = getClickable(selectedOption) || selectedOption;
|
|
557
|
+
if (typeof clickable.scrollIntoView === 'function') {
|
|
558
|
+
clickable.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
559
|
+
}
|
|
560
|
+
const rect = clickable.getBoundingClientRect();
|
|
561
|
+
return {
|
|
562
|
+
ok: true,
|
|
563
|
+
x: Math.round(rect.x + rect.width / 2),
|
|
564
|
+
y: Math.round(rect.y + rect.height / 2),
|
|
565
|
+
matchedTitle: resolveMatchedTitle(selectedOption)
|
|
566
|
+
};
|
|
515
567
|
})();
|
|
516
568
|
})()`;
|
|
517
569
|
}
|
|
@@ -893,7 +945,12 @@ class ChatSessionService {
|
|
|
893
945
|
if (!clicked) {
|
|
894
946
|
pastResult = await this.tryActivateByPastConversations(cdpService, title);
|
|
895
947
|
clicked = pastResult.ok;
|
|
896
|
-
|
|
948
|
+
// If we attempted past conversations, the panel is open (unless it clicked something)
|
|
949
|
+
usedPastConversations = true;
|
|
950
|
+
// If it failed to click anything, the panel is still open and blocking the UI.
|
|
951
|
+
if (!clicked) {
|
|
952
|
+
await this.closePanelWithEscape(cdpService);
|
|
953
|
+
}
|
|
897
954
|
}
|
|
898
955
|
if (clicked) {
|
|
899
956
|
break;
|
|
@@ -964,6 +1021,7 @@ class ChatSessionService {
|
|
|
964
1021
|
error: `Past Conversations selected a different chat (expected="${title}", actual="${afterPast.title}")`,
|
|
965
1022
|
};
|
|
966
1023
|
}
|
|
1024
|
+
await this.closePanelWithEscape(cdpService);
|
|
967
1025
|
return {
|
|
968
1026
|
ok: false,
|
|
969
1027
|
error: `Activated chat did not match target title (expected="${title}", actual="${after.title}") ` +
|
|
@@ -994,6 +1052,9 @@ class ChatSessionService {
|
|
|
994
1052
|
});
|
|
995
1053
|
const value = result?.result?.value;
|
|
996
1054
|
if (value?.ok) {
|
|
1055
|
+
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
|
1056
|
+
await this.cdpMouseClick(cdpService, value.x, value.y);
|
|
1057
|
+
}
|
|
997
1058
|
return {
|
|
998
1059
|
ok: true,
|
|
999
1060
|
...(typeof value.matchedTitle === 'string'
|
|
@@ -1031,5 +1092,44 @@ class ChatSessionService {
|
|
|
1031
1092
|
}
|
|
1032
1093
|
return { found: false, enabled: false, x: 0, y: 0 };
|
|
1033
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Rename the current chat in the Antigravity UI directly by updating the DOM.
|
|
1097
|
+
* Note: This is a cosmetic change until Antigravity persists a rename.
|
|
1098
|
+
*/
|
|
1099
|
+
async renameCurrentChatInUI(cdpService, newTitle) {
|
|
1100
|
+
try {
|
|
1101
|
+
const contextId = cdpService.getPrimaryContextId();
|
|
1102
|
+
const RENAME_SCRIPT = `(() => {
|
|
1103
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
1104
|
+
if (!panel) return { ok: false, error: 'Panel not found' };
|
|
1105
|
+
const header = panel.querySelector('div[class*="border-b"]');
|
|
1106
|
+
const titleEl = header?.querySelector('div[class*="text-ellipsis"]');
|
|
1107
|
+
if (titleEl) {
|
|
1108
|
+
titleEl.textContent = ${JSON.stringify(newTitle)};
|
|
1109
|
+
return { ok: true };
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const activeRow = Array.from(panel.querySelectorAll('div[class*="focusBackground"]'))
|
|
1113
|
+
.find((el) => el instanceof HTMLElement && el.offsetParent !== null);
|
|
1114
|
+
const activeTitle = activeRow?.querySelector('span.text-sm span, span.text-sm');
|
|
1115
|
+
if (activeTitle) {
|
|
1116
|
+
activeTitle.textContent = ${JSON.stringify(newTitle)};
|
|
1117
|
+
return { ok: true };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
return { ok: false, error: 'Title element not found' };
|
|
1121
|
+
})()`;
|
|
1122
|
+
const result = await cdpService.call('Runtime.evaluate', {
|
|
1123
|
+
expression: RENAME_SCRIPT,
|
|
1124
|
+
returnByValue: true,
|
|
1125
|
+
...(contextId !== null ? { contextId } : {}),
|
|
1126
|
+
awaitPromise: true,
|
|
1127
|
+
});
|
|
1128
|
+
return result?.result?.value || { ok: false, error: 'Eval failed' };
|
|
1129
|
+
}
|
|
1130
|
+
catch (e) {
|
|
1131
|
+
return { ok: false, error: e.message };
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1034
1134
|
}
|
|
1035
1135
|
exports.ChatSessionService = ChatSessionService;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ErrorPopupDetector = void 0;
|
|
4
|
+
const consecutiveEmptyPollGate_1 = require("../utils/consecutiveEmptyPollGate");
|
|
4
5
|
const logger_1 = require("../utils/logger");
|
|
5
6
|
const approvalDetector_1 = require("./approvalDetector");
|
|
6
7
|
/**
|
|
@@ -107,8 +108,10 @@ class ErrorPopupDetector {
|
|
|
107
108
|
lastDetectedInfo = null;
|
|
108
109
|
/** Timestamp of last notification (for cooldown-based dedup) */
|
|
109
110
|
lastNotifiedAt = 0;
|
|
110
|
-
/** Cooldown period in ms to suppress duplicate notifications
|
|
111
|
-
static COOLDOWN_MS =
|
|
111
|
+
/** Cooldown period in ms to suppress duplicate notifications */
|
|
112
|
+
static COOLDOWN_MS = 5000;
|
|
113
|
+
/** Gate for empty polls before reset */
|
|
114
|
+
emptyPollGate = new consecutiveEmptyPollGate_1.ConsecutiveEmptyPollGate(3);
|
|
112
115
|
constructor(options) {
|
|
113
116
|
this.cdpService = options.cdpService;
|
|
114
117
|
this.pollIntervalMs = options.pollIntervalMs ?? 3000;
|
|
@@ -123,6 +126,7 @@ class ErrorPopupDetector {
|
|
|
123
126
|
this.lastDetectedKey = null;
|
|
124
127
|
this.lastDetectedInfo = null;
|
|
125
128
|
this.lastNotifiedAt = 0;
|
|
129
|
+
this.emptyPollGate.reset();
|
|
126
130
|
this.schedulePoll();
|
|
127
131
|
}
|
|
128
132
|
/** Stop monitoring. */
|
|
@@ -208,28 +212,36 @@ class ErrorPopupDetector {
|
|
|
208
212
|
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
209
213
|
const info = result?.result?.value ?? null;
|
|
210
214
|
if (info) {
|
|
215
|
+
this.emptyPollGate.recordDetection();
|
|
211
216
|
// Duplicate prevention: use title + body snippet as key
|
|
212
217
|
const key = `${info.title}::${info.body.slice(0, 100)}`;
|
|
213
218
|
const now = Date.now();
|
|
214
219
|
const withinCooldown = (now - this.lastNotifiedAt) < ErrorPopupDetector.COOLDOWN_MS;
|
|
215
|
-
if (key !== this.lastDetectedKey
|
|
220
|
+
if (key !== this.lastDetectedKey) {
|
|
221
|
+
this.lastNotifiedAt = now;
|
|
216
222
|
this.lastDetectedKey = key;
|
|
217
223
|
this.lastDetectedInfo = info;
|
|
224
|
+
this.onErrorPopup(info);
|
|
225
|
+
}
|
|
226
|
+
else if (!withinCooldown) {
|
|
218
227
|
this.lastNotifiedAt = now;
|
|
228
|
+
this.lastDetectedInfo = info;
|
|
219
229
|
this.onErrorPopup(info);
|
|
220
230
|
}
|
|
221
|
-
else
|
|
222
|
-
// Same key -- update stored info silently
|
|
231
|
+
else {
|
|
232
|
+
// Same key, within cooldown -- update stored info silently
|
|
223
233
|
this.lastDetectedInfo = info;
|
|
224
234
|
}
|
|
225
235
|
}
|
|
226
236
|
else {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.onResolved
|
|
237
|
+
if (this.emptyPollGate.recordEmptyPoll()) {
|
|
238
|
+
// Reset when popup disappears (prepare for next detection)
|
|
239
|
+
const wasDetected = this.lastDetectedKey !== null;
|
|
240
|
+
this.lastDetectedKey = null;
|
|
241
|
+
this.lastDetectedInfo = null;
|
|
242
|
+
if (wasDetected && this.onResolved) {
|
|
243
|
+
this.onResolved();
|
|
244
|
+
}
|
|
233
245
|
}
|
|
234
246
|
}
|
|
235
247
|
}
|