lazy-gravity 0.9.1 → 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 +233 -41
- package/dist/bot/telegramJoinCommand.js +9 -0
- package/dist/bot/telegramMessageHandler.js +7 -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 +148 -27
- 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 +2 -2
|
@@ -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
|
}
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ChatSessionService = void 0;
|
|
4
|
+
exports.buildActivateViaPastConversationsScript = buildActivateViaPastConversationsScript;
|
|
4
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
|
+
`;
|
|
5
21
|
/** Script to get the state of the new chat button */
|
|
6
22
|
const GET_NEW_CHAT_BUTTON_SCRIPT = `(() => {
|
|
7
23
|
const btn = document.querySelector('[data-tooltip-id="new-conversation-tooltip"]');
|
|
8
24
|
if (!btn) return { found: false };
|
|
9
25
|
const cursor = window.getComputedStyle(btn).cursor;
|
|
26
|
+
if (typeof btn.scrollIntoView === 'function') {
|
|
27
|
+
btn.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
28
|
+
}
|
|
10
29
|
const rect = btn.getBoundingClientRect();
|
|
11
30
|
return {
|
|
12
31
|
found: true,
|
|
@@ -105,6 +124,9 @@ const GET_SESSION_VIEW_STATE_SCRIPT = `(() => {
|
|
|
105
124
|
const FIND_PAST_CONVERSATIONS_BUTTON_SCRIPT = `(() => {
|
|
106
125
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
107
126
|
const getRect = (el) => {
|
|
127
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
128
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
129
|
+
}
|
|
108
130
|
const rect = el.getBoundingClientRect();
|
|
109
131
|
return { found: true, x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
|
|
110
132
|
};
|
|
@@ -213,6 +235,9 @@ const FIND_SHOW_MORE_BUTTON_SCRIPT = `(() => {
|
|
|
213
235
|
if (!isVisible(el)) continue;
|
|
214
236
|
const text = (el.textContent || '').trim();
|
|
215
237
|
if (/^Show\\s+\\d+\\s+more/i.test(text)) {
|
|
238
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
239
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
240
|
+
}
|
|
216
241
|
const rect = el.getBoundingClientRect();
|
|
217
242
|
return { found: true, x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
|
|
218
243
|
}
|
|
@@ -240,11 +265,14 @@ function buildActivateChatByTitleScript(title) {
|
|
|
240
265
|
return true;
|
|
241
266
|
};
|
|
242
267
|
|
|
268
|
+
${FUZZY_MATCH_HELPERS_SCRIPT}
|
|
269
|
+
|
|
243
270
|
const nodes = Array.from(panel.querySelectorAll('button, [role="button"], a, li, div, span'))
|
|
244
271
|
.filter(isVisible);
|
|
245
272
|
|
|
246
273
|
const exact = [];
|
|
247
274
|
const includes = [];
|
|
275
|
+
const fuzzy = [];
|
|
248
276
|
for (const node of nodes) {
|
|
249
277
|
const text = normalize(node.textContent || '');
|
|
250
278
|
if (!text) continue;
|
|
@@ -252,6 +280,8 @@ function buildActivateChatByTitleScript(title) {
|
|
|
252
280
|
exact.push({ node, textLength: text.length });
|
|
253
281
|
} else if (text.includes(wanted)) {
|
|
254
282
|
includes.push({ node, textLength: text.length });
|
|
283
|
+
} else if (wordsMatch(text, wantedRaw)) {
|
|
284
|
+
fuzzy.push({ node, textLength: text.length });
|
|
255
285
|
}
|
|
256
286
|
}
|
|
257
287
|
|
|
@@ -261,10 +291,18 @@ function buildActivateChatByTitleScript(title) {
|
|
|
261
291
|
return list[0].node;
|
|
262
292
|
};
|
|
263
293
|
|
|
264
|
-
const target = pick(exact) || pick(includes);
|
|
294
|
+
const target = pick(exact) || pick(includes) || pick(fuzzy);
|
|
265
295
|
if (!target) return { ok: false, error: 'Chat title not found in side panel' };
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
};
|
|
268
306
|
})()`;
|
|
269
307
|
}
|
|
270
308
|
/**
|
|
@@ -290,9 +328,9 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
290
328
|
|
|
291
329
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
292
330
|
const asArray = (nodeList) => Array.from(nodeList || []);
|
|
293
|
-
const
|
|
294
|
-
if (!el || !(el instanceof Element)) return
|
|
295
|
-
|
|
331
|
+
const getLabelParts = (el) => {
|
|
332
|
+
if (!el || !(el instanceof Element)) return [];
|
|
333
|
+
return [
|
|
296
334
|
el.textContent || '',
|
|
297
335
|
el.getAttribute('aria-label') || '',
|
|
298
336
|
el.getAttribute('title') || '',
|
|
@@ -300,13 +338,26 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
300
338
|
el.getAttribute('data-tooltip-content') || '',
|
|
301
339
|
el.getAttribute('data-testid') || '',
|
|
302
340
|
];
|
|
303
|
-
|
|
341
|
+
};
|
|
342
|
+
const getLabelText = (el) => getLabelParts(el).filter(Boolean).join(' ');
|
|
343
|
+
const resolveMatchedTitle = (el) => {
|
|
344
|
+
const parts = getLabelParts(el);
|
|
345
|
+
for (const part of parts) {
|
|
346
|
+
if (!part) continue;
|
|
347
|
+
if (normalize(part) === wanted || (wantedLoose && normalizeLoose(part) === wantedLoose)) {
|
|
348
|
+
return wantedRaw;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const visible = (parts[0] || '').trim();
|
|
352
|
+
return visible || null;
|
|
304
353
|
};
|
|
305
354
|
const getClickable = (el) => {
|
|
306
355
|
if (!el || !(el instanceof Element)) return null;
|
|
307
356
|
const clickable = el.closest('button, [role="button"], a, li, [role="option"], [data-testid*="conversation"]');
|
|
308
357
|
return clickable instanceof HTMLElement ? clickable : (el instanceof HTMLElement ? el : null);
|
|
309
358
|
};
|
|
359
|
+
${FUZZY_MATCH_HELPERS_SCRIPT}
|
|
360
|
+
|
|
310
361
|
const pickBest = (elements, patterns) => {
|
|
311
362
|
const matched = [];
|
|
312
363
|
for (const el of elements) {
|
|
@@ -325,6 +376,9 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
325
376
|
) {
|
|
326
377
|
matched.push({ el, score: Math.abs(text.length - pattern.length) });
|
|
327
378
|
break;
|
|
379
|
+
} else if (wordsMatch(text, pattern)) {
|
|
380
|
+
matched.push({ el, score: Math.abs(text.length - pattern.length) + 1000 });
|
|
381
|
+
break;
|
|
328
382
|
}
|
|
329
383
|
}
|
|
330
384
|
}
|
|
@@ -346,7 +400,13 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
346
400
|
if (!el) return false;
|
|
347
401
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
348
402
|
el.focus();
|
|
349
|
-
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
|
+
}
|
|
350
410
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
351
411
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
352
412
|
return true;
|
|
@@ -469,28 +529,41 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
469
529
|
clickByPatterns(['select a conversation', 'select conversation', 'conversation'], '[role="button"], button, [aria-haspopup], [data-testid*="conversation"]');
|
|
470
530
|
await wait(220);
|
|
471
531
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
);
|
|
488
550
|
}
|
|
489
551
|
}
|
|
490
|
-
|
|
552
|
+
|
|
553
|
+
if (!selectedOption) {
|
|
491
554
|
return { ok: false, error: 'Conversation not found in Past Conversations' };
|
|
492
555
|
}
|
|
493
|
-
|
|
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
|
+
};
|
|
494
567
|
})();
|
|
495
568
|
})()`;
|
|
496
569
|
}
|
|
@@ -872,7 +945,12 @@ class ChatSessionService {
|
|
|
872
945
|
if (!clicked) {
|
|
873
946
|
pastResult = await this.tryActivateByPastConversations(cdpService, title);
|
|
874
947
|
clicked = pastResult.ok;
|
|
875
|
-
|
|
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
|
+
}
|
|
876
954
|
}
|
|
877
955
|
if (clicked) {
|
|
878
956
|
break;
|
|
@@ -943,6 +1021,7 @@ class ChatSessionService {
|
|
|
943
1021
|
error: `Past Conversations selected a different chat (expected="${title}", actual="${afterPast.title}")`,
|
|
944
1022
|
};
|
|
945
1023
|
}
|
|
1024
|
+
await this.closePanelWithEscape(cdpService);
|
|
946
1025
|
return {
|
|
947
1026
|
ok: false,
|
|
948
1027
|
error: `Activated chat did not match target title (expected="${title}", actual="${after.title}") ` +
|
|
@@ -973,6 +1052,9 @@ class ChatSessionService {
|
|
|
973
1052
|
});
|
|
974
1053
|
const value = result?.result?.value;
|
|
975
1054
|
if (value?.ok) {
|
|
1055
|
+
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
|
1056
|
+
await this.cdpMouseClick(cdpService, value.x, value.y);
|
|
1057
|
+
}
|
|
976
1058
|
return {
|
|
977
1059
|
ok: true,
|
|
978
1060
|
...(typeof value.matchedTitle === 'string'
|
|
@@ -1010,5 +1092,44 @@ class ChatSessionService {
|
|
|
1010
1092
|
}
|
|
1011
1093
|
return { found: false, enabled: false, x: 0, y: 0 };
|
|
1012
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
|
+
}
|
|
1013
1134
|
}
|
|
1014
1135
|
exports.ChatSessionService = ChatSessionService;
|