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
package/dist/bot/index.js
CHANGED
|
@@ -46,6 +46,9 @@ const logger_1 = require("../utils/logger");
|
|
|
46
46
|
const logBuffer_1 = require("../utils/logBuffer");
|
|
47
47
|
const discord_js_1 = require("discord.js");
|
|
48
48
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
49
|
+
const fs_1 = __importDefault(require("fs"));
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const path_1 = __importDefault(require("path"));
|
|
49
52
|
const wrappers_1 = require("../platform/discord/wrappers");
|
|
50
53
|
const config_1 = require("../utils/config");
|
|
51
54
|
const slashCommandHandler_1 = require("../commands/slashCommandHandler");
|
|
@@ -72,10 +75,12 @@ const chatSessionService_1 = require("../services/chatSessionService");
|
|
|
72
75
|
const responseMonitor_1 = require("../services/responseMonitor");
|
|
73
76
|
const antigravityLauncher_1 = require("../services/antigravityLauncher");
|
|
74
77
|
const pathUtils_1 = require("../utils/pathUtils");
|
|
78
|
+
const fileOpenCache_1 = require("../utils/fileOpenCache");
|
|
75
79
|
const promptDispatcher_1 = require("../services/promptDispatcher");
|
|
76
80
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
77
81
|
const streamMessageFormatter_1 = require("../utils/streamMessageFormatter");
|
|
78
82
|
const discordFormatter_1 = require("../utils/discordFormatter");
|
|
83
|
+
const discordResponseRenderer_1 = require("../platform/discord/discordResponseRenderer");
|
|
79
84
|
const processLogBuffer_1 = require("../utils/processLogBuffer");
|
|
80
85
|
const imageHandler_1 = require("../utils/imageHandler");
|
|
81
86
|
const modeUi_1 = require("../ui/modeUi");
|
|
@@ -106,8 +111,11 @@ const planningButtonAction_1 = require("../handlers/planningButtonAction");
|
|
|
106
111
|
const errorPopupButtonAction_1 = require("../handlers/errorPopupButtonAction");
|
|
107
112
|
const runCommandButtonAction_1 = require("../handlers/runCommandButtonAction");
|
|
108
113
|
const modelButtonAction_1 = require("../handlers/modelButtonAction");
|
|
109
|
-
const
|
|
114
|
+
const questionSelectAction_1 = require("../handlers/questionSelectAction");
|
|
110
115
|
const templateButtonAction_1 = require("../handlers/templateButtonAction");
|
|
116
|
+
const fileChangeButtonAction_1 = require("../handlers/fileChangeButtonAction");
|
|
117
|
+
const autoAcceptButtonAction_1 = require("../handlers/autoAcceptButtonAction");
|
|
118
|
+
const genericActionButtonAction_1 = require("../handlers/genericActionButtonAction");
|
|
111
119
|
const modeSelectAction_1 = require("../handlers/modeSelectAction");
|
|
112
120
|
const accountSelectAction_1 = require("../handlers/accountSelectAction");
|
|
113
121
|
const telegramStartupTarget_1 = require("./telegramStartupTarget");
|
|
@@ -172,8 +180,9 @@ function createSerialTaskQueueForTest(queueName, traceId) {
|
|
|
172
180
|
return queue;
|
|
173
181
|
};
|
|
174
182
|
}
|
|
183
|
+
// Shared fileOpenCache is imported from utils/fileOpenCache
|
|
175
184
|
/**
|
|
176
|
-
* Send a
|
|
185
|
+
* Send a user's prompt to Antigravity and monitor generation.
|
|
177
186
|
*
|
|
178
187
|
* Message strategy:
|
|
179
188
|
* - Send new messages per phase instead of editing, to preserve history
|
|
@@ -408,16 +417,20 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
408
417
|
return;
|
|
409
418
|
lastLiveResponseKey = renderKey;
|
|
410
419
|
for (let i = 0; i < plainChunks.length; i++) {
|
|
420
|
+
const messageOpts = { content: plainChunks[i] };
|
|
421
|
+
if (i === plainChunks.length - 1 && opts?.components?.length) {
|
|
422
|
+
messageOpts.components = opts.components;
|
|
423
|
+
}
|
|
411
424
|
if (!liveResponseMessages[i]) {
|
|
412
|
-
liveResponseMessages[i] = await channel.send(
|
|
425
|
+
liveResponseMessages[i] = await channel.send(messageOpts).catch((error) => {
|
|
413
426
|
logDeliveryError('liveResponse/plain/send', error);
|
|
414
427
|
return null;
|
|
415
428
|
});
|
|
416
429
|
continue;
|
|
417
430
|
}
|
|
418
|
-
await liveResponseMessages[i].edit(
|
|
431
|
+
await liveResponseMessages[i].edit(messageOpts).catch(async (error) => {
|
|
419
432
|
logDeliveryError('liveResponse/plain/edit', error);
|
|
420
|
-
liveResponseMessages[i] = await channel.send(
|
|
433
|
+
liveResponseMessages[i] = await channel.send(messageOpts).catch((sendError) => {
|
|
421
434
|
logDeliveryError('liveResponse/plain/resend', sendError);
|
|
422
435
|
return null;
|
|
423
436
|
});
|
|
@@ -445,15 +458,23 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
445
458
|
.setFooter({ text: footerText })
|
|
446
459
|
.setTimestamp();
|
|
447
460
|
if (!liveResponseMessages[i]) {
|
|
448
|
-
|
|
461
|
+
const messageOpts = { embeds: [embed] };
|
|
462
|
+
if (i === descriptions.length - 1 && opts?.components?.length) {
|
|
463
|
+
messageOpts.components = opts.components;
|
|
464
|
+
}
|
|
465
|
+
liveResponseMessages[i] = await channel.send(messageOpts).catch((error) => {
|
|
449
466
|
logDeliveryError('liveResponse/embed/send', error);
|
|
450
467
|
return null;
|
|
451
468
|
});
|
|
452
469
|
continue;
|
|
453
470
|
}
|
|
454
|
-
|
|
471
|
+
const messageOpts = { embeds: [embed] };
|
|
472
|
+
if (i === descriptions.length - 1 && opts?.components?.length) {
|
|
473
|
+
messageOpts.components = opts.components;
|
|
474
|
+
}
|
|
475
|
+
await liveResponseMessages[i].edit(messageOpts).catch(async (error) => {
|
|
455
476
|
logDeliveryError('liveResponse/embed/edit', error);
|
|
456
|
-
liveResponseMessages[i] = await channel.send(
|
|
477
|
+
liveResponseMessages[i] = await channel.send(messageOpts).catch((sendError) => {
|
|
457
478
|
logDeliveryError('liveResponse/embed/resend', sendError);
|
|
458
479
|
return null;
|
|
459
480
|
});
|
|
@@ -544,16 +565,18 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
544
565
|
try {
|
|
545
566
|
const baseline = await (0, responseMonitor_1.captureResponseMonitorBaseline)(cdp);
|
|
546
567
|
logger_1.logger.prompt(prompt);
|
|
547
|
-
let injectResult;
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
568
|
+
let injectResult = { ok: true };
|
|
569
|
+
if (!options?.resumeOnly) {
|
|
570
|
+
if (inboundImages.length > 0) {
|
|
571
|
+
injectResult = await cdp.injectMessageWithImageFiles(prompt, inboundImages.map((image) => image.localPath));
|
|
572
|
+
if (!injectResult.ok) {
|
|
573
|
+
await sendEmbed((0, i18n_1.t)('🖼️ Attached image fallback'), (0, i18n_1.t)('Failed to attach image directly, resending via URL reference.'), PHASE_COLORS.thinking);
|
|
574
|
+
injectResult = await cdp.injectMessage((0, imageHandler_1.buildPromptWithAttachmentUrls)(prompt, inboundImages));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
injectResult = await cdp.injectMessage(prompt);
|
|
553
579
|
}
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
injectResult = await cdp.injectMessage(prompt);
|
|
557
580
|
}
|
|
558
581
|
if (!injectResult.ok) {
|
|
559
582
|
isFinalized = true;
|
|
@@ -600,7 +623,7 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
600
623
|
lastProgressText = separated.output;
|
|
601
624
|
}
|
|
602
625
|
},
|
|
603
|
-
onComplete: async (finalText) => {
|
|
626
|
+
onComplete: async (finalText, citedFiles, fileChanges) => {
|
|
604
627
|
isFinalized = true;
|
|
605
628
|
try {
|
|
606
629
|
// If the user explicitly pressed /stop, skip output display entirely
|
|
@@ -680,18 +703,9 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
680
703
|
});
|
|
681
704
|
liveResponseUpdateVersion += 1;
|
|
682
705
|
const responseVersion = liveResponseUpdateVersion;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
expectedVersion: responseVersion,
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use `/screenshot` to verify.'), PHASE_COLORS.complete, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`), {
|
|
691
|
-
source: 'complete',
|
|
692
|
-
expectedVersion: responseVersion,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
706
|
+
const components = [];
|
|
707
|
+
if (!citedFiles)
|
|
708
|
+
citedFiles = [];
|
|
695
709
|
if (options && message.guild) {
|
|
696
710
|
try {
|
|
697
711
|
const sessionInfo = await options.chatSessionService.getCurrentSessionInfo(cdp);
|
|
@@ -711,7 +725,12 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
711
725
|
}
|
|
712
726
|
// Persist conversation_id for artifact picker resolution
|
|
713
727
|
if (options.artifactService) {
|
|
714
|
-
const
|
|
728
|
+
const session = options.chatSessionRepo.findByChannelId(message.channelId);
|
|
729
|
+
let workspaceDirName;
|
|
730
|
+
if (session && session.workspacePath) {
|
|
731
|
+
workspaceDirName = bridge.pool.extractProjectName(session.workspacePath);
|
|
732
|
+
}
|
|
733
|
+
const convId = options.artifactService.findConversationByTitle(sessionInfo.title, workspaceDirName);
|
|
715
734
|
if (convId) {
|
|
716
735
|
options.chatSessionRepo.setConversationId(message.channelId, convId);
|
|
717
736
|
}
|
|
@@ -722,6 +741,112 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
722
741
|
logger_1.logger.error('[Rename] Failed to get title from Antigravity and rename:', e);
|
|
723
742
|
}
|
|
724
743
|
}
|
|
744
|
+
if (citedFiles && citedFiles.length > 0) {
|
|
745
|
+
// Strip trailing punctuation and deduplicate
|
|
746
|
+
let uniqueFiles = Array.from(new Set(citedFiles.map(f => {
|
|
747
|
+
let clean = f.trim();
|
|
748
|
+
while (clean.match(/[.,;:!?]$/)) {
|
|
749
|
+
clean = clean.slice(0, -1);
|
|
750
|
+
}
|
|
751
|
+
return clean;
|
|
752
|
+
})));
|
|
753
|
+
// Filter out obviously bad paths
|
|
754
|
+
uniqueFiles = uniqueFiles.filter(f => f.length > 0 && f !== 'unknown');
|
|
755
|
+
const row = new discord_js_1.ActionRowBuilder();
|
|
756
|
+
// Max 5 buttons per row in Discord
|
|
757
|
+
for (let i = 0; i < Math.min(uniqueFiles.length, 5); i++) {
|
|
758
|
+
let fileUrl = uniqueFiles[i];
|
|
759
|
+
const wsPath = cdp.getCurrentWorkspacePath();
|
|
760
|
+
if (!wsPath) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (!fileUrl.startsWith('file:///') && !path_1.default.isAbsolute(fileUrl)) {
|
|
764
|
+
fileUrl = `file:///${path_1.default.resolve(wsPath, fileUrl).replace(/\\/g, '/')}`;
|
|
765
|
+
}
|
|
766
|
+
let fsPath = fileUrl.replace(/^file:\/\/\//, '');
|
|
767
|
+
if (path_1.default.sep === '/' && !fsPath.startsWith('/')) {
|
|
768
|
+
fsPath = '/' + fsPath;
|
|
769
|
+
}
|
|
770
|
+
else if (process.platform === 'win32' && fsPath.startsWith('/')) {
|
|
771
|
+
fsPath = fsPath.substring(1);
|
|
772
|
+
}
|
|
773
|
+
const relative = path_1.default.relative(wsPath, fsPath);
|
|
774
|
+
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (!fs_1.default.existsSync(fsPath)) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
let displayName = fileUrl;
|
|
781
|
+
if (displayName.startsWith('file:///')) {
|
|
782
|
+
const parts = displayName.split('/');
|
|
783
|
+
displayName = parts[parts.length - 1];
|
|
784
|
+
}
|
|
785
|
+
const hashId = Math.random().toString(36).substring(2, 10);
|
|
786
|
+
fileOpenCache_1.fileOpenCache.set(hashId, fileUrl);
|
|
787
|
+
row.addComponents(new discord_js_1.ButtonBuilder()
|
|
788
|
+
.setCustomId(`file_open:${hashId}`)
|
|
789
|
+
.setLabel(`Open ${displayName.substring(0, 20)}`)
|
|
790
|
+
.setStyle(discord_js_1.ButtonStyle.Primary));
|
|
791
|
+
}
|
|
792
|
+
if (row.components.length > 0) {
|
|
793
|
+
components.push(row);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (fileChanges && fileChanges.length > 0) {
|
|
797
|
+
// The extracted text is the toolbar title, e.g. "1 File With Changes"
|
|
798
|
+
const widgetTitle = fileChanges[fileChanges.length - 1] || 'Files With Changes';
|
|
799
|
+
const fileChangesRow = new discord_js_1.ActionRowBuilder().addComponents(new discord_js_1.ButtonBuilder()
|
|
800
|
+
.setCustomId((0, cdpBridgeManager_1.buildFileChangeCustomId)('reject', cdp.getCurrentWorkspaceName() || 'unknown', message.channelId))
|
|
801
|
+
.setLabel(`Reject all (${widgetTitle})`)
|
|
802
|
+
.setStyle(discord_js_1.ButtonStyle.Danger), new discord_js_1.ButtonBuilder()
|
|
803
|
+
.setCustomId((0, cdpBridgeManager_1.buildFileChangeCustomId)('accept', cdp.getCurrentWorkspaceName() || 'unknown', message.channelId))
|
|
804
|
+
.setLabel('Accept all')
|
|
805
|
+
.setStyle(discord_js_1.ButtonStyle.Success));
|
|
806
|
+
components.push(fileChangesRow);
|
|
807
|
+
}
|
|
808
|
+
const classified = monitor.getLastClassified();
|
|
809
|
+
if (options?.extractionMode === 'structured' && classified && outputFormat !== 'plain') {
|
|
810
|
+
const messageOptions = (0, discordResponseRenderer_1.renderDiscordResponse)(classified, {
|
|
811
|
+
projectName: cdp.getCurrentWorkspaceName() || 'unknown',
|
|
812
|
+
channelId: message.channelId
|
|
813
|
+
});
|
|
814
|
+
for (const msg of liveResponseMessages) {
|
|
815
|
+
if (msg)
|
|
816
|
+
await msg.delete().catch(() => { });
|
|
817
|
+
}
|
|
818
|
+
liveResponseMessages.length = 0;
|
|
819
|
+
if (components.length > 0) {
|
|
820
|
+
if (!messageOptions.components)
|
|
821
|
+
messageOptions.components = [];
|
|
822
|
+
messageOptions.components = [...(messageOptions.components || []), ...components];
|
|
823
|
+
}
|
|
824
|
+
if (messageOptions.embeds && messageOptions.embeds.length > 0) {
|
|
825
|
+
const lastEmbed = messageOptions.embeds[messageOptions.embeds.length - 1];
|
|
826
|
+
if (lastEmbed && typeof lastEmbed.setFooter === 'function') {
|
|
827
|
+
lastEmbed.setFooter({ text: (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`) });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (channel) {
|
|
831
|
+
await channel.send(messageOptions).catch((error) => {
|
|
832
|
+
logDeliveryError('renderDiscordResponse/send', error);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
else if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
837
|
+
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Final Output`, finalOutputText, PHASE_COLORS.complete, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`), {
|
|
838
|
+
source: 'complete',
|
|
839
|
+
expectedVersion: responseVersion,
|
|
840
|
+
components: components.length > 0 ? components : undefined,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use `/screenshot` to verify.'), PHASE_COLORS.complete, (0, i18n_1.t)(`⏱️ Time: ${elapsed}s | Complete`), {
|
|
845
|
+
source: 'complete',
|
|
846
|
+
expectedVersion: responseVersion,
|
|
847
|
+
components: components.length > 0 ? components : undefined,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
725
850
|
await sendGeneratedImages(finalOutputText || '');
|
|
726
851
|
await clearWatchingReaction();
|
|
727
852
|
await message.react(finalOutputText && finalOutputText.trim().length > 0 ? '✅' : '⚠️').catch(() => { });
|
|
@@ -977,6 +1102,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
977
1102
|
wsHandler,
|
|
978
1103
|
chatHandler,
|
|
979
1104
|
client,
|
|
1105
|
+
promptDispatcher,
|
|
980
1106
|
sendModeUI: modeUi_1.sendModeUI,
|
|
981
1107
|
sendModelsUI: modelsUi_1.sendModelsUI,
|
|
982
1108
|
sendAutoAcceptUI: autoAcceptUi_1.sendAutoAcceptUI,
|
|
@@ -984,6 +1110,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
984
1110
|
parseApprovalCustomId: cdpBridgeManager_1.parseApprovalCustomId,
|
|
985
1111
|
parseErrorPopupCustomId: cdpBridgeManager_1.parseErrorPopupCustomId,
|
|
986
1112
|
parsePlanningCustomId: cdpBridgeManager_1.parsePlanningCustomId,
|
|
1113
|
+
parseFileChangeCustomId: cdpBridgeManager_1.parseFileChangeCustomId,
|
|
987
1114
|
parseRunCommandCustomId: cdpBridgeManager_1.parseRunCommandCustomId,
|
|
988
1115
|
joinHandler,
|
|
989
1116
|
userPrefRepo,
|
|
@@ -993,8 +1120,10 @@ const startBot = async (cliLogLevel) => {
|
|
|
993
1120
|
chatSessionService,
|
|
994
1121
|
artifactThreadRepo,
|
|
995
1122
|
artifactService,
|
|
1123
|
+
channelManager,
|
|
1124
|
+
titleGenerator,
|
|
996
1125
|
antigravityAccounts: config.antigravityAccounts,
|
|
997
|
-
handleSlashInteraction: async (interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg, accountPrefRepoArg, channelPrefRepoArg, antigravityAccountsArg) => handleSlashInteraction(interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, chatSessionService, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg, promptDispatcher, templateRepo, joinHandler, userPrefRepo, accountPrefRepoArg, channelPrefRepoArg, antigravityAccountsArg,
|
|
1126
|
+
handleSlashInteraction: async (interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg, accountPrefRepoArg, channelPrefRepoArg, antigravityAccountsArg, chatSessionRepoArg, scheduleServiceArg) => handleSlashInteraction(interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, chatSessionService, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg, promptDispatcher, templateRepo, joinHandler, userPrefRepo, accountPrefRepoArg, channelPrefRepoArg, antigravityAccountsArg, chatSessionRepoArg, artifactService, scheduleServiceArg),
|
|
998
1127
|
handleTemplateUse: async (interaction, templateId) => {
|
|
999
1128
|
const template = templateRepo.findById(templateId);
|
|
1000
1129
|
if (!template) {
|
|
@@ -1035,6 +1164,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
1035
1164
|
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(bridge, cdp, projectName, selectedAccount);
|
|
1036
1165
|
(0, cdpBridgeManager_1.ensurePlanningDetector)(bridge, cdp, projectName, selectedAccount);
|
|
1037
1166
|
(0, cdpBridgeManager_1.ensureRunCommandDetector)(bridge, cdp, projectName, selectedAccount);
|
|
1167
|
+
(0, cdpBridgeManager_1.ensureQuestionDetector)(bridge, cdp, projectName, selectedAccount);
|
|
1038
1168
|
}
|
|
1039
1169
|
catch (e) {
|
|
1040
1170
|
await interaction.followUp({
|
|
@@ -1184,10 +1314,12 @@ const startBot = async (cliLogLevel) => {
|
|
|
1184
1314
|
: binding.workspacePath;
|
|
1185
1315
|
},
|
|
1186
1316
|
});
|
|
1317
|
+
const questionSelectAction = (0, questionSelectAction_1.createQuestionSelectAction)({ bridge, wsHandler });
|
|
1187
1318
|
const telegramSelectHandler = (0, selectHandler_1.createPlatformSelectHandler)({
|
|
1188
1319
|
actions: [
|
|
1189
1320
|
modeSelectAction,
|
|
1190
1321
|
accountSelectAction,
|
|
1322
|
+
questionSelectAction,
|
|
1191
1323
|
],
|
|
1192
1324
|
});
|
|
1193
1325
|
// Composite handler that routes to the right handler
|
|
@@ -1202,10 +1334,13 @@ const startBot = async (cliLogLevel) => {
|
|
|
1202
1334
|
accountPrefRepo,
|
|
1203
1335
|
channelPrefRepo,
|
|
1204
1336
|
antigravityAccounts: config.antigravityAccounts,
|
|
1337
|
+
activeMonitors,
|
|
1205
1338
|
}, interaction);
|
|
1206
1339
|
return;
|
|
1207
1340
|
}
|
|
1208
|
-
if (interaction.customId === 'mode_select' ||
|
|
1341
|
+
if (interaction.customId === 'mode_select' ||
|
|
1342
|
+
interaction.customId === 'account_select' ||
|
|
1343
|
+
interaction.customId.startsWith('question_select_action')) {
|
|
1209
1344
|
await telegramSelectHandler(interaction);
|
|
1210
1345
|
return;
|
|
1211
1346
|
}
|
|
@@ -1220,10 +1355,10 @@ const startBot = async (cliLogLevel) => {
|
|
|
1220
1355
|
}
|
|
1221
1356
|
const telegramButtonHandler = (0, buttonHandler_1.createPlatformButtonHandler)({
|
|
1222
1357
|
actions: [
|
|
1223
|
-
(0, approvalButtonAction_1.createApprovalButtonAction)({ bridge }),
|
|
1224
|
-
(0, planningButtonAction_1.createPlanningButtonAction)({ bridge }),
|
|
1225
|
-
(0, errorPopupButtonAction_1.createErrorPopupButtonAction)({ bridge }),
|
|
1226
|
-
(0, runCommandButtonAction_1.createRunCommandButtonAction)({ bridge }),
|
|
1358
|
+
(0, approvalButtonAction_1.createApprovalButtonAction)({ bridge, wsHandler }),
|
|
1359
|
+
(0, planningButtonAction_1.createPlanningButtonAction)({ bridge, wsHandler }),
|
|
1360
|
+
(0, errorPopupButtonAction_1.createErrorPopupButtonAction)({ bridge, wsHandler }),
|
|
1361
|
+
(0, runCommandButtonAction_1.createRunCommandButtonAction)({ bridge, wsHandler }),
|
|
1227
1362
|
(0, modelButtonAction_1.createModelButtonAction)({
|
|
1228
1363
|
bridge,
|
|
1229
1364
|
fetchQuota: () => bridge.quota.fetchQuota(),
|
|
@@ -1261,6 +1396,8 @@ const startBot = async (cliLogLevel) => {
|
|
|
1261
1396
|
}),
|
|
1262
1397
|
(0, autoAcceptButtonAction_1.createAutoAcceptButtonAction)({ autoAcceptService: bridge.autoAccept }),
|
|
1263
1398
|
(0, templateButtonAction_1.createTemplateButtonAction)({ bridge, templateRepo }),
|
|
1399
|
+
(0, fileChangeButtonAction_1.createFileChangeButtonAction)({ bridge, wsHandler }),
|
|
1400
|
+
(0, genericActionButtonAction_1.createGenericActionButtonAction)({ bridge, wsHandler }),
|
|
1264
1401
|
],
|
|
1265
1402
|
});
|
|
1266
1403
|
const eventRouter = new eventRouter_1.EventRouter({ allowedUsers }, {
|
|
@@ -1381,7 +1518,7 @@ async function autoRenameChannel(message, chatSessionRepo, titleGenerator, chann
|
|
|
1381
1518
|
/**
|
|
1382
1519
|
* Handle Discord Interactions API slash commands
|
|
1383
1520
|
*/
|
|
1384
|
-
async function handleSlashInteraction(interaction, handler, bridge, wsHandler, chatHandler, cleanupHandler, chatSessionService, modeService, modelService, autoAcceptService, _client, promptDispatcher, templateRepo, joinHandler, userPrefRepo, accountPrefRepo, channelPrefRepo, antigravityAccounts = [{ name: 'default', cdpPort: 9222 }], chatSessionRepo, artifactService) {
|
|
1521
|
+
async function handleSlashInteraction(interaction, handler, bridge, wsHandler, chatHandler, cleanupHandler, chatSessionService, modeService, modelService, autoAcceptService, _client, promptDispatcher, templateRepo, joinHandler, userPrefRepo, accountPrefRepo, channelPrefRepo, antigravityAccounts = [{ name: 'default', cdpPort: 9222 }], chatSessionRepo, artifactService, scheduleService) {
|
|
1385
1522
|
const commandName = interaction.commandName;
|
|
1386
1523
|
const getAccountPort = (accountName) => {
|
|
1387
1524
|
const match = antigravityAccounts.find((account) => account.name === accountName);
|
|
@@ -1406,9 +1543,7 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
1406
1543
|
const projectName = bridge.pool.extractProjectName(workspacePath);
|
|
1407
1544
|
return bridge.pool.getConnected(projectName, resolveSelectedAccount());
|
|
1408
1545
|
}
|
|
1409
|
-
return
|
|
1410
|
-
? bridge.pool.getConnected(bridge.lastActiveWorkspace, resolveSelectedAccount())
|
|
1411
|
-
: null;
|
|
1546
|
+
return null;
|
|
1412
1547
|
})();
|
|
1413
1548
|
const ensureChannelCdp = async () => {
|
|
1414
1549
|
const existing = getChannelCdp();
|
|
@@ -1879,6 +2014,63 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
1879
2014
|
await interaction.editReply({ content: codeBlock });
|
|
1880
2015
|
break;
|
|
1881
2016
|
}
|
|
2017
|
+
case 'open': {
|
|
2018
|
+
const filepath = interaction.options.getString('filepath', true);
|
|
2019
|
+
let resolvedPath = null;
|
|
2020
|
+
// 1. Try to resolve as an artifact
|
|
2021
|
+
if (chatSessionRepo && artifactService) {
|
|
2022
|
+
const session = chatSessionRepo.findByChannelId(interaction.channelId);
|
|
2023
|
+
if (session && session.conversationId) {
|
|
2024
|
+
const possibleArtifact = artifactService.getArtifactPath(session.conversationId, filepath);
|
|
2025
|
+
if (fs_1.default.existsSync(possibleArtifact)) {
|
|
2026
|
+
resolvedPath = possibleArtifact;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
// 2. Try to resolve against the workspace
|
|
2031
|
+
if (!resolvedPath) {
|
|
2032
|
+
const cdp = await ensureChannelCdp();
|
|
2033
|
+
if (cdp) {
|
|
2034
|
+
const wsPath = cdp.getCurrentWorkspacePath();
|
|
2035
|
+
if (wsPath) {
|
|
2036
|
+
let rawPath = filepath;
|
|
2037
|
+
if (filepath.startsWith('file:///')) {
|
|
2038
|
+
rawPath = filepath.replace('file:///', '');
|
|
2039
|
+
if (process.platform === 'win32' && rawPath.startsWith('/')) {
|
|
2040
|
+
rawPath = rawPath.substring(1);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
const candidatePath = path_1.default.isAbsolute(rawPath) ? rawPath : path_1.default.join(wsPath, rawPath);
|
|
2044
|
+
const relative = path_1.default.relative(wsPath, candidatePath);
|
|
2045
|
+
if (!relative.startsWith('..') && !path_1.default.isAbsolute(relative)) {
|
|
2046
|
+
resolvedPath = candidatePath;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (!resolvedPath) {
|
|
2052
|
+
await interaction.editReply({
|
|
2053
|
+
content: '❌ Error: Cannot resolve file path. Ensure the file exists as an artifact or within the current workspace.'
|
|
2054
|
+
});
|
|
2055
|
+
break;
|
|
2056
|
+
}
|
|
2057
|
+
try {
|
|
2058
|
+
(0, child_process_1.execFile)((0, pathUtils_1.getAntigravityCliPath)(), [resolvedPath], (error) => {
|
|
2059
|
+
if (error) {
|
|
2060
|
+
logger_1.logger.error(`Failed to open file via CLI: ${error.message}`);
|
|
2061
|
+
interaction.editReply({ content: `❌ Error opening file via CLI.` }).catch(() => { });
|
|
2062
|
+
}
|
|
2063
|
+
else {
|
|
2064
|
+
interaction.editReply({ content: `✅ Opened file: **${path_1.default.basename(resolvedPath)}**` }).catch(() => { });
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
catch (e) {
|
|
2069
|
+
logger_1.logger.error(`Failed to open file: ${e.message}`);
|
|
2070
|
+
await interaction.editReply({ content: `❌ Error opening file.` });
|
|
2071
|
+
}
|
|
2072
|
+
break;
|
|
2073
|
+
}
|
|
1882
2074
|
case 'artifacts': {
|
|
1883
2075
|
await (0, artifactsUi_1.sendArtifactPickerUI)(interaction, {
|
|
1884
2076
|
userPrefRepo,
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.handleJoin = handleJoin;
|
|
4
4
|
exports.handleTelegramJoinSelect = handleTelegramJoinSelect;
|
|
5
5
|
exports.handleMirror = handleMirror;
|
|
6
|
+
exports.startResponseMirror = startResponseMirror;
|
|
6
7
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
7
8
|
const responseMonitor_1 = require("../services/responseMonitor");
|
|
8
9
|
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
@@ -137,6 +138,14 @@ async function routeMirroredMessage(deps, cdp, workspacePath, info, channel) {
|
|
|
137
138
|
startResponseMirror(deps, cdp, workspacePath, channel, chatTitle || 'Unknown');
|
|
138
139
|
}
|
|
139
140
|
function startResponseMirror(deps, cdp, workspacePath, channel, chatTitle) {
|
|
141
|
+
// If the primary message handler already has an active monitor for this workspace,
|
|
142
|
+
// skip the passive mirror — the primary handler delivers the response itself, and
|
|
143
|
+
// running both would send the same AI response twice.
|
|
144
|
+
const projectName = deps.bridge.pool.extractProjectName(workspacePath);
|
|
145
|
+
if (deps.activeMonitors?.has(projectName)) {
|
|
146
|
+
logger_1.logger.debug(`[TelegramMirror] Skipping passive monitor — primary monitor active for ${projectName}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
140
149
|
const prev = activeResponseMonitors.get(workspacePath);
|
|
141
150
|
if (prev?.isActive()) {
|
|
142
151
|
prev.stop().catch(() => { });
|
|
@@ -152,6 +152,7 @@ function createTelegramMessageHandler(deps) {
|
|
|
152
152
|
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(deps.bridge, cdp, projectName, selectedAccount);
|
|
153
153
|
(0, cdpBridgeManager_1.ensurePlanningDetector)(deps.bridge, cdp, projectName, selectedAccount);
|
|
154
154
|
(0, cdpBridgeManager_1.ensureRunCommandDetector)(deps.bridge, cdp, projectName, selectedAccount);
|
|
155
|
+
(0, cdpBridgeManager_1.ensureQuestionDetector)(deps.bridge, cdp, projectName, selectedAccount);
|
|
155
156
|
// Acknowledge receipt
|
|
156
157
|
await message.react('\u{1F440}').catch(() => { });
|
|
157
158
|
// Download image attachments if present
|
|
@@ -173,6 +174,12 @@ function createTelegramMessageHandler(deps) {
|
|
|
173
174
|
// Determine the prompt text — use default for image-only messages
|
|
174
175
|
const effectivePrompt = promptText || 'Please review the attached images and respond accordingly.';
|
|
175
176
|
const baseline = await (0, responseMonitor_1.captureResponseMonitorBaseline)(cdp);
|
|
177
|
+
// Register the echo hash BEFORE injecting (the Discord path registers it later):
|
|
178
|
+
// UserMessageDetector polls the DOM every ~2s, so registering only after
|
|
179
|
+
// injectMessage() resolves leaves a window where the injected prompt is detected
|
|
180
|
+
// as PC-side input and mirrored back to Telegram. If injection fails, the stale
|
|
181
|
+
// hash is harmless — it expires via its 60s TTL.
|
|
182
|
+
deps.bridge.pool.getUserMessageDetector?.(projectName, selectedAccount)?.addEchoHash(effectivePrompt);
|
|
176
183
|
// Inject prompt (with or without images) into Antigravity
|
|
177
184
|
logger_1.logger.prompt(effectivePrompt);
|
|
178
185
|
let injectResult;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.ChatCommandHandler = void 0;
|
|
4
37
|
const i18n_1 = require("../utils/i18n");
|
|
@@ -73,9 +106,33 @@ class ChatCommandHandler {
|
|
|
73
106
|
});
|
|
74
107
|
return;
|
|
75
108
|
}
|
|
109
|
+
// Start a new chat in the IDE
|
|
110
|
+
const newChatResult = await this.chatSessionService.startNewChat(workspaceCdp);
|
|
111
|
+
if (!newChatResult.ok) {
|
|
112
|
+
// Log but don't fail the command, as the channel still needs to be created
|
|
113
|
+
// and the user might just have to click it manually if the IDE state is strange.
|
|
114
|
+
Promise.resolve().then(() => __importStar(require('../utils/logger'))).then(({ logger }) => {
|
|
115
|
+
logger.warn(`[/new] Could not start new chat in IDE automatically: ${newChatResult.error}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const customNameRaw = interaction.options.getString('name');
|
|
119
|
+
let safeCustomName = null;
|
|
120
|
+
if (customNameRaw) {
|
|
121
|
+
// Import TitleGeneratorService to sanitize the channel name if needed,
|
|
122
|
+
// or just do basic sanitization inline.
|
|
123
|
+
safeCustomName = customNameRaw
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/\s+/g, '-')
|
|
126
|
+
.replace(/[^a-z0-9\-_\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/g, '-')
|
|
127
|
+
.replace(/-{2,}/g, '-')
|
|
128
|
+
.replace(/^-+|-+$/g, '')
|
|
129
|
+
.substring(0, 80);
|
|
130
|
+
if (!safeCustomName)
|
|
131
|
+
safeCustomName = null;
|
|
132
|
+
}
|
|
76
133
|
// Create a new session channel
|
|
77
134
|
const sessionNumber = this.chatSessionRepo.getNextSessionNumber(parentId);
|
|
78
|
-
const channelName = `session-${sessionNumber}`;
|
|
135
|
+
const channelName = safeCustomName ? `${sessionNumber}-${safeCustomName}` : `session-${sessionNumber}`;
|
|
79
136
|
const sessionResult = await this.channelManager.createSessionChannel(guild, parentId, channelName);
|
|
80
137
|
const newChannelId = sessionResult.channelId;
|
|
81
138
|
// Register binding and session
|
|
@@ -92,6 +149,19 @@ class ChatCommandHandler {
|
|
|
92
149
|
activeAccountName: selectedAccount,
|
|
93
150
|
guildId: guild.id,
|
|
94
151
|
});
|
|
152
|
+
if (safeCustomName) {
|
|
153
|
+
// Set the display name and mark it as renamed so autoRenameChannel skips it
|
|
154
|
+
this.chatSessionRepo.updateDisplayName(newChannelId, safeCustomName);
|
|
155
|
+
// Try to make the IDE's DOM match the Discord channel
|
|
156
|
+
if (workspaceCdp) {
|
|
157
|
+
const renameResult = await this.chatSessionService.renameCurrentChatInUI(workspaceCdp, safeCustomName);
|
|
158
|
+
if (!renameResult.ok) {
|
|
159
|
+
Promise.resolve().then(() => __importStar(require('../utils/logger'))).then(({ logger }) => {
|
|
160
|
+
logger.warn(`[/new] Could not rename chat in IDE automatically: ${renameResult.error}`);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
95
165
|
const embed = new discord_js_1.EmbedBuilder()
|
|
96
166
|
.setTitle((0, i18n_1.t)('💬 Started a new session'))
|
|
97
167
|
.setDescription((0, i18n_1.t)(`Created a new chat session\n→ <#${newChannelId}>`))
|
|
@@ -95,7 +95,10 @@ const projectCommand = new discord_js_1.SlashCommandBuilder()
|
|
|
95
95
|
/** /new command definition (formerly /chat new, made into a standalone command) */
|
|
96
96
|
const newCommand = new discord_js_1.SlashCommandBuilder()
|
|
97
97
|
.setName('new')
|
|
98
|
-
.setDescription((0, i18n_1.t)('Start a new chat session in the current project'))
|
|
98
|
+
.setDescription((0, i18n_1.t)('Start a new chat session in the current project'))
|
|
99
|
+
.addStringOption(option => option.setName('name')
|
|
100
|
+
.setDescription((0, i18n_1.t)('Name of the new chat session'))
|
|
101
|
+
.setRequired(false));
|
|
99
102
|
/** /chat command definition (merged status + list) */
|
|
100
103
|
const chatCommand = new discord_js_1.SlashCommandBuilder()
|
|
101
104
|
.setName('chat')
|
|
@@ -157,6 +160,14 @@ const pingCommand = new discord_js_1.SlashCommandBuilder()
|
|
|
157
160
|
const artifactsCommand = new discord_js_1.SlashCommandBuilder()
|
|
158
161
|
.setName('artifacts')
|
|
159
162
|
.setDescription((0, i18n_1.t)('Browse and view generated artifacts from the active session'));
|
|
163
|
+
/** /open command definition */
|
|
164
|
+
const openCommand = new discord_js_1.SlashCommandBuilder()
|
|
165
|
+
.setName('open')
|
|
166
|
+
.setDescription((0, i18n_1.t)('Open and read a file from the workspace'))
|
|
167
|
+
.addStringOption((option) => option
|
|
168
|
+
.setName('filepath')
|
|
169
|
+
.setDescription((0, i18n_1.t)('Absolute or relative path to the file'))
|
|
170
|
+
.setRequired(true));
|
|
160
171
|
/** Array of commands to register */
|
|
161
172
|
exports.slashCommands = [
|
|
162
173
|
helpCommand,
|
|
@@ -179,6 +190,7 @@ exports.slashCommands = [
|
|
|
179
190
|
pingCommand,
|
|
180
191
|
logsCommand,
|
|
181
192
|
artifactsCommand,
|
|
193
|
+
openCommand,
|
|
182
194
|
];
|
|
183
195
|
/**
|
|
184
196
|
* Register slash commands with Discord
|