lazy-gravity 0.0.4 → 0.2.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 +22 -7
- package/dist/bin/cli.js +18 -18
- package/dist/bin/commands/doctor.js +25 -19
- package/dist/bin/commands/start.js +25 -2
- package/dist/bot/index.js +445 -126
- package/dist/commands/joinCommandHandler.js +302 -0
- package/dist/commands/joinDetachCommandHandler.js +285 -0
- package/dist/commands/registerSlashCommands.js +40 -0
- package/dist/commands/workspaceCommandHandler.js +17 -28
- package/dist/database/chatSessionRepository.js +10 -0
- package/dist/database/userPreferenceRepository.js +72 -0
- package/dist/events/interactionCreateHandler.js +338 -30
- package/dist/events/messageCreateHandler.js +161 -47
- package/dist/services/antigravityLauncher.js +4 -3
- package/dist/services/approvalDetector.js +7 -0
- package/dist/services/assistantDomExtractor.js +339 -0
- package/dist/services/cdpBridgeManager.js +323 -39
- package/dist/services/cdpConnectionPool.js +117 -33
- package/dist/services/cdpService.js +149 -53
- package/dist/services/chatSessionService.js +229 -8
- package/dist/services/errorPopupDetector.js +271 -0
- package/dist/services/planningDetector.js +318 -0
- package/dist/services/responseMonitor.js +308 -70
- package/dist/services/retryStore.js +46 -0
- package/dist/services/updateCheckService.js +147 -0
- package/dist/services/userMessageDetector.js +221 -0
- package/dist/ui/buttonUtils.js +33 -0
- package/dist/ui/modeUi.js +11 -1
- package/dist/ui/modelsUi.js +24 -13
- package/dist/ui/outputUi.js +30 -0
- package/dist/ui/projectListUi.js +83 -0
- package/dist/ui/sessionPickerUi.js +48 -0
- package/dist/utils/antigravityPaths.js +94 -0
- package/dist/utils/configLoader.js +18 -0
- package/dist/utils/discordButtonUtils.js +33 -0
- package/dist/utils/discordFormatter.js +149 -16
- package/dist/utils/htmlToDiscordMarkdown.js +184 -0
- package/dist/utils/logBuffer.js +47 -0
- package/dist/utils/logFileTransport.js +147 -0
- package/dist/utils/logger.js +86 -21
- package/dist/utils/pathUtils.js +57 -0
- package/dist/utils/plainTextFormatter.js +70 -0
- package/dist/utils/processLogBuffer.js +4 -0
- package/package.json +4 -4
|
@@ -7,10 +7,11 @@ exports.WorkspaceCommandHandler = exports.WORKSPACE_SELECT_ID = exports.PROJECT_
|
|
|
7
7
|
const i18n_1 = require("../utils/i18n");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const discord_js_1 = require("discord.js");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exports.
|
|
10
|
+
const projectListUi_1 = require("../ui/projectListUi");
|
|
11
|
+
// Re-export for backward compatibility
|
|
12
|
+
var projectListUi_2 = require("../ui/projectListUi");
|
|
13
|
+
Object.defineProperty(exports, "PROJECT_SELECT_ID", { enumerable: true, get: function () { return projectListUi_2.PROJECT_SELECT_ID; } });
|
|
14
|
+
Object.defineProperty(exports, "WORKSPACE_SELECT_ID", { enumerable: true, get: function () { return projectListUi_2.WORKSPACE_SELECT_ID; } });
|
|
14
15
|
/**
|
|
15
16
|
* Handler for the /project slash command.
|
|
16
17
|
* When a project is selected, auto-creates a Discord category + session-1 channel and binds them.
|
|
@@ -31,31 +32,19 @@ class WorkspaceCommandHandler {
|
|
|
31
32
|
* /project list -- Display project list via select menu
|
|
32
33
|
*/
|
|
33
34
|
async handleShow(interaction) {
|
|
34
|
-
const embed = new discord_js_1.EmbedBuilder()
|
|
35
|
-
.setTitle('📁 Projects')
|
|
36
|
-
.setColor(0x5865F2)
|
|
37
|
-
.setDescription((0, i18n_1.t)('Select a project to auto-create a category and session channel'))
|
|
38
|
-
.setTimestamp();
|
|
39
|
-
const components = [];
|
|
40
35
|
const workspaces = this.workspaceService.scanWorkspaces();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
components.push(new discord_js_1.ActionRowBuilder().addComponents(selectMenu));
|
|
54
|
-
}
|
|
55
|
-
await interaction.editReply({
|
|
56
|
-
embeds: [embed],
|
|
57
|
-
components,
|
|
58
|
-
});
|
|
36
|
+
const { embeds, components } = (0, projectListUi_1.buildProjectListUI)(workspaces, 0);
|
|
37
|
+
await interaction.editReply({ embeds, components });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Handle page navigation button press.
|
|
41
|
+
* Re-scans workspaces and renders the requested page.
|
|
42
|
+
*/
|
|
43
|
+
async handlePageButton(interaction, page) {
|
|
44
|
+
await interaction.deferUpdate();
|
|
45
|
+
const workspaces = this.workspaceService.scanWorkspaces();
|
|
46
|
+
const { embeds, components } = (0, projectListUi_1.buildProjectListUI)(workspaces, page);
|
|
47
|
+
await interaction.editReply({ embeds, components });
|
|
59
48
|
}
|
|
60
49
|
/**
|
|
61
50
|
* Handler for when a project is selected from the select menu.
|
|
@@ -67,6 +67,16 @@ class ChatSessionRepository {
|
|
|
67
67
|
const result = this.db.prepare('UPDATE chat_sessions SET display_name = ?, is_renamed = 1 WHERE channel_id = ?').run(displayName, channelId);
|
|
68
68
|
return result.changes > 0;
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Find a session by display name within a workspace.
|
|
72
|
+
* Returns the first match (most recent).
|
|
73
|
+
*/
|
|
74
|
+
findByDisplayName(workspacePath, displayName) {
|
|
75
|
+
const row = this.db.prepare('SELECT * FROM chat_sessions WHERE workspace_path = ? AND display_name = ? ORDER BY id DESC LIMIT 1').get(workspacePath, displayName);
|
|
76
|
+
if (!row)
|
|
77
|
+
return undefined;
|
|
78
|
+
return this.mapRow(row);
|
|
79
|
+
}
|
|
70
80
|
deleteByChannelId(channelId) {
|
|
71
81
|
const result = this.db.prepare('DELETE FROM chat_sessions WHERE channel_id = ?').run(channelId);
|
|
72
82
|
return result.changes > 0;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserPreferenceRepository = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Repository class for SQLite persistence of per-user preferences.
|
|
6
|
+
* Currently stores output format preference (embed vs plain text).
|
|
7
|
+
*/
|
|
8
|
+
class UserPreferenceRepository {
|
|
9
|
+
db;
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.initialize();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize table (create if not exists)
|
|
16
|
+
*/
|
|
17
|
+
initialize() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS user_preferences (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
user_id TEXT NOT NULL UNIQUE,
|
|
22
|
+
output_format TEXT NOT NULL DEFAULT 'embed',
|
|
23
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
24
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the output format preference for a user.
|
|
30
|
+
* Returns 'embed' as default if no preference is stored.
|
|
31
|
+
*/
|
|
32
|
+
getOutputFormat(userId) {
|
|
33
|
+
const row = this.db.prepare('SELECT output_format FROM user_preferences WHERE user_id = ?').get(userId);
|
|
34
|
+
if (!row)
|
|
35
|
+
return 'embed';
|
|
36
|
+
return row.output_format === 'plain' ? 'plain' : 'embed';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Set the output format preference for a user (upsert).
|
|
40
|
+
*/
|
|
41
|
+
setOutputFormat(userId, format) {
|
|
42
|
+
this.db.prepare(`
|
|
43
|
+
INSERT INTO user_preferences (user_id, output_format)
|
|
44
|
+
VALUES (?, ?)
|
|
45
|
+
ON CONFLICT(user_id)
|
|
46
|
+
DO UPDATE SET output_format = excluded.output_format,
|
|
47
|
+
updated_at = datetime('now')
|
|
48
|
+
`).run(userId, format);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get full preference record for a user
|
|
52
|
+
*/
|
|
53
|
+
findByUserId(userId) {
|
|
54
|
+
const row = this.db.prepare('SELECT * FROM user_preferences WHERE user_id = ?').get(userId);
|
|
55
|
+
if (!row)
|
|
56
|
+
return undefined;
|
|
57
|
+
return this.mapRow(row);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Map a DB row to UserPreferenceRecord
|
|
61
|
+
*/
|
|
62
|
+
mapRow(row) {
|
|
63
|
+
return {
|
|
64
|
+
id: row.id,
|
|
65
|
+
userId: row.user_id,
|
|
66
|
+
outputFormat: row.output_format,
|
|
67
|
+
createdAt: row.created_at,
|
|
68
|
+
updatedAt: row.updated_at,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.UserPreferenceRepository = UserPreferenceRepository;
|
|
@@ -4,11 +4,14 @@ exports.createInteractionCreateHandler = createInteractionCreateHandler;
|
|
|
4
4
|
const discord_js_1 = require("discord.js");
|
|
5
5
|
const i18n_1 = require("../utils/i18n");
|
|
6
6
|
const logger_1 = require("../utils/logger");
|
|
7
|
+
const discordButtonUtils_1 = require("../utils/discordButtonUtils");
|
|
7
8
|
const templateUi_1 = require("../ui/templateUi");
|
|
8
9
|
const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
10
|
+
const outputUi_1 = require("../ui/outputUi");
|
|
9
11
|
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
10
|
-
const
|
|
12
|
+
const projectListUi_1 = require("../ui/projectListUi");
|
|
11
13
|
const modeService_1 = require("../services/modeService");
|
|
14
|
+
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
12
15
|
function createInteractionCreateHandler(deps) {
|
|
13
16
|
return async (interaction) => {
|
|
14
17
|
if (interaction.isButton()) {
|
|
@@ -26,9 +29,9 @@ function createInteractionCreateHandler(deps) {
|
|
|
26
29
|
}).catch(logger_1.logger.error);
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
|
-
const
|
|
30
|
-
const detector =
|
|
31
|
-
? deps.bridge.pool.getApprovalDetector(
|
|
32
|
+
const projectName = approvalAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
33
|
+
const detector = projectName
|
|
34
|
+
? deps.bridge.pool.getApprovalDetector(projectName)
|
|
32
35
|
: undefined;
|
|
33
36
|
if (!detector) {
|
|
34
37
|
try {
|
|
@@ -62,32 +65,9 @@ function createInteractionCreateHandler(deps) {
|
|
|
62
65
|
.setColor(approvalAction.action === 'deny' ? 0xE74C3C : 0x2ECC71)
|
|
63
66
|
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
64
67
|
.setTimestamp();
|
|
65
|
-
const disabledRows = interaction.message.components
|
|
66
|
-
.map((row) => {
|
|
67
|
-
const rowAny = row;
|
|
68
|
-
if (!Array.isArray(rowAny.components))
|
|
69
|
-
return null;
|
|
70
|
-
const nextRow = new discord_js_1.ActionRowBuilder();
|
|
71
|
-
const disabledButtons = rowAny.components
|
|
72
|
-
.map((component) => {
|
|
73
|
-
const componentType = component?.type ?? component?.data?.type;
|
|
74
|
-
if (componentType !== 2)
|
|
75
|
-
return null;
|
|
76
|
-
const payload = typeof component?.toJSON === 'function'
|
|
77
|
-
? component.toJSON()
|
|
78
|
-
: component;
|
|
79
|
-
return discord_js_1.ButtonBuilder.from(payload).setDisabled(true);
|
|
80
|
-
})
|
|
81
|
-
.filter((button) => button !== null);
|
|
82
|
-
if (disabledButtons.length === 0)
|
|
83
|
-
return null;
|
|
84
|
-
nextRow.addComponents(...disabledButtons);
|
|
85
|
-
return nextRow;
|
|
86
|
-
})
|
|
87
|
-
.filter((row) => row !== null);
|
|
88
68
|
await interaction.update({
|
|
89
69
|
embeds: [updatedEmbed],
|
|
90
|
-
components:
|
|
70
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
91
71
|
});
|
|
92
72
|
}
|
|
93
73
|
else {
|
|
@@ -110,6 +90,282 @@ function createInteractionCreateHandler(deps) {
|
|
|
110
90
|
}
|
|
111
91
|
return;
|
|
112
92
|
}
|
|
93
|
+
const planningAction = deps.parsePlanningCustomId(interaction.customId);
|
|
94
|
+
if (planningAction) {
|
|
95
|
+
if (planningAction.channelId && planningAction.channelId !== interaction.channelId) {
|
|
96
|
+
await interaction.reply({
|
|
97
|
+
content: (0, i18n_1.t)('This planning action is linked to a different session channel.'),
|
|
98
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
99
|
+
}).catch(logger_1.logger.error);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const planWorkspaceDirName = planningAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
103
|
+
const planDetector = planWorkspaceDirName
|
|
104
|
+
? deps.bridge.pool.getPlanningDetector(planWorkspaceDirName)
|
|
105
|
+
: undefined;
|
|
106
|
+
if (!planDetector) {
|
|
107
|
+
try {
|
|
108
|
+
await interaction.reply({ content: (0, i18n_1.t)('Planning detector not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
109
|
+
}
|
|
110
|
+
catch { /* ignore */ }
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
if (planningAction.action === 'open') {
|
|
115
|
+
await interaction.deferUpdate();
|
|
116
|
+
const clicked = await planDetector.clickOpenButton();
|
|
117
|
+
if (!clicked) {
|
|
118
|
+
await interaction.followUp({ content: (0, i18n_1.t)('Open button not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Wait for DOM to update after Open click
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
123
|
+
// Extract plan content with retry
|
|
124
|
+
let planContent = null;
|
|
125
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
126
|
+
planContent = await planDetector.extractPlanContent();
|
|
127
|
+
if (planContent)
|
|
128
|
+
break;
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
130
|
+
}
|
|
131
|
+
// Update original embed with action history
|
|
132
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
133
|
+
const updatedEmbed = originalEmbed
|
|
134
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
135
|
+
: new discord_js_1.EmbedBuilder().setTitle('Planning Mode');
|
|
136
|
+
const historyText = `Open by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
137
|
+
updatedEmbed
|
|
138
|
+
.setColor(0x3498DB)
|
|
139
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
140
|
+
.setTimestamp();
|
|
141
|
+
await interaction.editReply({
|
|
142
|
+
embeds: [updatedEmbed],
|
|
143
|
+
components: interaction.message.components,
|
|
144
|
+
});
|
|
145
|
+
// Send plan content as a new message in the same channel
|
|
146
|
+
if (planContent && interaction.channel && 'send' in interaction.channel) {
|
|
147
|
+
// Discord embed description limit is 4096 chars
|
|
148
|
+
const MAX_PLAN_CONTENT = 4096;
|
|
149
|
+
const truncated = planContent.length > MAX_PLAN_CONTENT
|
|
150
|
+
? planContent.substring(0, MAX_PLAN_CONTENT - 15) + '\n\n(truncated)'
|
|
151
|
+
: planContent;
|
|
152
|
+
const planEmbed = new discord_js_1.EmbedBuilder()
|
|
153
|
+
.setTitle((0, i18n_1.t)('Plan Content'))
|
|
154
|
+
.setDescription(truncated)
|
|
155
|
+
.setColor(0x3498DB)
|
|
156
|
+
.setTimestamp();
|
|
157
|
+
await interaction.channel.send({ embeds: [planEmbed] }).catch(logger_1.logger.error);
|
|
158
|
+
}
|
|
159
|
+
else if (!planContent) {
|
|
160
|
+
await interaction.followUp({
|
|
161
|
+
content: (0, i18n_1.t)('Could not extract plan content from the editor.'),
|
|
162
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
163
|
+
}).catch(logger_1.logger.error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Proceed action
|
|
168
|
+
const clicked = await planDetector.clickProceedButton();
|
|
169
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
170
|
+
const updatedEmbed = originalEmbed
|
|
171
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
172
|
+
: new discord_js_1.EmbedBuilder().setTitle('Planning Mode');
|
|
173
|
+
const historyText = `Proceed by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
174
|
+
updatedEmbed
|
|
175
|
+
.setColor(clicked ? 0x2ECC71 : 0xE74C3C)
|
|
176
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
177
|
+
.setTimestamp();
|
|
178
|
+
try {
|
|
179
|
+
await interaction.update({
|
|
180
|
+
embeds: [updatedEmbed],
|
|
181
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (interactionError) {
|
|
185
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
186
|
+
logger_1.logger.warn('[Planning] Interaction expired. Responding directly in the channel.');
|
|
187
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
188
|
+
const fallbackMessage = clicked
|
|
189
|
+
? (0, i18n_1.t)('Proceed completed. Implementation started.')
|
|
190
|
+
: (0, i18n_1.t)('Proceed button not found.');
|
|
191
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
throw interactionError;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (planError) {
|
|
201
|
+
if (planError?.code === 10062 || planError?.code === 40060) {
|
|
202
|
+
logger_1.logger.warn('[Planning] Interaction expired.');
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
logger_1.logger.error('[Planning] Error handling planning button:', planError);
|
|
206
|
+
try {
|
|
207
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
208
|
+
await interaction.reply({ content: (0, i18n_1.t)('An error occurred while processing the planning action.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
await interaction.followUp({ content: (0, i18n_1.t)('An error occurred while processing the planning action.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch { /* ignore */ }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const errorPopupAction = deps.parseErrorPopupCustomId(interaction.customId);
|
|
220
|
+
if (errorPopupAction) {
|
|
221
|
+
if (errorPopupAction.channelId && errorPopupAction.channelId !== interaction.channelId) {
|
|
222
|
+
await interaction.reply({
|
|
223
|
+
content: (0, i18n_1.t)('This error popup action is linked to a different session channel.'),
|
|
224
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
225
|
+
}).catch(logger_1.logger.error);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const errorWorkspaceDirName = errorPopupAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
229
|
+
const errorDetector = errorWorkspaceDirName
|
|
230
|
+
? deps.bridge.pool.getErrorPopupDetector(errorWorkspaceDirName)
|
|
231
|
+
: undefined;
|
|
232
|
+
if (!errorDetector) {
|
|
233
|
+
try {
|
|
234
|
+
await interaction.reply({ content: (0, i18n_1.t)('Error popup detector not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
235
|
+
}
|
|
236
|
+
catch { /* ignore */ }
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
if (errorPopupAction.action === 'dismiss') {
|
|
241
|
+
const clicked = await errorDetector.clickDismissButton();
|
|
242
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
243
|
+
const updatedEmbed = originalEmbed
|
|
244
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
245
|
+
: new discord_js_1.EmbedBuilder().setTitle('Agent Error');
|
|
246
|
+
const historyText = `Dismiss by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
247
|
+
updatedEmbed
|
|
248
|
+
.setColor(clicked ? 0x95A5A6 : 0xE74C3C)
|
|
249
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
250
|
+
.setTimestamp();
|
|
251
|
+
try {
|
|
252
|
+
await interaction.update({
|
|
253
|
+
embeds: [updatedEmbed],
|
|
254
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (interactionError) {
|
|
258
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
259
|
+
logger_1.logger.warn('[ErrorPopup] Interaction expired. Responding directly in the channel.');
|
|
260
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
261
|
+
const fallbackMessage = clicked
|
|
262
|
+
? (0, i18n_1.t)('Error popup dismissed.')
|
|
263
|
+
: (0, i18n_1.t)('Dismiss button not found.');
|
|
264
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw interactionError;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (errorPopupAction.action === 'copy_debug') {
|
|
273
|
+
await interaction.deferUpdate();
|
|
274
|
+
const clicked = await errorDetector.clickCopyDebugInfoButton();
|
|
275
|
+
if (!clicked) {
|
|
276
|
+
await interaction.followUp({ content: (0, i18n_1.t)('Copy debug info button not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// Wait for clipboard to be populated
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
281
|
+
const clipboardContent = await errorDetector.readClipboard();
|
|
282
|
+
// Update original embed with action history
|
|
283
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
284
|
+
const updatedEmbed = originalEmbed
|
|
285
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
286
|
+
: new discord_js_1.EmbedBuilder().setTitle('Agent Error');
|
|
287
|
+
const historyText = `Copy debug info by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
288
|
+
updatedEmbed
|
|
289
|
+
.setColor(0x3498DB)
|
|
290
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
291
|
+
.setTimestamp();
|
|
292
|
+
await interaction.editReply({
|
|
293
|
+
embeds: [updatedEmbed],
|
|
294
|
+
components: interaction.message.components,
|
|
295
|
+
});
|
|
296
|
+
// Send debug info as a new message
|
|
297
|
+
if (clipboardContent && interaction.channel && 'send' in interaction.channel) {
|
|
298
|
+
const MAX_DEBUG_CONTENT = 4096;
|
|
299
|
+
const truncated = clipboardContent.length > MAX_DEBUG_CONTENT
|
|
300
|
+
? clipboardContent.substring(0, MAX_DEBUG_CONTENT - 15) + '\n\n(truncated)'
|
|
301
|
+
: clipboardContent;
|
|
302
|
+
const debugEmbed = new discord_js_1.EmbedBuilder()
|
|
303
|
+
.setTitle((0, i18n_1.t)('Debug Info'))
|
|
304
|
+
.setDescription(`\`\`\`\n${truncated}\n\`\`\``)
|
|
305
|
+
.setColor(0x3498DB)
|
|
306
|
+
.setTimestamp();
|
|
307
|
+
await interaction.channel.send({ embeds: [debugEmbed] }).catch(logger_1.logger.error);
|
|
308
|
+
}
|
|
309
|
+
else if (!clipboardContent) {
|
|
310
|
+
await interaction.followUp({
|
|
311
|
+
content: (0, i18n_1.t)('Could not read debug info from clipboard.'),
|
|
312
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
313
|
+
}).catch(logger_1.logger.error);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// Retry action
|
|
318
|
+
const clicked = await errorDetector.clickRetryButton();
|
|
319
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
320
|
+
const updatedEmbed = originalEmbed
|
|
321
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
322
|
+
: new discord_js_1.EmbedBuilder().setTitle('Agent Error');
|
|
323
|
+
const historyText = `Retry by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
324
|
+
updatedEmbed
|
|
325
|
+
.setColor(clicked ? 0x2ECC71 : 0xE74C3C)
|
|
326
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
327
|
+
.setTimestamp();
|
|
328
|
+
try {
|
|
329
|
+
await interaction.update({
|
|
330
|
+
embeds: [updatedEmbed],
|
|
331
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (interactionError) {
|
|
335
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
336
|
+
logger_1.logger.warn('[ErrorPopup] Interaction expired. Responding directly in the channel.');
|
|
337
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
338
|
+
const fallbackMessage = clicked
|
|
339
|
+
? (0, i18n_1.t)('Retry initiated.')
|
|
340
|
+
: (0, i18n_1.t)('Retry button not found.');
|
|
341
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
throw interactionError;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (errorPopupError) {
|
|
351
|
+
if (errorPopupError?.code === 10062 || errorPopupError?.code === 40060) {
|
|
352
|
+
logger_1.logger.warn('[ErrorPopup] Interaction expired.');
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
logger_1.logger.error('[ErrorPopup] Error handling error popup button:', errorPopupError);
|
|
356
|
+
try {
|
|
357
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
358
|
+
await interaction.reply({ content: (0, i18n_1.t)('An error occurred while processing the error popup action.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
await interaction.followUp({ content: (0, i18n_1.t)('An error occurred while processing the error popup action.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch { /* ignore */ }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
113
369
|
if (interaction.customId === cleanupCommandHandler_1.CLEANUP_ARCHIVE_BTN) {
|
|
114
370
|
await deps.cleanupHandler.handleArchive(interaction);
|
|
115
371
|
return;
|
|
@@ -167,6 +423,27 @@ function createInteractionCreateHandler(deps) {
|
|
|
167
423
|
});
|
|
168
424
|
return;
|
|
169
425
|
}
|
|
426
|
+
if (interaction.customId === outputUi_1.OUTPUT_BTN_EMBED || interaction.customId === outputUi_1.OUTPUT_BTN_PLAIN) {
|
|
427
|
+
if (deps.userPrefRepo) {
|
|
428
|
+
await interaction.deferUpdate();
|
|
429
|
+
const format = interaction.customId === outputUi_1.OUTPUT_BTN_PLAIN ? 'plain' : 'embed';
|
|
430
|
+
deps.userPrefRepo.setOutputFormat(interaction.user.id, format);
|
|
431
|
+
await (0, outputUi_1.sendOutputUI)({ editReply: async (data) => await interaction.editReply(data) }, format);
|
|
432
|
+
const label = format === 'plain' ? 'Plain Text' : 'Embed';
|
|
433
|
+
await interaction.followUp({
|
|
434
|
+
content: `Output format changed to **${label}**.`,
|
|
435
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (interaction.customId.startsWith(`${projectListUi_1.PROJECT_PAGE_PREFIX}:`)) {
|
|
441
|
+
const page = (0, projectListUi_1.parseProjectPageId)(interaction.customId);
|
|
442
|
+
if (!isNaN(page) && page >= 0) {
|
|
443
|
+
await deps.wsHandler.handlePageButton(interaction, page);
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
170
447
|
if (interaction.customId.startsWith(templateUi_1.TEMPLATE_BTN_PREFIX)) {
|
|
171
448
|
await interaction.deferUpdate();
|
|
172
449
|
const templateId = (0, templateUi_1.parseTemplateButtonId)(interaction.customId);
|
|
@@ -233,7 +510,33 @@ function createInteractionCreateHandler(deps) {
|
|
|
233
510
|
}
|
|
234
511
|
return;
|
|
235
512
|
}
|
|
236
|
-
if (interaction.isStringSelectMenu() && (
|
|
513
|
+
if (interaction.isStringSelectMenu() && (0, sessionPickerUi_1.isSessionSelectId)(interaction.customId)) {
|
|
514
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
515
|
+
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
await interaction.deferUpdate();
|
|
520
|
+
}
|
|
521
|
+
catch (deferError) {
|
|
522
|
+
if (deferError?.code === 10062 || deferError?.code === 40060) {
|
|
523
|
+
logger_1.logger.warn('[SessionSelect] deferUpdate expired. Skipping.');
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
logger_1.logger.error('[SessionSelect] deferUpdate failed:', deferError);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
if (deps.joinHandler) {
|
|
531
|
+
await deps.joinHandler.handleJoinSelect(interaction, deps.bridge);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
logger_1.logger.error('Session selection error:', error);
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (interaction.isStringSelectMenu() && (0, projectListUi_1.isProjectSelectId)(interaction.customId)) {
|
|
237
540
|
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
238
541
|
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
239
542
|
return;
|
|
@@ -261,7 +564,12 @@ function createInteractionCreateHandler(deps) {
|
|
|
261
564
|
return;
|
|
262
565
|
}
|
|
263
566
|
try {
|
|
264
|
-
|
|
567
|
+
if (commandInteraction.commandName === 'logs') {
|
|
568
|
+
await commandInteraction.deferReply({ flags: discord_js_1.MessageFlags.Ephemeral });
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
await commandInteraction.deferReply();
|
|
572
|
+
}
|
|
265
573
|
}
|
|
266
574
|
catch (deferError) {
|
|
267
575
|
if (deferError?.code === 10062) {
|