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,16 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.createInteractionCreateHandler = createInteractionCreateHandler;
|
|
4
7
|
const discord_js_1 = require("discord.js");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const genericActionButtonAction_1 = require("../handlers/genericActionButtonAction");
|
|
5
12
|
const i18n_1 = require("../utils/i18n");
|
|
6
13
|
const logger_1 = require("../utils/logger");
|
|
7
14
|
const discordButtonUtils_1 = require("../utils/discordButtonUtils");
|
|
15
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
16
|
+
const fileOpenCache_1 = require("../utils/fileOpenCache");
|
|
8
17
|
const templateUi_1 = require("../ui/templateUi");
|
|
9
18
|
const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
10
19
|
const outputUi_1 = require("../ui/outputUi");
|
|
20
|
+
const notificationSender_1 = require("../services/notificationSender");
|
|
21
|
+
const questionSelectAction_1 = require("../handlers/questionSelectAction");
|
|
22
|
+
const questionSkipAction_1 = require("../handlers/questionSkipAction");
|
|
23
|
+
const wrappers_1 = require("../platform/discord/wrappers");
|
|
11
24
|
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
12
25
|
const projectListUi_1 = require("../ui/projectListUi");
|
|
13
26
|
const modeService_1 = require("../services/modeService");
|
|
27
|
+
const approvalDetector_1 = require("../services/approvalDetector");
|
|
14
28
|
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
15
29
|
const artifactsUi_1 = require("../ui/artifactsUi");
|
|
16
30
|
const artifactService_1 = require("../services/artifactService");
|
|
@@ -37,16 +51,35 @@ function createInteractionCreateHandler(deps) {
|
|
|
37
51
|
accountPrefRepo: deps.accountPrefRepo,
|
|
38
52
|
accounts: deps.antigravityAccounts,
|
|
39
53
|
});
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
const resolveProjectFromChannel = (interaction) => {
|
|
55
|
+
if (!interaction.channelId)
|
|
56
|
+
return null;
|
|
57
|
+
let workspacePath = deps.wsHandler.getWorkspaceForChannel(interaction.channelId);
|
|
58
|
+
if (!workspacePath) {
|
|
59
|
+
const parentId = getParentChannelId(interaction);
|
|
60
|
+
if (parentId) {
|
|
61
|
+
workspacePath = deps.wsHandler.getWorkspaceForChannel(parentId);
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
|
-
return deps.bridge.
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
return workspacePath ? deps.bridge.pool.extractProjectName(workspacePath) : null;
|
|
65
|
+
};
|
|
66
|
+
const getChannelCdp = (interaction) => (() => {
|
|
67
|
+
if (!interaction.channelId)
|
|
68
|
+
return null;
|
|
69
|
+
const projectName = resolveProjectFromChannel(interaction);
|
|
70
|
+
if (projectName) {
|
|
71
|
+
return deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)));
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
49
74
|
})();
|
|
75
|
+
const buildResumeOptions = () => ({
|
|
76
|
+
chatSessionService: deps.chatSessionService,
|
|
77
|
+
chatSessionRepo: deps.chatSessionRepo,
|
|
78
|
+
channelManager: deps.channelManager,
|
|
79
|
+
titleGenerator: deps.titleGenerator,
|
|
80
|
+
userPrefRepo: deps.userPrefRepo,
|
|
81
|
+
artifactService: deps.artifactService,
|
|
82
|
+
});
|
|
50
83
|
const ensureBoundSessionActive = async (channelId, userId, cdp) => {
|
|
51
84
|
const savedTitle = deps.chatSessionRepo?.findByChannelId(channelId)?.displayName?.trim() || '';
|
|
52
85
|
if (!savedTitle || savedTitle === (0, i18n_1.t)('(Untitled)') || !deps.chatSessionService) {
|
|
@@ -114,6 +147,36 @@ function createInteractionCreateHandler(deps) {
|
|
|
114
147
|
return;
|
|
115
148
|
}
|
|
116
149
|
try {
|
|
150
|
+
const genericAction = (0, genericActionButtonAction_1.parseGenericActionCustomId)(interaction.customId);
|
|
151
|
+
if (genericAction) {
|
|
152
|
+
const { actionName: formattedName, projectName: parsedProject, channelId } = genericAction;
|
|
153
|
+
if (channelId && channelId !== interaction.channelId) {
|
|
154
|
+
await interaction.reply({
|
|
155
|
+
content: (0, i18n_1.t)('This action is linked to a different session channel.'),
|
|
156
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
157
|
+
}).catch(logger_1.logger.error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const projectName = parsedProject ?? resolveProjectFromChannel(interaction);
|
|
161
|
+
const cdp = projectName
|
|
162
|
+
? deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(channelId || interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
163
|
+
: await deps.getCurrentCdp(deps.bridge);
|
|
164
|
+
if (!cdp) {
|
|
165
|
+
await interaction.reply({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await interaction.deferUpdate();
|
|
169
|
+
const script = (0, approvalDetector_1.buildClickScript)(formattedName);
|
|
170
|
+
const evalResult = await cdp.call('Runtime.evaluate', {
|
|
171
|
+
expression: script,
|
|
172
|
+
returnByValue: true,
|
|
173
|
+
});
|
|
174
|
+
const resultValue = evalResult.result?.value;
|
|
175
|
+
if (!resultValue?.ok) {
|
|
176
|
+
await interaction.followUp({ content: `Failed to execute action: Button "${formattedName}" not found or obscured.`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
117
180
|
const approvalAction = deps.parseApprovalCustomId(interaction.customId);
|
|
118
181
|
if (approvalAction) {
|
|
119
182
|
if (approvalAction.channelId && approvalAction.channelId !== interaction.channelId) {
|
|
@@ -123,7 +186,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
123
186
|
}).catch(logger_1.logger.error);
|
|
124
187
|
return;
|
|
125
188
|
}
|
|
126
|
-
const projectName = approvalAction.projectName ??
|
|
189
|
+
const projectName = approvalAction.projectName ?? resolveProjectFromChannel(interaction);
|
|
127
190
|
const detector = projectName
|
|
128
191
|
? deps.bridge.pool.getApprovalDetector(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
129
192
|
: undefined;
|
|
@@ -193,7 +256,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
193
256
|
}).catch(logger_1.logger.error);
|
|
194
257
|
return;
|
|
195
258
|
}
|
|
196
|
-
const planWorkspaceDirName = planningAction.projectName ??
|
|
259
|
+
const planWorkspaceDirName = planningAction.projectName ?? resolveProjectFromChannel(interaction);
|
|
197
260
|
const planDetector = planWorkspaceDirName
|
|
198
261
|
? deps.bridge.pool.getPlanningDetector(planWorkspaceDirName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
199
262
|
: undefined;
|
|
@@ -257,6 +320,39 @@ function createInteractionCreateHandler(deps) {
|
|
|
257
320
|
}).catch(logger_1.logger.error);
|
|
258
321
|
}
|
|
259
322
|
}
|
|
323
|
+
else if (planningAction.action === 'reject') {
|
|
324
|
+
const clicked = await planDetector.clickRejectButton();
|
|
325
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
326
|
+
const updatedEmbed = originalEmbed
|
|
327
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
328
|
+
: new discord_js_1.EmbedBuilder().setTitle('Planning Mode');
|
|
329
|
+
const historyText = `Reject by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
330
|
+
updatedEmbed
|
|
331
|
+
.setColor(clicked ? 0xE74C3C : 0x95A5A6)
|
|
332
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
333
|
+
.setTimestamp();
|
|
334
|
+
try {
|
|
335
|
+
await interaction.update({
|
|
336
|
+
embeds: [updatedEmbed],
|
|
337
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (interactionError) {
|
|
341
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
342
|
+
logger_1.logger.warn('[Planning] Interaction expired. Responding directly in the channel.');
|
|
343
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
344
|
+
const fallbackMessage = clicked
|
|
345
|
+
? (0, i18n_1.t)('Reject completed.')
|
|
346
|
+
: (0, i18n_1.t)('Reject button not found.');
|
|
347
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
throw interactionError;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
260
356
|
else {
|
|
261
357
|
// Proceed action
|
|
262
358
|
const clicked = await planDetector.clickProceedButton();
|
|
@@ -289,6 +385,18 @@ function createInteractionCreateHandler(deps) {
|
|
|
289
385
|
throw interactionError;
|
|
290
386
|
}
|
|
291
387
|
}
|
|
388
|
+
if (clicked && deps.promptDispatcher && interaction.channelId && interaction.message) {
|
|
389
|
+
const cdp = getChannelCdp(interaction);
|
|
390
|
+
if (cdp) {
|
|
391
|
+
// Use 'as any' to bypass the type difference between Interaction's Message and Discord.js Message if needed
|
|
392
|
+
deps.promptDispatcher.resume({
|
|
393
|
+
message: interaction.message,
|
|
394
|
+
prompt: '',
|
|
395
|
+
cdp,
|
|
396
|
+
options: buildResumeOptions()
|
|
397
|
+
}).catch(e => logger_1.logger.error('[Planning] Failed to resume monitoring:', e));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
292
400
|
}
|
|
293
401
|
}
|
|
294
402
|
catch (planError) {
|
|
@@ -310,6 +418,81 @@ function createInteractionCreateHandler(deps) {
|
|
|
310
418
|
}
|
|
311
419
|
return;
|
|
312
420
|
}
|
|
421
|
+
// File Change handling
|
|
422
|
+
const fileChangeAction = deps.parseFileChangeCustomId(interaction.customId);
|
|
423
|
+
if (fileChangeAction) {
|
|
424
|
+
if (fileChangeAction.channelId && fileChangeAction.channelId !== interaction.channelId) {
|
|
425
|
+
await interaction.reply({
|
|
426
|
+
content: (0, i18n_1.t)('This file change action is linked to a different session channel.'),
|
|
427
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
428
|
+
}).catch(logger_1.logger.error);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const projectName = fileChangeAction.projectName ?? resolveProjectFromChannel(interaction);
|
|
432
|
+
const cdp = projectName ? deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction))) : null;
|
|
433
|
+
if (!cdp) {
|
|
434
|
+
await interaction.reply({ content: (0, i18n_1.t)('Workspace CDP connection not found.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
let clicked = false;
|
|
438
|
+
try {
|
|
439
|
+
await interaction.deferUpdate();
|
|
440
|
+
const actionLabelEval = fileChangeAction.action === 'accept' ? 'Accept all' : 'Reject all';
|
|
441
|
+
const script = (0, approvalDetector_1.buildClickScript)(actionLabelEval);
|
|
442
|
+
const contextId = cdp.getPrimaryContextId();
|
|
443
|
+
const callParams = {
|
|
444
|
+
expression: script,
|
|
445
|
+
returnByValue: true,
|
|
446
|
+
};
|
|
447
|
+
if (contextId !== null)
|
|
448
|
+
callParams.contextId = contextId;
|
|
449
|
+
const res = await cdp.call('Runtime.evaluate', callParams);
|
|
450
|
+
clicked = res?.result?.value?.ok === true;
|
|
451
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
452
|
+
const updatedEmbed = originalEmbed
|
|
453
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
454
|
+
: new discord_js_1.EmbedBuilder().setTitle('File Changes');
|
|
455
|
+
const actionLabel = fileChangeAction.action === 'accept' ? 'Accept All' : 'Reject All';
|
|
456
|
+
const historyText = `${actionLabel} by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
457
|
+
updatedEmbed
|
|
458
|
+
.setColor(clicked ? (fileChangeAction.action === 'accept' ? 0x2ECC71 : 0xE74C3C) : 0x95A5A6)
|
|
459
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
460
|
+
.setTimestamp();
|
|
461
|
+
await interaction.editReply({
|
|
462
|
+
embeds: [updatedEmbed],
|
|
463
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
464
|
+
});
|
|
465
|
+
if (clicked && deps.promptDispatcher && interaction.channelId && interaction.message) {
|
|
466
|
+
// Resume monitoring, treating the completion of file changes as a need to poll again
|
|
467
|
+
deps.promptDispatcher.resume({
|
|
468
|
+
message: interaction.message,
|
|
469
|
+
prompt: '',
|
|
470
|
+
cdp,
|
|
471
|
+
options: buildResumeOptions()
|
|
472
|
+
}).catch(e => logger_1.logger.error('[FileChange] Failed to resume monitoring:', e));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch (err) {
|
|
476
|
+
if (err?.code === 10062 || err?.code === 40060) {
|
|
477
|
+
logger_1.logger.warn('[FileChange] Interaction expired. Responding directly in the channel.');
|
|
478
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
479
|
+
const actionLabel = fileChangeAction.action === 'accept' ? 'Accept All' : 'Reject All';
|
|
480
|
+
const fallbackMessage = clicked
|
|
481
|
+
? `${actionLabel} completed.`
|
|
482
|
+
: (0, i18n_1.t)('File change button not found or error occurred.');
|
|
483
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
logger_1.logger.error('[FileChange] action error:', err);
|
|
488
|
+
await interaction.followUp({
|
|
489
|
+
content: `❌ Error executing file change action: ${err.message || 'Unknown error'}`,
|
|
490
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
491
|
+
}).catch(() => { });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
313
496
|
const errorPopupAction = deps.parseErrorPopupCustomId(interaction.customId);
|
|
314
497
|
if (errorPopupAction) {
|
|
315
498
|
if (errorPopupAction.channelId && errorPopupAction.channelId !== interaction.channelId) {
|
|
@@ -319,7 +502,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
319
502
|
}).catch(logger_1.logger.error);
|
|
320
503
|
return;
|
|
321
504
|
}
|
|
322
|
-
const errorWorkspaceDirName = errorPopupAction.projectName ??
|
|
505
|
+
const errorWorkspaceDirName = errorPopupAction.projectName ?? resolveProjectFromChannel(interaction);
|
|
323
506
|
const errorDetector = errorWorkspaceDirName
|
|
324
507
|
? deps.bridge.pool.getErrorPopupDetector(errorWorkspaceDirName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
325
508
|
: undefined;
|
|
@@ -469,7 +652,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
469
652
|
}).catch(logger_1.logger.error);
|
|
470
653
|
return;
|
|
471
654
|
}
|
|
472
|
-
const runCmdWorkspace = runCommandAction.projectName ??
|
|
655
|
+
const runCmdWorkspace = runCommandAction.projectName ?? resolveProjectFromChannel(interaction);
|
|
473
656
|
const runCmdDetector = runCmdWorkspace
|
|
474
657
|
? deps.bridge.pool.getRunCommandDetector(runCmdWorkspace, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
475
658
|
: undefined;
|
|
@@ -540,7 +723,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
540
723
|
}
|
|
541
724
|
if (interaction.customId === 'model_set_default_btn') {
|
|
542
725
|
await interaction.deferUpdate();
|
|
543
|
-
const cdp = getChannelCdp(interaction
|
|
726
|
+
const cdp = getChannelCdp(interaction);
|
|
544
727
|
if (!cdp) {
|
|
545
728
|
await interaction.followUp({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
546
729
|
return;
|
|
@@ -560,7 +743,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
560
743
|
deps.userPrefRepo.setDefaultModel(interaction.user.id, currentModel);
|
|
561
744
|
}
|
|
562
745
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
563
|
-
getCurrentCdp: () => getChannelCdp(interaction
|
|
746
|
+
getCurrentCdp: () => getChannelCdp(interaction),
|
|
564
747
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
565
748
|
});
|
|
566
749
|
await interaction.followUp({ content: `Default model set to **${currentModel}**.`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -568,7 +751,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
568
751
|
}
|
|
569
752
|
if (interaction.customId === 'model_clear_default_btn') {
|
|
570
753
|
await interaction.deferUpdate();
|
|
571
|
-
const cdp = getChannelCdp(interaction
|
|
754
|
+
const cdp = getChannelCdp(interaction);
|
|
572
755
|
if (cdp) {
|
|
573
756
|
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
574
757
|
if (!sessionReady.ok) {
|
|
@@ -581,7 +764,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
581
764
|
deps.userPrefRepo.setDefaultModel(interaction.user.id, null);
|
|
582
765
|
}
|
|
583
766
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
584
|
-
getCurrentCdp: () => getChannelCdp(interaction
|
|
767
|
+
getCurrentCdp: () => getChannelCdp(interaction),
|
|
585
768
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
586
769
|
});
|
|
587
770
|
await interaction.followUp({ content: 'Default model cleared.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -589,7 +772,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
589
772
|
}
|
|
590
773
|
if (interaction.customId === 'model_refresh_btn') {
|
|
591
774
|
await interaction.deferUpdate();
|
|
592
|
-
const cdp = getChannelCdp(interaction
|
|
775
|
+
const cdp = getChannelCdp(interaction);
|
|
593
776
|
if (cdp) {
|
|
594
777
|
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
595
778
|
if (!sessionReady.ok) {
|
|
@@ -598,7 +781,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
598
781
|
}
|
|
599
782
|
}
|
|
600
783
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
601
|
-
getCurrentCdp: () => getChannelCdp(interaction
|
|
784
|
+
getCurrentCdp: () => getChannelCdp(interaction),
|
|
602
785
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
603
786
|
});
|
|
604
787
|
return;
|
|
@@ -606,7 +789,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
606
789
|
if (interaction.customId.startsWith('model_btn_')) {
|
|
607
790
|
await interaction.deferUpdate();
|
|
608
791
|
const modelName = interaction.customId.replace('model_btn_', '');
|
|
609
|
-
const cdp = getChannelCdp(interaction
|
|
792
|
+
const cdp = getChannelCdp(interaction);
|
|
610
793
|
if (!cdp) {
|
|
611
794
|
await interaction.followUp({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
612
795
|
return;
|
|
@@ -622,7 +805,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
622
805
|
}
|
|
623
806
|
else {
|
|
624
807
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
625
|
-
getCurrentCdp: () => getChannelCdp(interaction
|
|
808
|
+
getCurrentCdp: () => getChannelCdp(interaction),
|
|
626
809
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
627
810
|
});
|
|
628
811
|
await interaction.followUp({ content: `Model changed to **${res.model}**!`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -688,6 +871,84 @@ function createInteractionCreateHandler(deps) {
|
|
|
688
871
|
}, true, passedConvId);
|
|
689
872
|
return;
|
|
690
873
|
}
|
|
874
|
+
if (interaction.customId.startsWith('file_open:')) {
|
|
875
|
+
await interaction.deferUpdate().catch(() => { });
|
|
876
|
+
const hashId = interaction.customId.replace('file_open:', '');
|
|
877
|
+
const fileUrl = fileOpenCache_1.fileOpenCache.get(hashId);
|
|
878
|
+
if (!fileUrl) {
|
|
879
|
+
await interaction.followUp({
|
|
880
|
+
content: (0, i18n_1.t)('File URL not found in cache or expired.'),
|
|
881
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
882
|
+
}).catch(logger_1.logger.error);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
let resolvedPath = fileUrl;
|
|
886
|
+
if (fileUrl.startsWith('file:///')) {
|
|
887
|
+
let rawPath = fileUrl.replace(/^file:\/\/\//, '');
|
|
888
|
+
if (path_1.default.sep === '/' && !rawPath.startsWith('/')) {
|
|
889
|
+
rawPath = '/' + rawPath;
|
|
890
|
+
}
|
|
891
|
+
else if (process.platform === 'win32' && rawPath.startsWith('/')) {
|
|
892
|
+
rawPath = rawPath.substring(1);
|
|
893
|
+
}
|
|
894
|
+
resolvedPath = path_1.default.resolve(rawPath);
|
|
895
|
+
}
|
|
896
|
+
let workspacePath = undefined;
|
|
897
|
+
if (interaction.channelId) {
|
|
898
|
+
workspacePath = deps.wsHandler.getWorkspaceForChannel(interaction.channelId) ||
|
|
899
|
+
deps.chatSessionRepo?.findByChannelId(interaction.channelId)?.workspacePath;
|
|
900
|
+
}
|
|
901
|
+
if (!workspacePath) {
|
|
902
|
+
await interaction.followUp({
|
|
903
|
+
content: `❌ Error: Cannot open files without an active workspace.`,
|
|
904
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
905
|
+
}).catch(logger_1.logger.error);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const relative = path_1.default.relative(workspacePath, resolvedPath);
|
|
909
|
+
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
|
910
|
+
await interaction.followUp({
|
|
911
|
+
content: `❌ Error: Cannot open files outside the workspace root.`,
|
|
912
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
913
|
+
}).catch(logger_1.logger.error);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
const fileContent = fs_1.default.readFileSync(resolvedPath, 'utf8');
|
|
918
|
+
const MAX_DESC_LEN = 4096;
|
|
919
|
+
const truncated = fileContent.length > MAX_DESC_LEN
|
|
920
|
+
? fileContent.substring(0, MAX_DESC_LEN - 15) + '\n\n(truncated)'
|
|
921
|
+
: fileContent;
|
|
922
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
923
|
+
.setTitle(`Opened: ${path_1.default.basename(resolvedPath)}`)
|
|
924
|
+
.setDescription(truncated)
|
|
925
|
+
.setColor(0x3498DB)
|
|
926
|
+
.setTimestamp();
|
|
927
|
+
(0, child_process_1.execFile)((0, pathUtils_1.getAntigravityCliPath)(), [resolvedPath], async (error) => {
|
|
928
|
+
if (error) {
|
|
929
|
+
logger_1.logger.error(`Failed to open file via CLI: ${error.message}`);
|
|
930
|
+
await interaction.followUp({
|
|
931
|
+
content: `❌ Error opening file via CLI: ${error.message}`,
|
|
932
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
933
|
+
}).catch(() => { });
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
await interaction.followUp({
|
|
937
|
+
content: `✅ Opened file in IDE: **${path_1.default.basename(resolvedPath)}**`,
|
|
938
|
+
embeds: [embed],
|
|
939
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
940
|
+
}).catch(() => { });
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
catch (e) {
|
|
945
|
+
await interaction.followUp({
|
|
946
|
+
content: `❌ Error opening file: ${e.message}`,
|
|
947
|
+
flags: discord_js_1.MessageFlags.Ephemeral
|
|
948
|
+
}).catch(logger_1.logger.error);
|
|
949
|
+
}
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
691
952
|
}
|
|
692
953
|
catch (error) {
|
|
693
954
|
logger_1.logger.error('Error during button interaction handling:', error);
|
|
@@ -723,7 +984,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
723
984
|
try {
|
|
724
985
|
const selectedMode = interaction.values[0];
|
|
725
986
|
deps.modeService.setMode(selectedMode);
|
|
726
|
-
const cdp = getChannelCdp(interaction
|
|
987
|
+
const cdp = getChannelCdp(interaction);
|
|
727
988
|
if (cdp) {
|
|
728
989
|
const res = await cdp.setUiMode(selectedMode);
|
|
729
990
|
if (!res.ok) {
|
|
@@ -739,6 +1000,9 @@ function createInteractionCreateHandler(deps) {
|
|
|
739
1000
|
if (interaction.deferred || interaction.replied) {
|
|
740
1001
|
await interaction.followUp({ content: 'An error occurred while changing the mode.', flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
741
1002
|
}
|
|
1003
|
+
else {
|
|
1004
|
+
await interaction.reply({ content: 'An error occurred while changing the mode.', flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
1005
|
+
}
|
|
742
1006
|
}
|
|
743
1007
|
catch (e) {
|
|
744
1008
|
logger_1.logger.error('Failed to send error message:', e);
|
|
@@ -866,10 +1130,10 @@ function createInteractionCreateHandler(deps) {
|
|
|
866
1130
|
const artifactService = new artifactService_1.ArtifactService();
|
|
867
1131
|
// Resolve the selected artifact by rescanning and matching the encoded value
|
|
868
1132
|
const channelId = interaction.channelId;
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1133
|
+
const session = deps.chatSessionRepo?.findByChannelId(channelId);
|
|
1134
|
+
const sessionTitle = session?.displayName?.trim() ?? '';
|
|
1135
|
+
const workspaceDirName = (0, pathUtils_1.getWorkspaceDirName)(session);
|
|
1136
|
+
const conversationId = session?.conversationId || (sessionTitle ? artifactService.findConversationByTitle(sessionTitle, workspaceDirName) : null);
|
|
873
1137
|
if (!conversationId) {
|
|
874
1138
|
await interaction.editReply({ content: '📂 No artifacts found.', components: [], allowedMentions: { parse: [] } }).catch(logger_1.logger.error);
|
|
875
1139
|
return;
|
|
@@ -975,6 +1239,40 @@ function createInteractionCreateHandler(deps) {
|
|
|
975
1239
|
}
|
|
976
1240
|
return;
|
|
977
1241
|
}
|
|
1242
|
+
if (interaction.isStringSelectMenu() && interaction.customId.startsWith(notificationSender_1.QUESTION_SELECT_ACTION_PREFIX)) {
|
|
1243
|
+
try {
|
|
1244
|
+
const action = (0, questionSelectAction_1.createQuestionSelectAction)({ bridge: deps.bridge, wsHandler: deps.wsHandler });
|
|
1245
|
+
const wrapped = (0, wrappers_1.wrapDiscordSelect)(interaction);
|
|
1246
|
+
await action.execute(wrapped, interaction.values);
|
|
1247
|
+
}
|
|
1248
|
+
catch (err) {
|
|
1249
|
+
logger_1.logger.error('[QuestionSelectAction] Error executing action:', err);
|
|
1250
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
1251
|
+
await interaction.reply({ content: 'An error occurred.', ephemeral: true }).catch(logger_1.logger.error);
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
await interaction.followUp({ content: 'An error occurred.', ephemeral: true }).catch(logger_1.logger.error);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (interaction.isButton() && interaction.customId.startsWith(notificationSender_1.QUESTION_SKIP_ACTION_PREFIX)) {
|
|
1260
|
+
try {
|
|
1261
|
+
const action = (0, questionSkipAction_1.createQuestionSkipAction)({ bridge: deps.bridge, wsHandler: deps.wsHandler });
|
|
1262
|
+
const wrapped = (0, wrappers_1.wrapDiscordButton)(interaction);
|
|
1263
|
+
await action.execute(wrapped, {});
|
|
1264
|
+
}
|
|
1265
|
+
catch (err) {
|
|
1266
|
+
logger_1.logger.error('[QuestionSkipAction] Error executing action:', err);
|
|
1267
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
1268
|
+
await interaction.reply({ content: 'An error occurred.', ephemeral: true }).catch(logger_1.logger.error);
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
await interaction.followUp({ content: 'An error occurred.', ephemeral: true }).catch(logger_1.logger.error);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
978
1276
|
if (interaction.isStringSelectMenu() && (0, projectListUi_1.isProjectSelectId)(interaction.customId)) {
|
|
979
1277
|
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
980
1278
|
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
@@ -1019,7 +1317,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
1019
1317
|
throw deferError;
|
|
1020
1318
|
}
|
|
1021
1319
|
try {
|
|
1022
|
-
await deps.handleSlashInteraction(commandInteraction, deps.slashCommandHandler, deps.bridge, deps.wsHandler, deps.chatHandler, deps.cleanupHandler, deps.modeService, deps.modelService, deps.bridge.autoAccept, deps.client, deps.accountPrefRepo, deps.channelPrefRepo, deps.antigravityAccounts, deps.chatSessionRepo);
|
|
1320
|
+
await deps.handleSlashInteraction(commandInteraction, deps.slashCommandHandler, deps.bridge, deps.wsHandler, deps.chatHandler, deps.cleanupHandler, deps.modeService, deps.modelService, deps.bridge.autoAccept, deps.client, deps.accountPrefRepo, deps.channelPrefRepo, deps.antigravityAccounts, deps.chatSessionRepo, deps.scheduleService);
|
|
1023
1321
|
}
|
|
1024
1322
|
catch (error) {
|
|
1025
1323
|
logger_1.logger.error(`[SlashCommand] command=${commandInteraction.commandName} channel=${commandInteraction.channelId} ` +
|
|
@@ -16,6 +16,7 @@ function createMessageCreateHandler(deps) {
|
|
|
16
16
|
const ensureErrorPopupDetector = deps.ensureErrorPopupDetector ?? cdpBridgeManager_1.ensureErrorPopupDetector;
|
|
17
17
|
const ensurePlanningDetector = deps.ensurePlanningDetector ?? cdpBridgeManager_1.ensurePlanningDetector;
|
|
18
18
|
const ensureRunCommandDetector = deps.ensureRunCommandDetector ?? cdpBridgeManager_1.ensureRunCommandDetector;
|
|
19
|
+
const ensureQuestionDetector = deps.ensureQuestionDetector ?? cdpBridgeManager_1.ensureQuestionDetector;
|
|
19
20
|
const registerApprovalWorkspaceChannel = deps.registerApprovalWorkspaceChannel ?? cdpBridgeManager_1.registerApprovalWorkspaceChannel;
|
|
20
21
|
const registerApprovalSessionChannel = deps.registerApprovalSessionChannel ?? cdpBridgeManager_1.registerApprovalSessionChannel;
|
|
21
22
|
const downloadInboundImageAttachments = deps.downloadInboundImageAttachments ?? imageHandler_1.downloadInboundImageAttachments;
|
|
@@ -190,10 +191,57 @@ function createMessageCreateHandler(deps) {
|
|
|
190
191
|
}
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
const allAttachments = Array.from(message.attachments.values());
|
|
195
|
+
const hasImageAttachments = allAttachments.some((attachment) => isImageAttachment(attachment.contentType, attachment.name));
|
|
196
|
+
const MAX_TEXT_ATTACHMENT_SIZE = 50 * 1024; // 50KB
|
|
197
|
+
const textAttachments = allAttachments.filter((a) => {
|
|
198
|
+
if (isImageAttachment(a.contentType, a.name))
|
|
199
|
+
return false;
|
|
200
|
+
if (a.size > MAX_TEXT_ATTACHMENT_SIZE)
|
|
201
|
+
return false;
|
|
202
|
+
const isTextType = a.contentType?.startsWith('text/') || a.contentType?.startsWith('application/json') || a.contentType?.startsWith('application/javascript');
|
|
203
|
+
const hasTextExt = a.name?.match(/\.(txt|md|js|ts|py|json|html|css|csv|log|sh|yml|yaml|xml)$/i);
|
|
204
|
+
return isTextType || hasTextExt;
|
|
205
|
+
});
|
|
206
|
+
if (message.content.trim() || hasImageAttachments || textAttachments.length > 0) {
|
|
207
|
+
let promptText = message.content.trim() || 'Please review the attached content and respond accordingly.';
|
|
208
|
+
// Prepend reply context if replying to a message
|
|
209
|
+
if (message.reference && message.reference.messageId) {
|
|
210
|
+
try {
|
|
211
|
+
const repliedTo = await message.channel.messages.fetch(message.reference.messageId);
|
|
212
|
+
if (repliedTo) {
|
|
213
|
+
const cleanContent = (repliedTo.content || '(Attachment/Embed)').trim();
|
|
214
|
+
const truncated = cleanContent.length > 500 ? cleanContent.substring(0, 500) + '...' : cleanContent;
|
|
215
|
+
promptText = `[Replying to context: "${truncated}"]\n\n${promptText}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
logger_1.logger.warn('[MessageCreate] Failed to fetch replied-to message context:', e);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Fetch and append text attachments in parallel with a small concurrency cap
|
|
223
|
+
const CONCURRENCY_LIMIT = 3;
|
|
224
|
+
for (let i = 0; i < textAttachments.length; i += CONCURRENCY_LIMIT) {
|
|
225
|
+
const chunk = textAttachments.slice(i, i + CONCURRENCY_LIMIT);
|
|
226
|
+
const results = await Promise.all(chunk.map(async (textAtt) => {
|
|
227
|
+
try {
|
|
228
|
+
const res = await fetch(textAtt.url);
|
|
229
|
+
if (res.ok) {
|
|
230
|
+
const content = await res.text();
|
|
231
|
+
return `\n\n[Attached File: ${textAtt.name}]\n\`\`\`\n${content}\n\`\`\``;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
logger_1.logger.warn(`[MessageCreate] Non-ok status fetching text attachment ${textAtt.name}: ${res.status}`);
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
logger_1.logger.warn(`[MessageCreate] Failed to fetch text attachment ${textAtt.name}:`, e);
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
}));
|
|
243
|
+
promptText += results.join('');
|
|
244
|
+
}
|
|
197
245
|
const inboundImages = await downloadInboundImageAttachments(message);
|
|
198
246
|
if (hasImageAttachments && inboundImages.length === 0) {
|
|
199
247
|
await message.reply('Failed to retrieve attached images. Please wait and try again.').catch(() => { });
|
|
@@ -253,6 +301,7 @@ function createMessageCreateHandler(deps) {
|
|
|
253
301
|
ensureErrorPopupDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
254
302
|
ensurePlanningDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
255
303
|
ensureRunCommandDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
304
|
+
ensureQuestionDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
256
305
|
let session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
257
306
|
const staleSessionAccount = session?.isRenamed
|
|
258
307
|
&& ((session.activeAccountName && session.activeAccountName !== selectedAccount)
|