lazy-gravity 0.10.0 → 0.12.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/README.md +7 -0
- package/dist/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +526 -24
- package/dist/commands/chatCommandHandler.js +11 -41
- package/dist/commands/registerSlashCommands.js +62 -0
- package/dist/database/scheduleRepository.js +50 -6
- package/dist/events/interactionCreateHandler.js +163 -28
- package/dist/events/messageCreateHandler.js +24 -28
- package/dist/handlers/fileChangeButtonAction.js +12 -24
- package/dist/handlers/genericActionButtonAction.js +16 -18
- package/dist/handlers/planningButtonAction.js +105 -17
- package/dist/handlers/planningModalSubmitAction.js +38 -0
- package/dist/handlers/questionSelectAction.js +8 -7
- package/dist/handlers/questionSkipAction.js +7 -6
- package/dist/platform/discord/wrappers.js +76 -0
- package/dist/services/approvalDetector.js +43 -14
- package/dist/services/artifactService.js +61 -21
- package/dist/services/assistantDomExtractor.js +23 -3
- package/dist/services/cdpBridgeManager.js +35 -2
- package/dist/services/cdpConnectionPool.js +7 -2
- package/dist/services/cdpService.js +113 -8
- package/dist/services/chatSessionService.js +15 -8
- package/dist/services/heartbeatService.js +261 -0
- package/dist/services/notificationSender.js +12 -2
- package/dist/services/planningDetector.js +1 -1
- package/dist/services/promptDispatcher.js +21 -1
- package/dist/services/questionDetector.js +128 -76
- package/dist/services/responseMonitor.js +17 -1
- package/dist/services/scheduleService.js +101 -4
- package/dist/utils/configLoader.js +8 -0
- package/dist/utils/fileOpenCache.js +2 -6
- package/dist/utils/questionActionUtils.js +22 -0
- package/package.json +2 -1
|
@@ -4,7 +4,7 @@ exports.createFileChangeButtonAction = createFileChangeButtonAction;
|
|
|
4
4
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
5
5
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
6
6
|
const logger_1 = require("../utils/logger");
|
|
7
|
-
const
|
|
7
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
8
8
|
function createFileChangeButtonAction(deps) {
|
|
9
9
|
return {
|
|
10
10
|
match(customId) {
|
|
@@ -36,32 +36,20 @@ function createFileChangeButtonAction(deps) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
const targetText = action === 'accept' ? 'Accept all' : 'Reject all';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const success = await (0, questionActionUtils_1.executeBrowserClick)(cdp, targetText);
|
|
40
|
+
if (success) {
|
|
41
|
+
await interaction
|
|
42
|
+
.update({
|
|
43
|
+
text: `${action === 'accept' ? '✅ Accepted' : '❌ Rejected'} file changes.`,
|
|
44
|
+
components: [],
|
|
45
|
+
})
|
|
46
|
+
.catch((err) => {
|
|
47
|
+
logger_1.logger.warn('[FileChangeAction] update failed:', err);
|
|
44
48
|
});
|
|
45
|
-
if (result?.result?.value?.ok) {
|
|
46
|
-
await interaction
|
|
47
|
-
.update({
|
|
48
|
-
text: `${action === 'accept' ? '✅ Accepted' : '❌ Rejected'} file changes.`,
|
|
49
|
-
components: [],
|
|
50
|
-
})
|
|
51
|
-
.catch((err) => {
|
|
52
|
-
logger_1.logger.warn('[FileChangeAction] update failed:', err);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
await interaction
|
|
57
|
-
.followUp({ text: `Could not find the "${targetText}" button in the IDE. The file change prompt may have been dismissed.` })
|
|
58
|
-
.catch(() => { });
|
|
59
|
-
}
|
|
60
49
|
}
|
|
61
|
-
|
|
62
|
-
logger_1.logger.error('[FileChangeAction] Error clicking button:', error);
|
|
50
|
+
else {
|
|
63
51
|
await interaction
|
|
64
|
-
.followUp({ text:
|
|
52
|
+
.followUp({ text: `Could not find the "${targetText}" button in the IDE. The file change prompt may have been dismissed.` })
|
|
65
53
|
.catch(() => { });
|
|
66
54
|
}
|
|
67
55
|
},
|
|
@@ -4,14 +4,7 @@ exports.parseGenericActionCustomId = parseGenericActionCustomId;
|
|
|
4
4
|
exports.createGenericActionButtonAction = createGenericActionButtonAction;
|
|
5
5
|
const logger_1 = require("../utils/logger");
|
|
6
6
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
7
|
-
const
|
|
8
|
-
/**
|
|
9
|
-
* Parses a generic action button customId into its components.
|
|
10
|
-
* Format: action_btn_[action_name]:[project_name]:[channel_id]
|
|
11
|
-
*
|
|
12
|
-
* @param customId The customId to parse
|
|
13
|
-
* @returns Parsed action details, or null if not an action button
|
|
14
|
-
*/
|
|
7
|
+
const questionActionUtils_1 = require("../utils/questionActionUtils");
|
|
15
8
|
function parseGenericActionCustomId(customId) {
|
|
16
9
|
if (!customId.startsWith('action_btn_'))
|
|
17
10
|
return null;
|
|
@@ -19,10 +12,15 @@ function parseGenericActionCustomId(customId) {
|
|
|
19
12
|
const actionName = parts[0].replace('action_btn_', '').replace(/_/g, ' ');
|
|
20
13
|
// Capitalize first letter
|
|
21
14
|
const formattedName = actionName.charAt(0).toUpperCase() + actionName.slice(1);
|
|
15
|
+
if (parts.length < 3) {
|
|
16
|
+
return { actionName: formattedName };
|
|
17
|
+
}
|
|
18
|
+
const channelId = parts[parts.length - 1];
|
|
19
|
+
const projectName = parts.slice(1, parts.length - 1).join(':');
|
|
22
20
|
return {
|
|
23
21
|
actionName: formattedName,
|
|
24
|
-
projectName:
|
|
25
|
-
channelId:
|
|
22
|
+
projectName: projectName || undefined,
|
|
23
|
+
channelId: channelId || undefined,
|
|
26
24
|
};
|
|
27
25
|
}
|
|
28
26
|
function createGenericActionButtonAction(deps) {
|
|
@@ -42,12 +40,17 @@ function createGenericActionButtonAction(deps) {
|
|
|
42
40
|
const actionName = params.actionName;
|
|
43
41
|
const channelId = params.channelId || interaction.channel?.id;
|
|
44
42
|
if (!channelId) {
|
|
43
|
+
await interaction.reply({
|
|
44
|
+
text: 'Error: Cannot resolve channel ID.',
|
|
45
|
+
ephemeral: true,
|
|
46
|
+
}).catch(() => { });
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
const projectName = (0, projectResolver_1.resolveProjectName)(deps, channelId, params.projectName);
|
|
48
50
|
await interaction.deferUpdate().catch(() => { });
|
|
51
|
+
const accountName = deps.bridge.selectedAccountByChannel?.get(channelId) || 'default';
|
|
49
52
|
const cdp = projectName
|
|
50
|
-
? deps.bridge.pool.getConnected(projectName)
|
|
53
|
+
? deps.bridge.pool.getConnected(projectName, accountName)
|
|
51
54
|
: null;
|
|
52
55
|
if (!cdp) {
|
|
53
56
|
await interaction.followUp({
|
|
@@ -57,13 +60,8 @@ function createGenericActionButtonAction(deps) {
|
|
|
57
60
|
}
|
|
58
61
|
// Simulate DOM click instead of chat injection
|
|
59
62
|
logger_1.logger.info(`[GenericActionButton] Clicking action button "${actionName}" via DOM script`);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
expression: script,
|
|
63
|
-
returnByValue: true,
|
|
64
|
-
});
|
|
65
|
-
const resultValue = evalResult.result?.value;
|
|
66
|
-
if (!resultValue?.ok) {
|
|
63
|
+
const success = await (0, questionActionUtils_1.executeBrowserClick)(cdp, actionName);
|
|
64
|
+
if (!success) {
|
|
67
65
|
await interaction.followUp({
|
|
68
66
|
text: `Failed to execute action: Button "${actionName}" not found or obscured.`,
|
|
69
67
|
}).catch(() => { });
|
|
@@ -5,11 +5,48 @@
|
|
|
5
5
|
* Handles Open / Proceed button presses for the planning mode dialog
|
|
6
6
|
* from both Discord and Telegram using the ButtonAction interface.
|
|
7
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
42
|
exports.createPlanningButtonAction = createPlanningButtonAction;
|
|
10
43
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
11
44
|
const projectResolver_1 = require("../utils/projectResolver");
|
|
12
45
|
const logger_1 = require("../utils/logger");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
13
50
|
const MAX_PLAN_CONTENT = 4096;
|
|
14
51
|
function createPlanningButtonAction(deps) {
|
|
15
52
|
return {
|
|
@@ -25,11 +62,10 @@ function createPlanningButtonAction(deps) {
|
|
|
25
62
|
},
|
|
26
63
|
async execute(interaction, params) {
|
|
27
64
|
const { action, channelId } = params;
|
|
28
|
-
// Acknowledge immediately so Telegram doesn't time out
|
|
29
|
-
await interaction.deferUpdate().catch(() => { });
|
|
30
65
|
if (channelId && channelId !== interaction.channel.id) {
|
|
66
|
+
await interaction.deferUpdate().catch(() => { });
|
|
31
67
|
await interaction
|
|
32
|
-
.
|
|
68
|
+
.followUp({ text: 'This planning action is linked to a different session channel.', ephemeral: true })
|
|
33
69
|
.catch(() => { });
|
|
34
70
|
return;
|
|
35
71
|
}
|
|
@@ -38,14 +74,42 @@ function createPlanningButtonAction(deps) {
|
|
|
38
74
|
? deps.bridge.pool.getPlanningDetector(projectName)
|
|
39
75
|
: undefined;
|
|
40
76
|
if (!detector) {
|
|
77
|
+
await interaction.deferUpdate().catch(() => { });
|
|
41
78
|
await interaction
|
|
42
|
-
.
|
|
79
|
+
.followUp({ text: 'Planning detector not found.', ephemeral: true })
|
|
43
80
|
.catch(() => { });
|
|
44
81
|
return;
|
|
45
82
|
}
|
|
46
83
|
if (action === 'open') {
|
|
84
|
+
const info = detector.getLastDetectedInfo();
|
|
85
|
+
const isReview = info?.openText?.toLowerCase().includes('review');
|
|
86
|
+
let modalFailed = false;
|
|
87
|
+
if (isReview && interaction.showModal) {
|
|
88
|
+
try {
|
|
89
|
+
await interaction.showModal({
|
|
90
|
+
title: 'Submit Comment',
|
|
91
|
+
customId: `plan_comment_${projectName || ''}_${channelId}`,
|
|
92
|
+
components: [{
|
|
93
|
+
components: [{
|
|
94
|
+
type: 'textInput',
|
|
95
|
+
customId: 'comment',
|
|
96
|
+
label: 'Add a message...',
|
|
97
|
+
style: 'paragraph',
|
|
98
|
+
required: false
|
|
99
|
+
}]
|
|
100
|
+
}]
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
logger_1.logger.error('[PlanningAction] showModal failed:', err);
|
|
106
|
+
modalFailed = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Acknowledge if we didn't show a modal
|
|
110
|
+
await interaction.deferUpdate().catch(() => { });
|
|
47
111
|
let clicked = false;
|
|
48
|
-
if (
|
|
112
|
+
if (info?.hasOpenButton && (!isReview || modalFailed)) {
|
|
49
113
|
try {
|
|
50
114
|
clicked = await detector.clickOpenButton();
|
|
51
115
|
if (clicked) {
|
|
@@ -56,7 +120,8 @@ function createPlanningButtonAction(deps) {
|
|
|
56
120
|
logger_1.logger.error('[PlanningAction] clickOpenButton failed:', e);
|
|
57
121
|
}
|
|
58
122
|
}
|
|
59
|
-
//
|
|
123
|
+
// Since we only come here if it's NOT a review (or modal failed),
|
|
124
|
+
// we can just extract plan content and followUp
|
|
60
125
|
let planContent = null;
|
|
61
126
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
62
127
|
planContent = await detector.extractPlanContent();
|
|
@@ -64,22 +129,42 @@ function createPlanningButtonAction(deps) {
|
|
|
64
129
|
break;
|
|
65
130
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
66
131
|
}
|
|
132
|
+
if (!planContent && !clicked) {
|
|
133
|
+
// Fallback to reading implementation_plan.md from the workspace
|
|
134
|
+
const workspaceId = deps.wsHandler.getWorkspaceForChannel(interaction.channel.id);
|
|
135
|
+
if (workspaceId) {
|
|
136
|
+
const planFile = path.join(workspaceId, 'implementation_plan.md');
|
|
137
|
+
if (fs.existsSync(planFile)) {
|
|
138
|
+
try {
|
|
139
|
+
planContent = fs.readFileSync(planFile, 'utf8');
|
|
140
|
+
// Proactively open the file in the IDE
|
|
141
|
+
(0, child_process_1.execFile)((0, pathUtils_1.getAntigravityCliPath)(), [planFile], (err) => {
|
|
142
|
+
if (err)
|
|
143
|
+
logger_1.logger.error(`[PlanningAction] Failed to open plan via CLI: ${err.message}`);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
logger_1.logger.error(`[PlanningAction] Failed to read plan file: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
67
152
|
if (!planContent && !clicked) {
|
|
68
153
|
await interaction
|
|
69
|
-
.
|
|
154
|
+
.followUp({ text: 'Plan content could not be extracted from the IDE or workspace.' })
|
|
70
155
|
.catch(() => { });
|
|
71
156
|
return;
|
|
72
157
|
}
|
|
73
158
|
await interaction
|
|
74
|
-
.
|
|
159
|
+
.editReply({
|
|
75
160
|
text: '📋 Plan opened',
|
|
76
161
|
components: [],
|
|
77
162
|
})
|
|
78
163
|
.catch((err) => {
|
|
79
|
-
logger_1.logger.warn('[PlanningAction]
|
|
164
|
+
logger_1.logger.warn('[PlanningAction] editReply failed:', err);
|
|
80
165
|
});
|
|
81
166
|
if (planContent) {
|
|
82
|
-
|
|
167
|
+
let truncated = planContent.length > MAX_PLAN_CONTENT
|
|
83
168
|
? planContent.substring(0, MAX_PLAN_CONTENT - 15) + '\n\n(truncated)'
|
|
84
169
|
: planContent;
|
|
85
170
|
await interaction
|
|
@@ -90,13 +175,16 @@ function createPlanningButtonAction(deps) {
|
|
|
90
175
|
}
|
|
91
176
|
else {
|
|
92
177
|
await interaction
|
|
93
|
-
.followUp({ text: '
|
|
94
|
-
.catch(() => {
|
|
178
|
+
.followUp({ text: 'Plan opened in IDE, but content could not be extracted.' })
|
|
179
|
+
.catch((err) => {
|
|
180
|
+
logger_1.logger.warn('[PlanningAction] followUp failed:', err);
|
|
181
|
+
});
|
|
95
182
|
}
|
|
96
183
|
}
|
|
97
184
|
else if (action === 'reject') {
|
|
185
|
+
await interaction.deferUpdate().catch(() => { });
|
|
98
186
|
// Reject action
|
|
99
|
-
await interaction.
|
|
187
|
+
await interaction.followUp({ text: 'Rejection of the plan is not allowed.', ephemeral: true }).catch(() => { });
|
|
100
188
|
return;
|
|
101
189
|
}
|
|
102
190
|
else {
|
|
@@ -114,17 +202,17 @@ function createPlanningButtonAction(deps) {
|
|
|
114
202
|
}
|
|
115
203
|
if (clicked) {
|
|
116
204
|
await interaction
|
|
117
|
-
.
|
|
118
|
-
text: '▶️ Proceed started',
|
|
205
|
+
.editReply({
|
|
206
|
+
text: '▶️ Proceed started.\n\n⏳ IDE is working on the response...',
|
|
119
207
|
components: [],
|
|
120
208
|
})
|
|
121
209
|
.catch((err) => {
|
|
122
|
-
logger_1.logger.warn('[PlanningAction]
|
|
210
|
+
logger_1.logger.warn('[PlanningAction] editReply failed:', err);
|
|
123
211
|
});
|
|
124
212
|
}
|
|
125
213
|
else {
|
|
126
214
|
await interaction
|
|
127
|
-
.
|
|
215
|
+
.followUp({ text: 'Proceed button not found.', ephemeral: true })
|
|
128
216
|
.catch(() => { });
|
|
129
217
|
}
|
|
130
218
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.execute = execute;
|
|
4
|
+
const i18n_1 = require("../utils/i18n");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
async function execute(interaction, projectName, pool, accountName) {
|
|
7
|
+
try {
|
|
8
|
+
if (!projectName) {
|
|
9
|
+
await interaction.reply({ text: (0, i18n_1.t)('Project name not found.'), ephemeral: true });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const cdp = pool.getConnected(projectName, accountName);
|
|
13
|
+
if (!cdp) {
|
|
14
|
+
await interaction.reply({ text: (0, i18n_1.t)('Not connected to workspace.'), ephemeral: true });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const comment = interaction.fields.get('comment')?.trim();
|
|
18
|
+
if (!comment) {
|
|
19
|
+
await interaction.reply({ text: (0, i18n_1.t)('Comment cannot be empty.'), ephemeral: true });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await interaction.deferUpdate();
|
|
23
|
+
// Inject the comment into the chat
|
|
24
|
+
const result = await cdp.injectMessage(comment);
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
await interaction.followUp({ text: (0, i18n_1.t)('Failed to submit comment: {{error}}', { error: result.error }), ephemeral: true });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await interaction.followUp({ text: (0, i18n_1.t)('Comment submitted to the plan successfully.'), ephemeral: true });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger_1.logger.error('[PlanningModalSubmit] Error:', error);
|
|
33
|
+
try {
|
|
34
|
+
await interaction.followUp({ text: (0, i18n_1.t)('An error occurred while submitting your comment.'), ephemeral: true });
|
|
35
|
+
}
|
|
36
|
+
catch { /* ignore */ }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -13,16 +13,16 @@ function createQuestionSelectAction(deps) {
|
|
|
13
13
|
async execute(interaction, values) {
|
|
14
14
|
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SELECT_ACTION_PREFIX);
|
|
15
15
|
if (!parsed)
|
|
16
|
-
return;
|
|
16
|
+
return false;
|
|
17
17
|
const selectedOption = parseInt(values[0], 10);
|
|
18
18
|
if (isNaN(selectedOption))
|
|
19
|
-
return;
|
|
19
|
+
return false;
|
|
20
20
|
const channelId = parsed.channelId;
|
|
21
21
|
if (channelId && channelId !== interaction.channel.id) {
|
|
22
22
|
await interaction
|
|
23
23
|
.reply({ text: 'This question is linked to a different session channel.', ephemeral: true })
|
|
24
24
|
.catch(() => { });
|
|
25
|
-
return;
|
|
25
|
+
return false;
|
|
26
26
|
}
|
|
27
27
|
const projectName = (0, projectResolver_1.resolveProjectName)(deps, interaction.channel.id, parsed.projectName);
|
|
28
28
|
logger_1.logger.debug(`[QuestionSelectAction] project=${projectName ?? 'null'} channel=${interaction.channel.id}`);
|
|
@@ -34,7 +34,7 @@ function createQuestionSelectAction(deps) {
|
|
|
34
34
|
await interaction
|
|
35
35
|
.reply({ text: 'Question detector not found.', ephemeral: true })
|
|
36
36
|
.catch(() => { });
|
|
37
|
-
return;
|
|
37
|
+
return false;
|
|
38
38
|
}
|
|
39
39
|
await interaction.deferUpdate().catch(() => { });
|
|
40
40
|
let success = false;
|
|
@@ -47,16 +47,16 @@ function createQuestionSelectAction(deps) {
|
|
|
47
47
|
await interaction
|
|
48
48
|
.followUp({ text: `Failed to answer question: ${msg}`, ephemeral: true })
|
|
49
49
|
.catch(() => { });
|
|
50
|
-
return;
|
|
50
|
+
return false;
|
|
51
51
|
}
|
|
52
52
|
if (success) {
|
|
53
|
-
const updatePayload = { text: `✅ Submitted option ${selectedOption + 1}
|
|
53
|
+
const updatePayload = { text: `✅ Submitted option ${selectedOption + 1}.\n\n⏳ IDE is working on the response...`, components: [] };
|
|
54
54
|
try {
|
|
55
55
|
await interaction.editReply(updatePayload);
|
|
56
56
|
}
|
|
57
57
|
catch (editErr) {
|
|
58
58
|
logger_1.logger.warn('[QuestionSelectAction] editReply failed, sending followUp:', editErr);
|
|
59
|
-
await interaction.followUp({ text: `✅ Submitted option ${selectedOption + 1}
|
|
59
|
+
await interaction.followUp({ text: `✅ Submitted option ${selectedOption + 1}.\n\n⏳ IDE is working on the response...` }).catch(() => { });
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
else {
|
|
@@ -64,6 +64,7 @@ function createQuestionSelectAction(deps) {
|
|
|
64
64
|
.followUp({ text: 'Failed to answer question (it may have been resolved already).', ephemeral: true })
|
|
65
65
|
.catch(() => { });
|
|
66
66
|
}
|
|
67
|
+
return success;
|
|
67
68
|
},
|
|
68
69
|
};
|
|
69
70
|
}
|
|
@@ -14,13 +14,13 @@ function createQuestionSkipAction(deps) {
|
|
|
14
14
|
async execute(interaction, params) {
|
|
15
15
|
const parsed = (0, questionActionUtils_1.parseQuestionCustomId)(interaction.customId, notificationSender_1.QUESTION_SKIP_ACTION_PREFIX);
|
|
16
16
|
if (!parsed)
|
|
17
|
-
return;
|
|
17
|
+
return false;
|
|
18
18
|
const channelId = parsed.channelId;
|
|
19
19
|
if (channelId && channelId !== interaction.channel.id) {
|
|
20
20
|
await interaction
|
|
21
21
|
.reply({ text: 'This question is linked to a different session channel.', ephemeral: true })
|
|
22
22
|
.catch(() => { });
|
|
23
|
-
return;
|
|
23
|
+
return false;
|
|
24
24
|
}
|
|
25
25
|
const projectName = (0, projectResolver_1.resolveProjectName)(deps, interaction.channel.id, parsed.projectName);
|
|
26
26
|
logger_1.logger.debug(`[QuestionSkipAction] project=${projectName ?? 'null'} channel=${interaction.channel.id}`);
|
|
@@ -32,7 +32,7 @@ function createQuestionSkipAction(deps) {
|
|
|
32
32
|
await interaction
|
|
33
33
|
.reply({ text: 'Question detector not found.', ephemeral: true })
|
|
34
34
|
.catch(() => { });
|
|
35
|
-
return;
|
|
35
|
+
return false;
|
|
36
36
|
}
|
|
37
37
|
await interaction.deferUpdate().catch(() => { });
|
|
38
38
|
let success = false;
|
|
@@ -45,16 +45,16 @@ function createQuestionSkipAction(deps) {
|
|
|
45
45
|
await interaction
|
|
46
46
|
.followUp({ text: `Failed to skip question: ${msg}`, ephemeral: true })
|
|
47
47
|
.catch(() => { });
|
|
48
|
-
return;
|
|
48
|
+
return false;
|
|
49
49
|
}
|
|
50
50
|
if (success) {
|
|
51
|
-
const updatePayload = { text:
|
|
51
|
+
const updatePayload = { text: '✅ Skipped question.\n\n⏳ IDE is working on the response...', components: [] };
|
|
52
52
|
try {
|
|
53
53
|
await interaction.editReply(updatePayload);
|
|
54
54
|
}
|
|
55
55
|
catch (editErr) {
|
|
56
56
|
logger_1.logger.warn('[QuestionSkipAction] editReply failed, sending followUp:', editErr);
|
|
57
|
-
await interaction.followUp({ text:
|
|
57
|
+
await interaction.followUp({ text: '✅ Skipped question.\n\n⏳ IDE is working on the response...' }).catch(() => { });
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
@@ -62,6 +62,7 @@ function createQuestionSkipAction(deps) {
|
|
|
62
62
|
.followUp({ text: 'Failed to skip question (it may have been resolved already).', ephemeral: true })
|
|
63
63
|
.catch(() => { });
|
|
64
64
|
}
|
|
65
|
+
return success;
|
|
65
66
|
},
|
|
66
67
|
};
|
|
67
68
|
}
|
|
@@ -15,6 +15,7 @@ exports.wrapDiscordMessage = wrapDiscordMessage;
|
|
|
15
15
|
exports.wrapDiscordButton = wrapDiscordButton;
|
|
16
16
|
exports.wrapDiscordSelect = wrapDiscordSelect;
|
|
17
17
|
exports.wrapDiscordCommand = wrapDiscordCommand;
|
|
18
|
+
exports.wrapDiscordModalSubmit = wrapDiscordModalSubmit;
|
|
18
19
|
const discord_js_1 = require("discord.js");
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Style mapping
|
|
@@ -107,6 +108,32 @@ function toDiscordComponents(rows) {
|
|
|
107
108
|
return actionRow;
|
|
108
109
|
});
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Convert a platform ModalDef to a discord.js ModalBuilder.
|
|
113
|
+
*/
|
|
114
|
+
function toDiscordModal(modal) {
|
|
115
|
+
const builder = new discord_js_1.ModalBuilder()
|
|
116
|
+
.setCustomId(modal.customId)
|
|
117
|
+
.setTitle(modal.title);
|
|
118
|
+
for (const row of modal.components) {
|
|
119
|
+
const actionRow = new discord_js_1.ActionRowBuilder();
|
|
120
|
+
for (const comp of row.components) {
|
|
121
|
+
const input = new discord_js_1.TextInputBuilder()
|
|
122
|
+
.setCustomId(comp.customId)
|
|
123
|
+
.setLabel(comp.label)
|
|
124
|
+
.setStyle(comp.style === 'paragraph' ? discord_js_1.TextInputStyle.Paragraph : discord_js_1.TextInputStyle.Short);
|
|
125
|
+
if (comp.placeholder !== undefined) {
|
|
126
|
+
input.setPlaceholder(comp.placeholder);
|
|
127
|
+
}
|
|
128
|
+
if (comp.required !== undefined) {
|
|
129
|
+
input.setRequired(comp.required);
|
|
130
|
+
}
|
|
131
|
+
actionRow.addComponents(input);
|
|
132
|
+
}
|
|
133
|
+
builder.addComponents(actionRow);
|
|
134
|
+
}
|
|
135
|
+
return builder;
|
|
136
|
+
}
|
|
110
137
|
/**
|
|
111
138
|
* Convert a platform MessagePayload to discord.js message send/reply options.
|
|
112
139
|
*
|
|
@@ -256,6 +283,9 @@ function wrapDiscordButton(interaction) {
|
|
|
256
283
|
const sent = await interaction.followUp(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
257
284
|
return wrapDiscordSentMessage(sent);
|
|
258
285
|
},
|
|
286
|
+
async showModal(modal) {
|
|
287
|
+
await interaction.showModal(toDiscordModal(modal));
|
|
288
|
+
},
|
|
259
289
|
};
|
|
260
290
|
}
|
|
261
291
|
/** Wrap a discord.js StringSelectMenuInteraction as a PlatformSelectInteraction. */
|
|
@@ -329,3 +359,49 @@ function wrapDiscordCommand(interaction) {
|
|
|
329
359
|
},
|
|
330
360
|
};
|
|
331
361
|
}
|
|
362
|
+
/** Wrap a discord.js ModalSubmitInteraction as a PlatformModalSubmitInteraction. */
|
|
363
|
+
function wrapDiscordModalSubmit(interaction) {
|
|
364
|
+
const user = wrapDiscordUser(interaction.user);
|
|
365
|
+
const channel = interaction.channel
|
|
366
|
+
? wrapDiscordChannel(interaction.channel)
|
|
367
|
+
: buildFallbackChannel(interaction.channelId ?? 'unknown');
|
|
368
|
+
const fields = new Map();
|
|
369
|
+
for (const [key, field] of interaction.fields.fields.entries()) {
|
|
370
|
+
fields.set(key, interaction.fields.getTextInputValue(key));
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
id: interaction.id,
|
|
374
|
+
platform: 'discord',
|
|
375
|
+
customId: interaction.customId,
|
|
376
|
+
user,
|
|
377
|
+
channel,
|
|
378
|
+
messageId: interaction.message?.id ?? null,
|
|
379
|
+
fields,
|
|
380
|
+
async deferUpdate() {
|
|
381
|
+
if (interaction.isFromMessage()) {
|
|
382
|
+
await interaction.deferUpdate();
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
await interaction.deferReply({ ephemeral: true });
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
async reply(payload) {
|
|
389
|
+
await interaction.reply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
390
|
+
},
|
|
391
|
+
async update(payload) {
|
|
392
|
+
if (interaction.isFromMessage()) {
|
|
393
|
+
await interaction.update(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
await interaction.reply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async editReply(payload) {
|
|
400
|
+
await interaction.editReply(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
401
|
+
},
|
|
402
|
+
async followUp(payload) {
|
|
403
|
+
const sent = await interaction.followUp(toDiscordPayload(payload, EPHEMERAL_ALLOWED));
|
|
404
|
+
return wrapDiscordSentMessage(sent);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|