lazy-gravity 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +191 -22
- package/dist/commands/chatCommandHandler.js +11 -41
- package/dist/events/interactionCreateHandler.js +158 -27
- package/dist/events/messageCreateHandler.js +14 -4
- 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/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/utils/fileOpenCache.js +2 -6
- package/dist/utils/questionActionUtils.js +22 -0
- package/package.json +1 -1
|
@@ -21,6 +21,8 @@ const notificationSender_1 = require("../services/notificationSender");
|
|
|
21
21
|
const questionSelectAction_1 = require("../handlers/questionSelectAction");
|
|
22
22
|
const questionSkipAction_1 = require("../handlers/questionSkipAction");
|
|
23
23
|
const wrappers_1 = require("../platform/discord/wrappers");
|
|
24
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
25
|
+
const projectResolver_1 = require("../utils/projectResolver");
|
|
24
26
|
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
25
27
|
const projectListUi_1 = require("../ui/projectListUi");
|
|
26
28
|
const modeService_1 = require("../services/modeService");
|
|
@@ -226,6 +228,19 @@ function createInteractionCreateHandler(deps) {
|
|
|
226
228
|
embeds: [updatedEmbed],
|
|
227
229
|
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
228
230
|
});
|
|
231
|
+
if (success && (approvalAction.action === 'approve' || approvalAction.action === 'always_allow') && deps.promptDispatcher && interaction.channelId && interaction.message) {
|
|
232
|
+
const cdp = projectName
|
|
233
|
+
? deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
234
|
+
: null;
|
|
235
|
+
if (cdp) {
|
|
236
|
+
deps.promptDispatcher.resume({
|
|
237
|
+
message: interaction.message,
|
|
238
|
+
prompt: '',
|
|
239
|
+
cdp,
|
|
240
|
+
options: buildResumeOptions()
|
|
241
|
+
}).catch(e => logger_1.logger.error('[Approval] Failed to resume monitoring:', e));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
229
244
|
}
|
|
230
245
|
else {
|
|
231
246
|
await interaction.reply({ content: 'Approval button not found.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -873,8 +888,39 @@ function createInteractionCreateHandler(deps) {
|
|
|
873
888
|
}
|
|
874
889
|
if (interaction.customId.startsWith('file_open:')) {
|
|
875
890
|
await interaction.deferUpdate().catch(() => { });
|
|
876
|
-
|
|
877
|
-
const
|
|
891
|
+
let fileUrl = undefined;
|
|
892
|
+
const suffix = interaction.customId.substring('file_open:'.length);
|
|
893
|
+
if (suffix.startsWith('rel:')) {
|
|
894
|
+
const relativePath = suffix.substring('rel:'.length);
|
|
895
|
+
const workspacePath = interaction.channelId ? (deps.wsHandler.getWorkspaceForChannel(interaction.channelId) ||
|
|
896
|
+
deps.chatSessionRepo?.findByChannelId(interaction.channelId)?.workspacePath) : undefined;
|
|
897
|
+
if (workspacePath) {
|
|
898
|
+
fileUrl = `file:///${path_1.default.join(workspacePath, relativePath).replace(/\\/g, '/')}`;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else if (suffix.startsWith('art:')) {
|
|
902
|
+
const parts = suffix.substring('art:'.length).split(':');
|
|
903
|
+
if (parts.length >= 2) {
|
|
904
|
+
const convId = parts[0];
|
|
905
|
+
const filename = parts.slice(1).join(':');
|
|
906
|
+
if (deps.artifactService) {
|
|
907
|
+
const artifacts = deps.artifactService.listArtifacts(convId);
|
|
908
|
+
const matched = artifacts.find(art => art.filename.toLowerCase() === filename.toLowerCase());
|
|
909
|
+
if (matched) {
|
|
910
|
+
fileUrl = `file:///${matched.absolutePath.replace(/\\/g, '/')}`;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (!fileUrl) {
|
|
914
|
+
const brainDir = path_1.default.join(require('os').homedir(), '.gemini', 'antigravity', 'brain');
|
|
915
|
+
fileUrl = `file:///${path_1.default.join(brainDir, convId, filename).replace(/\\/g, '/')}`;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
// Fallback to cache (for legacy file_open:<hashId> or explicit cache usage)
|
|
921
|
+
const hashId = suffix.startsWith('cache:') ? suffix.substring('cache:'.length) : suffix;
|
|
922
|
+
fileUrl = fileOpenCache_1.fileOpenCache.get(hashId);
|
|
923
|
+
}
|
|
878
924
|
if (!fileUrl) {
|
|
879
925
|
await interaction.followUp({
|
|
880
926
|
content: (0, i18n_1.t)('File URL not found in cache or expired.'),
|
|
@@ -893,37 +939,58 @@ function createInteractionCreateHandler(deps) {
|
|
|
893
939
|
}
|
|
894
940
|
resolvedPath = path_1.default.resolve(rawPath);
|
|
895
941
|
}
|
|
896
|
-
|
|
897
|
-
if (
|
|
898
|
-
workspacePath =
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
942
|
+
const isArtifact = resolvedPath.includes('.gemini') && resolvedPath.includes('antigravity') && resolvedPath.includes('brain');
|
|
943
|
+
if (!isArtifact) {
|
|
944
|
+
let workspacePath = undefined;
|
|
945
|
+
if (interaction.channelId) {
|
|
946
|
+
workspacePath = deps.wsHandler.getWorkspaceForChannel(interaction.channelId) ||
|
|
947
|
+
deps.chatSessionRepo?.findByChannelId(interaction.channelId)?.workspacePath;
|
|
948
|
+
}
|
|
949
|
+
if (!workspacePath) {
|
|
950
|
+
await interaction.followUp({
|
|
951
|
+
content: `❌ Error: Cannot open files without an active workspace.`,
|
|
952
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
953
|
+
}).catch(logger_1.logger.error);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const resolvedWorkspacePath = path_1.default.resolve(workspacePath);
|
|
957
|
+
const relative = path_1.default.relative(resolvedWorkspacePath, resolvedPath);
|
|
958
|
+
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
|
959
|
+
await interaction.followUp({
|
|
960
|
+
content: `❌ Error: Cannot open files outside the workspace root.`,
|
|
961
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
962
|
+
}).catch(logger_1.logger.error);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
915
965
|
}
|
|
916
966
|
try {
|
|
967
|
+
const comp = interaction.component;
|
|
968
|
+
const buttonLabel = comp?.type === 2 ? comp.label : null;
|
|
969
|
+
const isReviewBtn = suffix.startsWith('art:') ||
|
|
970
|
+
buttonLabel?.toLowerCase().includes('review') ||
|
|
971
|
+
resolvedPath.endsWith('walkthrough.md') ||
|
|
972
|
+
resolvedPath.endsWith('task.md') ||
|
|
973
|
+
resolvedPath.endsWith('implementation_plan.md');
|
|
917
974
|
const fileContent = fs_1.default.readFileSync(resolvedPath, 'utf8');
|
|
918
975
|
const MAX_DESC_LEN = 4096;
|
|
919
|
-
|
|
976
|
+
let truncated = fileContent.length > MAX_DESC_LEN
|
|
920
977
|
? fileContent.substring(0, MAX_DESC_LEN - 15) + '\n\n(truncated)'
|
|
921
978
|
: fileContent;
|
|
979
|
+
if (isReviewBtn) {
|
|
980
|
+
truncated += '\n\n💡 *To request changes to this artifact, simply reply in this channel.*';
|
|
981
|
+
}
|
|
922
982
|
const embed = new discord_js_1.EmbedBuilder()
|
|
923
|
-
.setTitle(`Opened: ${path_1.default.basename(resolvedPath)}`)
|
|
983
|
+
.setTitle(isReviewBtn ? `Reviewing: ${path_1.default.basename(resolvedPath)}` : `Opened: ${path_1.default.basename(resolvedPath)}`)
|
|
924
984
|
.setDescription(truncated)
|
|
925
|
-
.setColor(0x3498DB)
|
|
985
|
+
.setColor(isReviewBtn ? 0x2ECC71 : 0x3498DB)
|
|
926
986
|
.setTimestamp();
|
|
987
|
+
if (isReviewBtn) {
|
|
988
|
+
await interaction.followUp({
|
|
989
|
+
embeds: [embed],
|
|
990
|
+
components: []
|
|
991
|
+
}).catch(logger_1.logger.error);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
927
994
|
(0, child_process_1.execFile)((0, pathUtils_1.getAntigravityCliPath)(), [resolvedPath], async (error) => {
|
|
928
995
|
if (error) {
|
|
929
996
|
logger_1.logger.error(`Failed to open file via CLI: ${error.message}`);
|
|
@@ -933,9 +1000,14 @@ function createInteractionCreateHandler(deps) {
|
|
|
933
1000
|
}).catch(() => { });
|
|
934
1001
|
}
|
|
935
1002
|
else {
|
|
1003
|
+
const closeRow = new discord_js_1.ActionRowBuilder().addComponents(new discord_js_1.ButtonBuilder()
|
|
1004
|
+
.setCustomId('file_close_active')
|
|
1005
|
+
.setLabel('Close Active Tab in IDE')
|
|
1006
|
+
.setStyle(discord_js_1.ButtonStyle.Secondary));
|
|
936
1007
|
await interaction.followUp({
|
|
937
1008
|
content: `✅ Opened file in IDE: **${path_1.default.basename(resolvedPath)}**`,
|
|
938
1009
|
embeds: [embed],
|
|
1010
|
+
components: [closeRow],
|
|
939
1011
|
flags: discord_js_1.MessageFlags.Ephemeral
|
|
940
1012
|
}).catch(() => { });
|
|
941
1013
|
}
|
|
@@ -949,9 +1021,38 @@ function createInteractionCreateHandler(deps) {
|
|
|
949
1021
|
}
|
|
950
1022
|
return;
|
|
951
1023
|
}
|
|
1024
|
+
if (interaction.customId === 'file_close_active') {
|
|
1025
|
+
await interaction.deferUpdate().catch(() => { });
|
|
1026
|
+
try {
|
|
1027
|
+
const cdp = deps.getCurrentCdp(deps.bridge);
|
|
1028
|
+
if (!cdp) {
|
|
1029
|
+
await interaction.followUp({
|
|
1030
|
+
content: `❌ No active CDP connection found.`,
|
|
1031
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
1032
|
+
}).catch(() => { });
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (!cdp.isConnected()) {
|
|
1036
|
+
await cdp.connect();
|
|
1037
|
+
}
|
|
1038
|
+
await cdp.closeActiveEditor();
|
|
1039
|
+
await interaction.followUp({
|
|
1040
|
+
content: `✅ Sent close command to IDE.`,
|
|
1041
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
1042
|
+
}).catch(() => { });
|
|
1043
|
+
}
|
|
1044
|
+
catch (e) {
|
|
1045
|
+
logger_1.logger.error(`Failed to close active editor: ${e.message}`);
|
|
1046
|
+
await interaction.followUp({
|
|
1047
|
+
content: `❌ Error closing editor: ${e.message}`,
|
|
1048
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
1049
|
+
}).catch(() => { });
|
|
1050
|
+
}
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
952
1053
|
}
|
|
953
1054
|
catch (error) {
|
|
954
|
-
logger_1.logger.error(
|
|
1055
|
+
logger_1.logger.error(`[InteractionCreate] Error handling interaction: ${error instanceof Error ? error.message : String(error)}`);
|
|
955
1056
|
try {
|
|
956
1057
|
if (!interaction.replied && !interaction.deferred) {
|
|
957
1058
|
await interaction.reply({ content: 'An error occurred while processing the button action.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -1243,7 +1344,22 @@ function createInteractionCreateHandler(deps) {
|
|
|
1243
1344
|
try {
|
|
1244
1345
|
const action = (0, questionSelectAction_1.createQuestionSelectAction)({ bridge: deps.bridge, wsHandler: deps.wsHandler });
|
|
1245
1346
|
const wrapped = (0, wrappers_1.wrapDiscordSelect)(interaction);
|
|
1246
|
-
await action.execute(wrapped, interaction.values);
|
|
1347
|
+
const success = await action.execute(wrapped, interaction.values);
|
|
1348
|
+
if (success && deps.promptDispatcher && interaction.channelId && interaction.message) {
|
|
1349
|
+
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SELECT_ACTION_PREFIX);
|
|
1350
|
+
if (parsed) {
|
|
1351
|
+
const projectName = (0, projectResolver_1.resolveProjectName)({ bridge: deps.bridge, wsHandler: deps.wsHandler }, interaction.channelId, parsed.projectName);
|
|
1352
|
+
const cdp = projectName ? deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction))) : null;
|
|
1353
|
+
if (cdp) {
|
|
1354
|
+
deps.promptDispatcher.resume({
|
|
1355
|
+
message: interaction.message,
|
|
1356
|
+
prompt: '',
|
|
1357
|
+
cdp,
|
|
1358
|
+
options: buildResumeOptions()
|
|
1359
|
+
}).catch(e => logger_1.logger.error('[QuestionSelectAction] Failed to resume monitoring:', e));
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1247
1363
|
}
|
|
1248
1364
|
catch (err) {
|
|
1249
1365
|
logger_1.logger.error('[QuestionSelectAction] Error executing action:', err);
|
|
@@ -1260,7 +1376,22 @@ function createInteractionCreateHandler(deps) {
|
|
|
1260
1376
|
try {
|
|
1261
1377
|
const action = (0, questionSkipAction_1.createQuestionSkipAction)({ bridge: deps.bridge, wsHandler: deps.wsHandler });
|
|
1262
1378
|
const wrapped = (0, wrappers_1.wrapDiscordButton)(interaction);
|
|
1263
|
-
await action.execute(wrapped, {});
|
|
1379
|
+
const success = await action.execute(wrapped, {});
|
|
1380
|
+
if (success && deps.promptDispatcher && interaction.channelId && interaction.message) {
|
|
1381
|
+
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SKIP_ACTION_PREFIX);
|
|
1382
|
+
if (parsed) {
|
|
1383
|
+
const projectName = (0, projectResolver_1.resolveProjectName)({ bridge: deps.bridge, wsHandler: deps.wsHandler }, interaction.channelId, parsed.projectName);
|
|
1384
|
+
const cdp = projectName ? deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction))) : null;
|
|
1385
|
+
if (cdp) {
|
|
1386
|
+
deps.promptDispatcher.resume({
|
|
1387
|
+
message: interaction.message,
|
|
1388
|
+
prompt: '',
|
|
1389
|
+
cdp,
|
|
1390
|
+
options: buildResumeOptions()
|
|
1391
|
+
}).catch(e => logger_1.logger.error('[QuestionSkipAction] Failed to resume monitoring:', e));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1264
1395
|
}
|
|
1265
1396
|
catch (err) {
|
|
1266
1397
|
logger_1.logger.error('[QuestionSkipAction] Error executing action:', err);
|
|
@@ -225,7 +225,10 @@ function createMessageCreateHandler(deps) {
|
|
|
225
225
|
const chunk = textAttachments.slice(i, i + CONCURRENCY_LIMIT);
|
|
226
226
|
const results = await Promise.all(chunk.map(async (textAtt) => {
|
|
227
227
|
try {
|
|
228
|
-
const
|
|
228
|
+
const controller = new AbortController();
|
|
229
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
230
|
+
const res = await fetch(textAtt.url, { signal: controller.signal });
|
|
231
|
+
clearTimeout(timeoutId);
|
|
229
232
|
if (res.ok) {
|
|
230
233
|
const content = await res.text();
|
|
231
234
|
return `\n\n[Attached File: ${textAtt.name}]\n\`\`\`\n${content}\n\`\`\``;
|
|
@@ -349,9 +352,16 @@ function createMessageCreateHandler(deps) {
|
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
if (!activationResult.ok) {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
+
const isDeleted = activationResult.error?.includes('Conversation not found in Past Conversations');
|
|
356
|
+
let replyText = `⚠️ Could not route this message to the bound session (${session.displayName}).\n*Reason: ${activationResult.error}*`;
|
|
357
|
+
if (isDeleted) {
|
|
358
|
+
deps.chatSessionRepo.deleteByChannelId(message.channelId);
|
|
359
|
+
replyText += `\n\n💡 **Tip**: This session appears to have been deleted in the IDE. I have unbound this channel so your next message will start a fresh chat. You can also type \`/new\` anytime.`;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
replyText += `\n\n💡 If this session is broken, type \`/new\` to force a new chat.`;
|
|
363
|
+
}
|
|
364
|
+
await message.reply(replyText).catch(() => { });
|
|
355
365
|
return;
|
|
356
366
|
}
|
|
357
367
|
}
|
|
@@ -4,7 +4,7 @@ exports.createFileChangeButtonAction = createFileChangeButtonAction;
|
|
|
4
4
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
5
5
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
6
6
|
const logger_1 = require("../utils/logger");
|
|
7
|
-
const
|
|
7
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
8
8
|
function createFileChangeButtonAction(deps) {
|
|
9
9
|
return {
|
|
10
10
|
match(customId) {
|
|
@@ -36,32 +36,20 @@ function createFileChangeButtonAction(deps) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
const targetText = action === 'accept' ? 'Accept all' : 'Reject all';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const success = await (0, questionActionUtils_1.executeBrowserClick)(cdp, targetText);
|
|
40
|
+
if (success) {
|
|
41
|
+
await interaction
|
|
42
|
+
.update({
|
|
43
|
+
text: `${action === 'accept' ? '✅ Accepted' : '❌ Rejected'} file changes.`,
|
|
44
|
+
components: [],
|
|
45
|
+
})
|
|
46
|
+
.catch((err) => {
|
|
47
|
+
logger_1.logger.warn('[FileChangeAction] update failed:', err);
|
|
44
48
|
});
|
|
45
|
-
if (result?.result?.value?.ok) {
|
|
46
|
-
await interaction
|
|
47
|
-
.update({
|
|
48
|
-
text: `${action === 'accept' ? '✅ Accepted' : '❌ Rejected'} file changes.`,
|
|
49
|
-
components: [],
|
|
50
|
-
})
|
|
51
|
-
.catch((err) => {
|
|
52
|
-
logger_1.logger.warn('[FileChangeAction] update failed:', err);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
await interaction
|
|
57
|
-
.followUp({ text: `Could not find the "${targetText}" button in the IDE. The file change prompt may have been dismissed.` })
|
|
58
|
-
.catch(() => { });
|
|
59
|
-
}
|
|
60
49
|
}
|
|
61
|
-
|
|
62
|
-
logger_1.logger.error('[FileChangeAction] Error clicking button:', error);
|
|
50
|
+
else {
|
|
63
51
|
await interaction
|
|
64
|
-
.followUp({ text:
|
|
52
|
+
.followUp({ text: `Could not find the "${targetText}" button in the IDE. The file change prompt may have been dismissed.` })
|
|
65
53
|
.catch(() => { });
|
|
66
54
|
}
|
|
67
55
|
},
|
|
@@ -4,14 +4,7 @@ exports.parseGenericActionCustomId = parseGenericActionCustomId;
|
|
|
4
4
|
exports.createGenericActionButtonAction = createGenericActionButtonAction;
|
|
5
5
|
const logger_1 = require("../utils/logger");
|
|
6
6
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
7
|
-
const
|
|
8
|
-
/**
|
|
9
|
-
* Parses a generic action button customId into its components.
|
|
10
|
-
* Format: action_btn_[action_name]:[project_name]:[channel_id]
|
|
11
|
-
*
|
|
12
|
-
* @param customId The customId to parse
|
|
13
|
-
* @returns Parsed action details, or null if not an action button
|
|
14
|
-
*/
|
|
7
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
15
8
|
function parseGenericActionCustomId(customId) {
|
|
16
9
|
if (!customId.startsWith('action_btn_'))
|
|
17
10
|
return null;
|
|
@@ -19,10 +12,15 @@ function parseGenericActionCustomId(customId) {
|
|
|
19
12
|
const actionName = parts[0].replace('action_btn_', '').replace(/_/g, ' ');
|
|
20
13
|
// Capitalize first letter
|
|
21
14
|
const formattedName = actionName.charAt(0).toUpperCase() + actionName.slice(1);
|
|
15
|
+
if (parts.length < 3) {
|
|
16
|
+
return { actionName: formattedName };
|
|
17
|
+
}
|
|
18
|
+
const channelId = parts[parts.length - 1];
|
|
19
|
+
const projectName = parts.slice(1, parts.length - 1).join(':');
|
|
22
20
|
return {
|
|
23
21
|
actionName: formattedName,
|
|
24
|
-
projectName:
|
|
25
|
-
channelId:
|
|
22
|
+
projectName: projectName || undefined,
|
|
23
|
+
channelId: channelId || undefined,
|
|
26
24
|
};
|
|
27
25
|
}
|
|
28
26
|
function createGenericActionButtonAction(deps) {
|
|
@@ -42,12 +40,17 @@ function createGenericActionButtonAction(deps) {
|
|
|
42
40
|
const actionName = params.actionName;
|
|
43
41
|
const channelId = params.channelId || interaction.channel?.id;
|
|
44
42
|
if (!channelId) {
|
|
43
|
+
await interaction.reply({
|
|
44
|
+
text: 'Error: Cannot resolve channel ID.',
|
|
45
|
+
ephemeral: true,
|
|
46
|
+
}).catch(() => { });
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
const projectName = (0, projectResolver_1.resolveProjectName)(deps, channelId, params.projectName);
|
|
48
50
|
await interaction.deferUpdate().catch(() => { });
|
|
51
|
+
const accountName = deps.bridge.selectedAccountByChannel?.get(channelId) || 'default';
|
|
49
52
|
const cdp = projectName
|
|
50
|
-
? deps.bridge.pool.getConnected(projectName)
|
|
53
|
+
? deps.bridge.pool.getConnected(projectName, accountName)
|
|
51
54
|
: null;
|
|
52
55
|
if (!cdp) {
|
|
53
56
|
await interaction.followUp({
|
|
@@ -57,13 +60,8 @@ function createGenericActionButtonAction(deps) {
|
|
|
57
60
|
}
|
|
58
61
|
// Simulate DOM click instead of chat injection
|
|
59
62
|
logger_1.logger.info(`[GenericActionButton] Clicking action button "${actionName}" via DOM script`);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
expression: script,
|
|
63
|
-
returnByValue: true,
|
|
64
|
-
});
|
|
65
|
-
const resultValue = evalResult.result?.value;
|
|
66
|
-
if (!resultValue?.ok) {
|
|
63
|
+
const success = await (0, questionActionUtils_1.executeBrowserClick)(cdp, actionName);
|
|
64
|
+
if (!success) {
|
|
67
65
|
await interaction.followUp({
|
|
68
66
|
text: `Failed to execute action: Button "${actionName}" not found or obscured.`,
|
|
69
67
|
}).catch(() => { });
|
|
@@ -5,11 +5,48 @@
|
|
|
5
5
|
* Handles Open / Proceed button presses for the planning mode dialog
|
|
6
6
|
* from both Discord and Telegram using the ButtonAction interface.
|
|
7
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
42
|
exports.createPlanningButtonAction = createPlanningButtonAction;
|
|
10
43
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
11
44
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
12
45
|
const logger_1 = require("../utils/logger");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
13
50
|
const MAX_PLAN_CONTENT = 4096;
|
|
14
51
|
function createPlanningButtonAction(deps) {
|
|
15
52
|
return {
|
|
@@ -25,11 +62,10 @@ function createPlanningButtonAction(deps) {
|
|
|
25
62
|
},
|
|
26
63
|
async execute(interaction, params) {
|
|
27
64
|
const { action, channelId } = params;
|
|
28
|
-
// Acknowledge immediately so Telegram doesn't time out
|
|
29
|
-
await interaction.deferUpdate().catch(() => { });
|
|
30
65
|
if (channelId && channelId !== interaction.channel.id) {
|
|
66
|
+
await interaction.deferUpdate().catch(() => { });
|
|
31
67
|
await interaction
|
|
32
|
-
.
|
|
68
|
+
.followUp({ text: 'This planning action is linked to a different session channel.', ephemeral: true })
|
|
33
69
|
.catch(() => { });
|
|
34
70
|
return;
|
|
35
71
|
}
|
|
@@ -38,14 +74,42 @@ function createPlanningButtonAction(deps) {
|
|
|
38
74
|
? deps.bridge.pool.getPlanningDetector(projectName)
|
|
39
75
|
: undefined;
|
|
40
76
|
if (!detector) {
|
|
77
|
+
await interaction.deferUpdate().catch(() => { });
|
|
41
78
|
await interaction
|
|
42
|
-
.
|
|
79
|
+
.followUp({ text: 'Planning detector not found.', ephemeral: true })
|
|
43
80
|
.catch(() => { });
|
|
44
81
|
return;
|
|
45
82
|
}
|
|
46
83
|
if (action === 'open') {
|
|
84
|
+
const info = detector.getLastDetectedInfo();
|
|
85
|
+
const isReview = info?.openText?.toLowerCase().includes('review');
|
|
86
|
+
let modalFailed = false;
|
|
87
|
+
if (isReview && interaction.showModal) {
|
|
88
|
+
try {
|
|
89
|
+
await interaction.showModal({
|
|
90
|
+
title: 'Submit Comment',
|
|
91
|
+
customId: `plan_comment_${projectName || ''}_${channelId}`,
|
|
92
|
+
components: [{
|
|
93
|
+
components: [{
|
|
94
|
+
type: 'textInput',
|
|
95
|
+
customId: 'comment',
|
|
96
|
+
label: 'Add a message...',
|
|
97
|
+
style: 'paragraph',
|
|
98
|
+
required: false
|
|
99
|
+
}]
|
|
100
|
+
}]
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
logger_1.logger.error('[PlanningAction] showModal failed:', err);
|
|
106
|
+
modalFailed = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Acknowledge if we didn't show a modal
|
|
110
|
+
await interaction.deferUpdate().catch(() => { });
|
|
47
111
|
let clicked = false;
|
|
48
|
-
if (
|
|
112
|
+
if (info?.hasOpenButton && (!isReview || modalFailed)) {
|
|
49
113
|
try {
|
|
50
114
|
clicked = await detector.clickOpenButton();
|
|
51
115
|
if (clicked) {
|
|
@@ -56,7 +120,8 @@ function createPlanningButtonAction(deps) {
|
|
|
56
120
|
logger_1.logger.error('[PlanningAction] clickOpenButton failed:', e);
|
|
57
121
|
}
|
|
58
122
|
}
|
|
59
|
-
//
|
|
123
|
+
// Since we only come here if it's NOT a review (or modal failed),
|
|
124
|
+
// we can just extract plan content and followUp
|
|
60
125
|
let planContent = null;
|
|
61
126
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
62
127
|
planContent = await detector.extractPlanContent();
|
|
@@ -64,22 +129,42 @@ function createPlanningButtonAction(deps) {
|
|
|
64
129
|
break;
|
|
65
130
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
66
131
|
}
|
|
132
|
+
if (!planContent && !clicked) {
|
|
133
|
+
// Fallback to reading implementation_plan.md from the workspace
|
|
134
|
+
const workspaceId = deps.wsHandler.getWorkspaceForChannel(interaction.channel.id);
|
|
135
|
+
if (workspaceId) {
|
|
136
|
+
const planFile = path.join(workspaceId, 'implementation_plan.md');
|
|
137
|
+
if (fs.existsSync(planFile)) {
|
|
138
|
+
try {
|
|
139
|
+
planContent = fs.readFileSync(planFile, 'utf8');
|
|
140
|
+
// Proactively open the file in the IDE
|
|
141
|
+
(0, child_process_1.execFile)((0, pathUtils_1.getAntigravityCliPath)(), [planFile], (err) => {
|
|
142
|
+
if (err)
|
|
143
|
+
logger_1.logger.error(`[PlanningAction] Failed to open plan via CLI: ${err.message}`);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
logger_1.logger.error(`[PlanningAction] Failed to read plan file: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
67
152
|
if (!planContent && !clicked) {
|
|
68
153
|
await interaction
|
|
69
|
-
.
|
|
154
|
+
.followUp({ text: 'Plan content could not be extracted from the IDE or workspace.' })
|
|
70
155
|
.catch(() => { });
|
|
71
156
|
return;
|
|
72
157
|
}
|
|
73
158
|
await interaction
|
|
74
|
-
.
|
|
159
|
+
.editReply({
|
|
75
160
|
text: '📋 Plan opened',
|
|
76
161
|
components: [],
|
|
77
162
|
})
|
|
78
163
|
.catch((err) => {
|
|
79
|
-
logger_1.logger.warn('[PlanningAction]
|
|
164
|
+
logger_1.logger.warn('[PlanningAction] editReply failed:', err);
|
|
80
165
|
});
|
|
81
166
|
if (planContent) {
|
|
82
|
-
|
|
167
|
+
let truncated = planContent.length > MAX_PLAN_CONTENT
|
|
83
168
|
? planContent.substring(0, MAX_PLAN_CONTENT - 15) + '\n\n(truncated)'
|
|
84
169
|
: planContent;
|
|
85
170
|
await interaction
|
|
@@ -90,13 +175,16 @@ function createPlanningButtonAction(deps) {
|
|
|
90
175
|
}
|
|
91
176
|
else {
|
|
92
177
|
await interaction
|
|
93
|
-
.followUp({ text: '
|
|
94
|
-
.catch(() => {
|
|
178
|
+
.followUp({ text: 'Plan opened in IDE, but content could not be extracted.' })
|
|
179
|
+
.catch((err) => {
|
|
180
|
+
logger_1.logger.warn('[PlanningAction] followUp failed:', err);
|
|
181
|
+
});
|
|
95
182
|
}
|
|
96
183
|
}
|
|
97
184
|
else if (action === 'reject') {
|
|
185
|
+
await interaction.deferUpdate().catch(() => { });
|
|
98
186
|
// Reject action
|
|
99
|
-
await interaction.
|
|
187
|
+
await interaction.followUp({ text: 'Rejection of the plan is not allowed.', ephemeral: true }).catch(() => { });
|
|
100
188
|
return;
|
|
101
189
|
}
|
|
102
190
|
else {
|
|
@@ -114,17 +202,17 @@ function createPlanningButtonAction(deps) {
|
|
|
114
202
|
}
|
|
115
203
|
if (clicked) {
|
|
116
204
|
await interaction
|
|
117
|
-
.
|
|
118
|
-
text: '▶️ Proceed started',
|
|
205
|
+
.editReply({
|
|
206
|
+
text: '▶️ Proceed started.\n\n⏳ IDE is working on the response...',
|
|
119
207
|
components: [],
|
|
120
208
|
})
|
|
121
209
|
.catch((err) => {
|
|
122
|
-
logger_1.logger.warn('[PlanningAction]
|
|
210
|
+
logger_1.logger.warn('[PlanningAction] editReply failed:', err);
|
|
123
211
|
});
|
|
124
212
|
}
|
|
125
213
|
else {
|
|
126
214
|
await interaction
|
|
127
|
-
.
|
|
215
|
+
.followUp({ text: 'Proceed button not found.', ephemeral: true })
|
|
128
216
|
.catch(() => { });
|
|
129
217
|
}
|
|
130
218
|
}
|