lazy-gravity 0.9.2 ā 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot/index.js +232 -41
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/commands/chatCommandHandler.js +71 -1
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +324 -26
- package/dist/events/messageCreateHandler.js +53 -4
- package/dist/handlers/__tests__/fileChangeButtonAction.test.js +85 -0
- package/dist/handlers/approvalButtonAction.js +2 -1
- package/dist/handlers/errorPopupButtonAction.js +2 -1
- package/dist/handlers/fileChangeButtonAction.js +69 -0
- package/dist/handlers/genericActionButtonAction.js +74 -0
- package/dist/handlers/planningButtonAction.js +24 -9
- package/dist/handlers/questionSelectAction.js +69 -0
- package/dist/handlers/questionSkipAction.js +67 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/services/approvalDetector.js +70 -24
- package/dist/services/artifactService.js +57 -31
- package/dist/services/assistantDomExtractor.js +174 -3
- package/dist/services/cdpBridgeManager.js +114 -6
- package/dist/services/cdpConnectionPool.js +15 -0
- package/dist/services/cdpService.js +21 -7
- package/dist/services/chatSessionService.js +133 -33
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +70 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +7 -1
- package/dist/services/questionDetector.js +376 -0
- package/dist/services/responseMonitor.js +28 -2
- package/dist/services/runCommandDetector.js +14 -7
- package/dist/ui/artifactsUi.js +4 -3
- package/dist/utils/consecutiveEmptyPollGate.js +21 -0
- package/dist/utils/fileOpenCache.js +33 -0
- package/dist/utils/htmlToDiscordMarkdown.js +5 -2
- package/dist/utils/pathUtils.js +12 -0
- package/dist/utils/projectResolver.js +10 -0
- package/dist/utils/questionActionUtils.js +25 -0
- package/package.json +1 -1
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
|
|
@@ -1206,7 +1338,9 @@ const startBot = async (cliLogLevel) => {
|
|
|
1206
1338
|
}, interaction);
|
|
1207
1339
|
return;
|
|
1208
1340
|
}
|
|
1209
|
-
if (interaction.customId === 'mode_select' ||
|
|
1341
|
+
if (interaction.customId === 'mode_select' ||
|
|
1342
|
+
interaction.customId === 'account_select' ||
|
|
1343
|
+
interaction.customId.startsWith('question_select_action')) {
|
|
1210
1344
|
await telegramSelectHandler(interaction);
|
|
1211
1345
|
return;
|
|
1212
1346
|
}
|
|
@@ -1221,10 +1355,10 @@ const startBot = async (cliLogLevel) => {
|
|
|
1221
1355
|
}
|
|
1222
1356
|
const telegramButtonHandler = (0, buttonHandler_1.createPlatformButtonHandler)({
|
|
1223
1357
|
actions: [
|
|
1224
|
-
(0, approvalButtonAction_1.createApprovalButtonAction)({ bridge }),
|
|
1225
|
-
(0, planningButtonAction_1.createPlanningButtonAction)({ bridge }),
|
|
1226
|
-
(0, errorPopupButtonAction_1.createErrorPopupButtonAction)({ bridge }),
|
|
1227
|
-
(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 }),
|
|
1228
1362
|
(0, modelButtonAction_1.createModelButtonAction)({
|
|
1229
1363
|
bridge,
|
|
1230
1364
|
fetchQuota: () => bridge.quota.fetchQuota(),
|
|
@@ -1262,6 +1396,8 @@ const startBot = async (cliLogLevel) => {
|
|
|
1262
1396
|
}),
|
|
1263
1397
|
(0, autoAcceptButtonAction_1.createAutoAcceptButtonAction)({ autoAcceptService: bridge.autoAccept }),
|
|
1264
1398
|
(0, templateButtonAction_1.createTemplateButtonAction)({ bridge, templateRepo }),
|
|
1399
|
+
(0, fileChangeButtonAction_1.createFileChangeButtonAction)({ bridge, wsHandler }),
|
|
1400
|
+
(0, genericActionButtonAction_1.createGenericActionButtonAction)({ bridge, wsHandler }),
|
|
1265
1401
|
],
|
|
1266
1402
|
});
|
|
1267
1403
|
const eventRouter = new eventRouter_1.EventRouter({ allowedUsers }, {
|
|
@@ -1382,7 +1518,7 @@ async function autoRenameChannel(message, chatSessionRepo, titleGenerator, chann
|
|
|
1382
1518
|
/**
|
|
1383
1519
|
* Handle Discord Interactions API slash commands
|
|
1384
1520
|
*/
|
|
1385
|
-
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) {
|
|
1386
1522
|
const commandName = interaction.commandName;
|
|
1387
1523
|
const getAccountPort = (accountName) => {
|
|
1388
1524
|
const match = antigravityAccounts.find((account) => account.name === accountName);
|
|
@@ -1407,9 +1543,7 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
1407
1543
|
const projectName = bridge.pool.extractProjectName(workspacePath);
|
|
1408
1544
|
return bridge.pool.getConnected(projectName, resolveSelectedAccount());
|
|
1409
1545
|
}
|
|
1410
|
-
return
|
|
1411
|
-
? bridge.pool.getConnected(bridge.lastActiveWorkspace, resolveSelectedAccount())
|
|
1412
|
-
: null;
|
|
1546
|
+
return null;
|
|
1413
1547
|
})();
|
|
1414
1548
|
const ensureChannelCdp = async () => {
|
|
1415
1549
|
const existing = getChannelCdp();
|
|
@@ -1880,6 +2014,63 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
1880
2014
|
await interaction.editReply({ content: codeBlock });
|
|
1881
2015
|
break;
|
|
1882
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
|
+
}
|
|
1883
2074
|
case 'artifacts': {
|
|
1884
2075
|
await (0, artifactsUi_1.sendArtifactPickerUI)(interaction, {
|
|
1885
2076
|
userPrefRepo,
|
|
@@ -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
|
|
@@ -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
|