lazy-gravity 0.1.0 → 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 +18 -6
- package/dist/bin/cli.js +18 -18
- package/dist/bin/commands/doctor.js +2 -1
- package/dist/bin/commands/start.js +25 -2
- package/dist/bot/index.js +346 -152
- package/dist/commands/joinCommandHandler.js +302 -0
- package/dist/commands/joinDetachCommandHandler.js +285 -0
- package/dist/commands/registerSlashCommands.js +35 -0
- package/dist/database/chatSessionRepository.js +10 -0
- package/dist/database/userPreferenceRepository.js +72 -0
- package/dist/events/interactionCreateHandler.js +58 -36
- package/dist/events/messageCreateHandler.js +158 -53
- package/dist/services/antigravityLauncher.js +4 -3
- package/dist/services/approvalDetector.js +6 -0
- package/dist/services/cdpBridgeManager.js +184 -84
- package/dist/services/cdpConnectionPool.js +79 -51
- package/dist/services/cdpService.js +149 -51
- package/dist/services/chatSessionService.js +229 -8
- package/dist/services/errorPopupDetector.js +6 -0
- package/dist/services/planningDetector.js +6 -0
- package/dist/services/responseMonitor.js +125 -24
- package/dist/services/updateCheckService.js +147 -0
- package/dist/services/userMessageDetector.js +221 -0
- package/dist/ui/modeUi.js +11 -1
- package/dist/ui/outputUi.js +30 -0
- package/dist/ui/sessionPickerUi.js +48 -0
- package/dist/utils/antigravityPaths.js +94 -0
- package/dist/utils/configLoader.js +10 -0
- package/dist/utils/discordButtonUtils.js +33 -0
- package/dist/utils/logBuffer.js +47 -0
- package/dist/utils/logger.js +80 -20
- package/dist/utils/pathUtils.js +57 -0
- package/dist/utils/plainTextFormatter.js +70 -0
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCurrentChatTitle = getCurrentChatTitle;
|
|
3
4
|
exports.registerApprovalWorkspaceChannel = registerApprovalWorkspaceChannel;
|
|
4
5
|
exports.registerApprovalSessionChannel = registerApprovalSessionChannel;
|
|
5
6
|
exports.resolveApprovalChannelForCurrentChat = resolveApprovalChannelForCurrentChat;
|
|
@@ -14,15 +15,18 @@ exports.getCurrentCdp = getCurrentCdp;
|
|
|
14
15
|
exports.ensureApprovalDetector = ensureApprovalDetector;
|
|
15
16
|
exports.ensurePlanningDetector = ensurePlanningDetector;
|
|
16
17
|
exports.ensureErrorPopupDetector = ensureErrorPopupDetector;
|
|
18
|
+
exports.ensureUserMessageDetector = ensureUserMessageDetector;
|
|
17
19
|
const discord_js_1 = require("discord.js");
|
|
18
20
|
const i18n_1 = require("../utils/i18n");
|
|
19
21
|
const logger_1 = require("../utils/logger");
|
|
22
|
+
const discordButtonUtils_1 = require("../utils/discordButtonUtils");
|
|
20
23
|
const approvalDetector_1 = require("./approvalDetector");
|
|
21
24
|
const autoAcceptService_1 = require("./autoAcceptService");
|
|
22
25
|
const cdpConnectionPool_1 = require("./cdpConnectionPool");
|
|
23
26
|
const errorPopupDetector_1 = require("./errorPopupDetector");
|
|
24
27
|
const planningDetector_1 = require("./planningDetector");
|
|
25
28
|
const quotaService_1 = require("./quotaService");
|
|
29
|
+
const userMessageDetector_1 = require("./userMessageDetector");
|
|
26
30
|
const APPROVE_ACTION_PREFIX = 'approve_action';
|
|
27
31
|
const ALWAYS_ALLOW_ACTION_PREFIX = 'always_allow_action';
|
|
28
32
|
const DENY_ACTION_PREFIX = 'deny_action';
|
|
@@ -34,8 +38,8 @@ const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
|
34
38
|
function normalizeSessionTitle(title) {
|
|
35
39
|
return title.trim().toLowerCase();
|
|
36
40
|
}
|
|
37
|
-
function buildSessionRouteKey(
|
|
38
|
-
return `${
|
|
41
|
+
function buildSessionRouteKey(projectName, sessionTitle) {
|
|
42
|
+
return `${projectName}::${normalizeSessionTitle(sessionTitle)}`;
|
|
39
43
|
}
|
|
40
44
|
const GET_CURRENT_CHAT_TITLE_SCRIPT = `(() => {
|
|
41
45
|
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
@@ -67,127 +71,127 @@ async function getCurrentChatTitle(cdp) {
|
|
|
67
71
|
}
|
|
68
72
|
return null;
|
|
69
73
|
}
|
|
70
|
-
function registerApprovalWorkspaceChannel(bridge,
|
|
71
|
-
bridge.approvalChannelByWorkspace.set(
|
|
74
|
+
function registerApprovalWorkspaceChannel(bridge, projectName, channel) {
|
|
75
|
+
bridge.approvalChannelByWorkspace.set(projectName, channel);
|
|
72
76
|
}
|
|
73
|
-
function registerApprovalSessionChannel(bridge,
|
|
77
|
+
function registerApprovalSessionChannel(bridge, projectName, sessionTitle, channel) {
|
|
74
78
|
if (!sessionTitle || sessionTitle.trim().length === 0)
|
|
75
79
|
return;
|
|
76
|
-
bridge.approvalChannelBySession.set(buildSessionRouteKey(
|
|
77
|
-
bridge.approvalChannelByWorkspace.set(
|
|
80
|
+
bridge.approvalChannelBySession.set(buildSessionRouteKey(projectName, sessionTitle), channel);
|
|
81
|
+
bridge.approvalChannelByWorkspace.set(projectName, channel);
|
|
78
82
|
}
|
|
79
|
-
function resolveApprovalChannelForCurrentChat(bridge,
|
|
83
|
+
function resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle) {
|
|
80
84
|
// Try session-level match first (most precise routing)
|
|
81
85
|
if (currentChatTitle && currentChatTitle.trim().length > 0) {
|
|
82
|
-
const key = buildSessionRouteKey(
|
|
86
|
+
const key = buildSessionRouteKey(projectName, currentChatTitle);
|
|
83
87
|
const sessionChannel = bridge.approvalChannelBySession.get(key);
|
|
84
88
|
if (sessionChannel)
|
|
85
89
|
return sessionChannel;
|
|
86
90
|
}
|
|
87
91
|
// Fall back to workspace-level routing
|
|
88
|
-
return bridge.approvalChannelByWorkspace.get(
|
|
92
|
+
return bridge.approvalChannelByWorkspace.get(projectName) ?? null;
|
|
89
93
|
}
|
|
90
|
-
function buildApprovalCustomId(action,
|
|
94
|
+
function buildApprovalCustomId(action, projectName, channelId) {
|
|
91
95
|
const prefix = action === 'approve'
|
|
92
96
|
? APPROVE_ACTION_PREFIX
|
|
93
97
|
: action === 'always_allow'
|
|
94
98
|
? ALWAYS_ALLOW_ACTION_PREFIX
|
|
95
99
|
: DENY_ACTION_PREFIX;
|
|
96
100
|
if (channelId && channelId.trim().length > 0) {
|
|
97
|
-
return `${prefix}:${
|
|
101
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
98
102
|
}
|
|
99
|
-
return `${prefix}:${
|
|
103
|
+
return `${prefix}:${projectName}`;
|
|
100
104
|
}
|
|
101
105
|
function parseApprovalCustomId(customId) {
|
|
102
106
|
if (customId === APPROVE_ACTION_PREFIX) {
|
|
103
|
-
return { action: 'approve',
|
|
107
|
+
return { action: 'approve', projectName: null, channelId: null };
|
|
104
108
|
}
|
|
105
109
|
if (customId === ALWAYS_ALLOW_ACTION_PREFIX) {
|
|
106
|
-
return { action: 'always_allow',
|
|
110
|
+
return { action: 'always_allow', projectName: null, channelId: null };
|
|
107
111
|
}
|
|
108
112
|
if (customId === DENY_ACTION_PREFIX) {
|
|
109
|
-
return { action: 'deny',
|
|
113
|
+
return { action: 'deny', projectName: null, channelId: null };
|
|
110
114
|
}
|
|
111
115
|
if (customId.startsWith(`${APPROVE_ACTION_PREFIX}:`)) {
|
|
112
116
|
const rest = customId.substring(`${APPROVE_ACTION_PREFIX}:`.length);
|
|
113
|
-
const [
|
|
114
|
-
return { action: 'approve',
|
|
117
|
+
const [projectName, channelId] = rest.split(':');
|
|
118
|
+
return { action: 'approve', projectName: projectName || null, channelId: channelId || null };
|
|
115
119
|
}
|
|
116
120
|
if (customId.startsWith(`${ALWAYS_ALLOW_ACTION_PREFIX}:`)) {
|
|
117
121
|
const rest = customId.substring(`${ALWAYS_ALLOW_ACTION_PREFIX}:`.length);
|
|
118
|
-
const [
|
|
119
|
-
return { action: 'always_allow',
|
|
122
|
+
const [projectName, channelId] = rest.split(':');
|
|
123
|
+
return { action: 'always_allow', projectName: projectName || null, channelId: channelId || null };
|
|
120
124
|
}
|
|
121
125
|
if (customId.startsWith(`${DENY_ACTION_PREFIX}:`)) {
|
|
122
126
|
const rest = customId.substring(`${DENY_ACTION_PREFIX}:`.length);
|
|
123
|
-
const [
|
|
124
|
-
return { action: 'deny',
|
|
127
|
+
const [projectName, channelId] = rest.split(':');
|
|
128
|
+
return { action: 'deny', projectName: projectName || null, channelId: channelId || null };
|
|
125
129
|
}
|
|
126
130
|
return null;
|
|
127
131
|
}
|
|
128
|
-
function buildPlanningCustomId(action,
|
|
132
|
+
function buildPlanningCustomId(action, projectName, channelId) {
|
|
129
133
|
const prefix = action === 'open'
|
|
130
134
|
? PLANNING_OPEN_ACTION_PREFIX
|
|
131
135
|
: PLANNING_PROCEED_ACTION_PREFIX;
|
|
132
136
|
if (channelId && channelId.trim().length > 0) {
|
|
133
|
-
return `${prefix}:${
|
|
137
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
134
138
|
}
|
|
135
|
-
return `${prefix}:${
|
|
139
|
+
return `${prefix}:${projectName}`;
|
|
136
140
|
}
|
|
137
141
|
function parsePlanningCustomId(customId) {
|
|
138
142
|
if (customId === PLANNING_OPEN_ACTION_PREFIX) {
|
|
139
|
-
return { action: 'open',
|
|
143
|
+
return { action: 'open', projectName: null, channelId: null };
|
|
140
144
|
}
|
|
141
145
|
if (customId === PLANNING_PROCEED_ACTION_PREFIX) {
|
|
142
|
-
return { action: 'proceed',
|
|
146
|
+
return { action: 'proceed', projectName: null, channelId: null };
|
|
143
147
|
}
|
|
144
148
|
if (customId.startsWith(`${PLANNING_OPEN_ACTION_PREFIX}:`)) {
|
|
145
149
|
const rest = customId.substring(`${PLANNING_OPEN_ACTION_PREFIX}:`.length);
|
|
146
|
-
const [
|
|
147
|
-
return { action: 'open',
|
|
150
|
+
const [projectName, channelId] = rest.split(':');
|
|
151
|
+
return { action: 'open', projectName: projectName || null, channelId: channelId || null };
|
|
148
152
|
}
|
|
149
153
|
if (customId.startsWith(`${PLANNING_PROCEED_ACTION_PREFIX}:`)) {
|
|
150
154
|
const rest = customId.substring(`${PLANNING_PROCEED_ACTION_PREFIX}:`.length);
|
|
151
|
-
const [
|
|
152
|
-
return { action: 'proceed',
|
|
155
|
+
const [projectName, channelId] = rest.split(':');
|
|
156
|
+
return { action: 'proceed', projectName: projectName || null, channelId: channelId || null };
|
|
153
157
|
}
|
|
154
158
|
return null;
|
|
155
159
|
}
|
|
156
|
-
function buildErrorPopupCustomId(action,
|
|
160
|
+
function buildErrorPopupCustomId(action, projectName, channelId) {
|
|
157
161
|
const prefix = action === 'dismiss'
|
|
158
162
|
? ERROR_POPUP_DISMISS_ACTION_PREFIX
|
|
159
163
|
: action === 'copy_debug'
|
|
160
164
|
? ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX
|
|
161
165
|
: ERROR_POPUP_RETRY_ACTION_PREFIX;
|
|
162
166
|
if (channelId && channelId.trim().length > 0) {
|
|
163
|
-
return `${prefix}:${
|
|
167
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
164
168
|
}
|
|
165
|
-
return `${prefix}:${
|
|
169
|
+
return `${prefix}:${projectName}`;
|
|
166
170
|
}
|
|
167
171
|
function parseErrorPopupCustomId(customId) {
|
|
168
172
|
if (customId === ERROR_POPUP_DISMISS_ACTION_PREFIX) {
|
|
169
|
-
return { action: 'dismiss',
|
|
173
|
+
return { action: 'dismiss', projectName: null, channelId: null };
|
|
170
174
|
}
|
|
171
175
|
if (customId === ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX) {
|
|
172
|
-
return { action: 'copy_debug',
|
|
176
|
+
return { action: 'copy_debug', projectName: null, channelId: null };
|
|
173
177
|
}
|
|
174
178
|
if (customId === ERROR_POPUP_RETRY_ACTION_PREFIX) {
|
|
175
|
-
return { action: 'retry',
|
|
179
|
+
return { action: 'retry', projectName: null, channelId: null };
|
|
176
180
|
}
|
|
177
181
|
if (customId.startsWith(`${ERROR_POPUP_DISMISS_ACTION_PREFIX}:`)) {
|
|
178
182
|
const rest = customId.substring(`${ERROR_POPUP_DISMISS_ACTION_PREFIX}:`.length);
|
|
179
|
-
const [
|
|
180
|
-
return { action: 'dismiss',
|
|
183
|
+
const [projectName, channelId] = rest.split(':');
|
|
184
|
+
return { action: 'dismiss', projectName: projectName || null, channelId: channelId || null };
|
|
181
185
|
}
|
|
182
186
|
if (customId.startsWith(`${ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX}:`)) {
|
|
183
187
|
const rest = customId.substring(`${ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX}:`.length);
|
|
184
|
-
const [
|
|
185
|
-
return { action: 'copy_debug',
|
|
188
|
+
const [projectName, channelId] = rest.split(':');
|
|
189
|
+
return { action: 'copy_debug', projectName: projectName || null, channelId: channelId || null };
|
|
186
190
|
}
|
|
187
191
|
if (customId.startsWith(`${ERROR_POPUP_RETRY_ACTION_PREFIX}:`)) {
|
|
188
192
|
const rest = customId.substring(`${ERROR_POPUP_RETRY_ACTION_PREFIX}:`.length);
|
|
189
|
-
const [
|
|
190
|
-
return { action: 'retry',
|
|
193
|
+
const [projectName, channelId] = rest.split(':');
|
|
194
|
+
return { action: 'retry', projectName: projectName || null, channelId: channelId || null };
|
|
191
195
|
}
|
|
192
196
|
return null;
|
|
193
197
|
}
|
|
@@ -226,20 +230,42 @@ function getCurrentCdp(bridge) {
|
|
|
226
230
|
* Helper to start an approval detector for each workspace.
|
|
227
231
|
* Does nothing if a detector for the same workspace is already running.
|
|
228
232
|
*/
|
|
229
|
-
function ensureApprovalDetector(bridge, cdp,
|
|
230
|
-
const existing = bridge.pool.getApprovalDetector(
|
|
233
|
+
function ensureApprovalDetector(bridge, cdp, projectName, client) {
|
|
234
|
+
const existing = bridge.pool.getApprovalDetector(projectName);
|
|
231
235
|
if (existing && existing.isActive())
|
|
232
236
|
return;
|
|
237
|
+
// Track the most recent button message for auto-disable on resolve.
|
|
238
|
+
// Only the latest message is tracked; if a new detection fires before the previous
|
|
239
|
+
// is resolved, the older message reference is overwritten. This is acceptable because
|
|
240
|
+
// the detector's lastDetectedKey deduplication prevents rapid successive notifications.
|
|
241
|
+
let lastButtonMessage = null;
|
|
233
242
|
const detector = new approvalDetector_1.ApprovalDetector({
|
|
234
243
|
cdpService: cdp,
|
|
235
244
|
pollIntervalMs: 2000,
|
|
245
|
+
onResolved: () => {
|
|
246
|
+
if (!lastButtonMessage)
|
|
247
|
+
return;
|
|
248
|
+
const msg = lastButtonMessage;
|
|
249
|
+
lastButtonMessage = null;
|
|
250
|
+
const originalEmbed = msg.embeds[0];
|
|
251
|
+
const updatedEmbed = originalEmbed
|
|
252
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
253
|
+
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Approval Required'));
|
|
254
|
+
updatedEmbed
|
|
255
|
+
.setColor(0x95A5A6)
|
|
256
|
+
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
257
|
+
msg.edit({
|
|
258
|
+
embeds: [updatedEmbed],
|
|
259
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
260
|
+
}).catch(logger_1.logger.error);
|
|
261
|
+
},
|
|
236
262
|
onApprovalRequired: async (info) => {
|
|
237
|
-
logger_1.logger.
|
|
263
|
+
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Approval button detected (allow="${info.approveText}", deny="${info.denyText}")`);
|
|
238
264
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
239
|
-
const targetChannel = resolveApprovalChannelForCurrentChat(bridge,
|
|
265
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
240
266
|
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
241
267
|
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
242
|
-
logger_1.logger.warn(`[ApprovalDetector:${
|
|
268
|
+
logger_1.logger.warn(`[ApprovalDetector:${projectName}] Skipped approval notification because chat is not linked to a Discord session` +
|
|
243
269
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
244
270
|
return;
|
|
245
271
|
}
|
|
@@ -247,10 +273,16 @@ function ensureApprovalDetector(bridge, cdp, workspaceDirName, client) {
|
|
|
247
273
|
const accepted = await detector.alwaysAllowButton() || await detector.approveButton();
|
|
248
274
|
const autoEmbed = new discord_js_1.EmbedBuilder()
|
|
249
275
|
.setTitle(accepted ? (0, i18n_1.t)('Auto-approved') : (0, i18n_1.t)('Auto-approve failed'))
|
|
250
|
-
.setDescription(
|
|
276
|
+
.setDescription(accepted ? (0, i18n_1.t)('An action was automatically approved.') : (0, i18n_1.t)('Auto-approve attempted but failed. Manual approval required.'))
|
|
251
277
|
.setColor(accepted ? 0x2ECC71 : 0xF39C12)
|
|
252
|
-
.addFields({ name: (0, i18n_1.t)('Auto-approve mode'), value: (0, i18n_1.t)('ON'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value:
|
|
253
|
-
|
|
278
|
+
.addFields({ name: (0, i18n_1.t)('Auto-approve mode'), value: (0, i18n_1.t)('ON'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true }, { name: (0, i18n_1.t)('Result'), value: accepted ? (0, i18n_1.t)('Executed Always Allow/Allow') : (0, i18n_1.t)('Manual approval required'), inline: true });
|
|
279
|
+
if (info.description) {
|
|
280
|
+
autoEmbed.addFields({ name: (0, i18n_1.t)('Action Detail'), value: info.description.substring(0, 1024), inline: false });
|
|
281
|
+
}
|
|
282
|
+
if (info.approveText) {
|
|
283
|
+
autoEmbed.addFields({ name: (0, i18n_1.t)('Approved via'), value: info.approveText, inline: true });
|
|
284
|
+
}
|
|
285
|
+
autoEmbed.setTimestamp();
|
|
254
286
|
await targetChannel.send({ embeds: [autoEmbed] }).catch(logger_1.logger.error);
|
|
255
287
|
if (accepted) {
|
|
256
288
|
return;
|
|
@@ -260,49 +292,72 @@ function ensureApprovalDetector(bridge, cdp, workspaceDirName, client) {
|
|
|
260
292
|
.setTitle((0, i18n_1.t)('Approval Required'))
|
|
261
293
|
.setDescription(info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'))
|
|
262
294
|
.setColor(0xFFA500)
|
|
263
|
-
.addFields({ name: (0, i18n_1.t)('Allow button'), value: info.approveText, inline: true }, { name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText || (0, i18n_1.t)('In Dropdown'), inline: true }, { name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value:
|
|
295
|
+
.addFields({ name: (0, i18n_1.t)('Allow button'), value: info.approveText, inline: true }, { name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText || (0, i18n_1.t)('In Dropdown'), inline: true }, { name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true })
|
|
264
296
|
.setTimestamp();
|
|
265
297
|
const approveBtn = new discord_js_1.ButtonBuilder()
|
|
266
|
-
.setCustomId(buildApprovalCustomId('approve',
|
|
298
|
+
.setCustomId(buildApprovalCustomId('approve', projectName, targetChannelId))
|
|
267
299
|
.setLabel((0, i18n_1.t)('Allow'))
|
|
268
300
|
.setStyle(discord_js_1.ButtonStyle.Success);
|
|
269
301
|
const alwaysAllowBtn = new discord_js_1.ButtonBuilder()
|
|
270
|
-
.setCustomId(buildApprovalCustomId('always_allow',
|
|
302
|
+
.setCustomId(buildApprovalCustomId('always_allow', projectName, targetChannelId))
|
|
271
303
|
.setLabel((0, i18n_1.t)('Allow Chat'))
|
|
272
304
|
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
273
305
|
const denyBtn = new discord_js_1.ButtonBuilder()
|
|
274
|
-
.setCustomId(buildApprovalCustomId('deny',
|
|
306
|
+
.setCustomId(buildApprovalCustomId('deny', projectName, targetChannelId))
|
|
275
307
|
.setLabel((0, i18n_1.t)('Deny'))
|
|
276
308
|
.setStyle(discord_js_1.ButtonStyle.Danger);
|
|
277
309
|
const row = new discord_js_1.ActionRowBuilder().addComponents(approveBtn, alwaysAllowBtn, denyBtn);
|
|
278
|
-
targetChannel.send({
|
|
310
|
+
const sent = await targetChannel.send({
|
|
279
311
|
embeds: [embed],
|
|
280
312
|
components: [row],
|
|
281
|
-
}).catch(logger_1.logger.error);
|
|
313
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
314
|
+
if (sent) {
|
|
315
|
+
lastButtonMessage = sent;
|
|
316
|
+
}
|
|
282
317
|
},
|
|
283
318
|
});
|
|
284
319
|
detector.start();
|
|
285
|
-
bridge.pool.registerApprovalDetector(
|
|
286
|
-
logger_1.logger.
|
|
320
|
+
bridge.pool.registerApprovalDetector(projectName, detector);
|
|
321
|
+
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Started approval button detection`);
|
|
287
322
|
}
|
|
288
323
|
/**
|
|
289
324
|
* Helper to start a planning detector for each workspace.
|
|
290
325
|
* Does nothing if a detector for the same workspace is already running.
|
|
291
326
|
*/
|
|
292
|
-
function ensurePlanningDetector(bridge, cdp,
|
|
293
|
-
const existing = bridge.pool.getPlanningDetector(
|
|
327
|
+
function ensurePlanningDetector(bridge, cdp, projectName, _client) {
|
|
328
|
+
const existing = bridge.pool.getPlanningDetector(projectName);
|
|
294
329
|
if (existing && existing.isActive())
|
|
295
330
|
return;
|
|
331
|
+
// Track the most recent planning message for auto-disable on resolve.
|
|
332
|
+
// See ensureApprovalDetector comment for tracking limitation rationale.
|
|
333
|
+
let lastPlanningMessage = null;
|
|
296
334
|
const detector = new planningDetector_1.PlanningDetector({
|
|
297
335
|
cdpService: cdp,
|
|
298
336
|
pollIntervalMs: 2000,
|
|
337
|
+
onResolved: () => {
|
|
338
|
+
if (!lastPlanningMessage)
|
|
339
|
+
return;
|
|
340
|
+
const msg = lastPlanningMessage;
|
|
341
|
+
lastPlanningMessage = null;
|
|
342
|
+
const originalEmbed = msg.embeds[0];
|
|
343
|
+
const updatedEmbed = originalEmbed
|
|
344
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
345
|
+
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Planning Mode'));
|
|
346
|
+
updatedEmbed
|
|
347
|
+
.setColor(0x95A5A6)
|
|
348
|
+
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
349
|
+
msg.edit({
|
|
350
|
+
embeds: [updatedEmbed],
|
|
351
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
352
|
+
}).catch(logger_1.logger.error);
|
|
353
|
+
},
|
|
299
354
|
onPlanningRequired: async (info) => {
|
|
300
|
-
logger_1.logger.
|
|
355
|
+
logger_1.logger.debug(`[PlanningDetector:${projectName}] Planning buttons detected (title="${info.planTitle}")`);
|
|
301
356
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
302
|
-
const targetChannel = resolveApprovalChannelForCurrentChat(bridge,
|
|
357
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
303
358
|
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
304
359
|
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
305
|
-
logger_1.logger.warn(`[PlanningDetector:${
|
|
360
|
+
logger_1.logger.warn(`[PlanningDetector:${projectName}] Skipped planning notification because chat is not linked to a Discord session` +
|
|
306
361
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
307
362
|
return;
|
|
308
363
|
}
|
|
@@ -311,48 +366,71 @@ function ensurePlanningDetector(bridge, cdp, workspaceDirName, _client) {
|
|
|
311
366
|
.setTitle((0, i18n_1.t)('Planning Mode'))
|
|
312
367
|
.setDescription(descriptionText)
|
|
313
368
|
.setColor(0x3498DB)
|
|
314
|
-
.addFields({ name: (0, i18n_1.t)('Plan'), value: info.planTitle || (0, i18n_1.t)('Implementation Plan'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value:
|
|
369
|
+
.addFields({ name: (0, i18n_1.t)('Plan'), value: info.planTitle || (0, i18n_1.t)('Implementation Plan'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true })
|
|
315
370
|
.setTimestamp();
|
|
316
371
|
if (info.planSummary && info.description) {
|
|
317
372
|
embed.addFields({ name: (0, i18n_1.t)('Summary'), value: info.planSummary.substring(0, 1024), inline: false });
|
|
318
373
|
}
|
|
319
374
|
const openBtn = new discord_js_1.ButtonBuilder()
|
|
320
|
-
.setCustomId(buildPlanningCustomId('open',
|
|
375
|
+
.setCustomId(buildPlanningCustomId('open', projectName, targetChannelId))
|
|
321
376
|
.setLabel((0, i18n_1.t)('Open'))
|
|
322
377
|
.setStyle(discord_js_1.ButtonStyle.Secondary);
|
|
323
378
|
const proceedBtn = new discord_js_1.ButtonBuilder()
|
|
324
|
-
.setCustomId(buildPlanningCustomId('proceed',
|
|
379
|
+
.setCustomId(buildPlanningCustomId('proceed', projectName, targetChannelId))
|
|
325
380
|
.setLabel((0, i18n_1.t)('Proceed'))
|
|
326
381
|
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
327
382
|
const row = new discord_js_1.ActionRowBuilder().addComponents(openBtn, proceedBtn);
|
|
328
|
-
targetChannel.send({
|
|
383
|
+
const sent = await targetChannel.send({
|
|
329
384
|
embeds: [embed],
|
|
330
385
|
components: [row],
|
|
331
|
-
}).catch(logger_1.logger.error);
|
|
386
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
387
|
+
if (sent) {
|
|
388
|
+
lastPlanningMessage = sent;
|
|
389
|
+
}
|
|
332
390
|
},
|
|
333
391
|
});
|
|
334
392
|
detector.start();
|
|
335
|
-
bridge.pool.registerPlanningDetector(
|
|
336
|
-
logger_1.logger.
|
|
393
|
+
bridge.pool.registerPlanningDetector(projectName, detector);
|
|
394
|
+
logger_1.logger.debug(`[PlanningDetector:${projectName}] Started planning button detection`);
|
|
337
395
|
}
|
|
338
396
|
/**
|
|
339
397
|
* Helper to start an error popup detector for each workspace.
|
|
340
398
|
* Does nothing if a detector for the same workspace is already running.
|
|
341
399
|
*/
|
|
342
|
-
function ensureErrorPopupDetector(bridge, cdp,
|
|
343
|
-
const existing = bridge.pool.getErrorPopupDetector(
|
|
400
|
+
function ensureErrorPopupDetector(bridge, cdp, projectName, _client) {
|
|
401
|
+
const existing = bridge.pool.getErrorPopupDetector(projectName);
|
|
344
402
|
if (existing && existing.isActive())
|
|
345
403
|
return;
|
|
404
|
+
// Track the most recent error message for auto-disable on resolve.
|
|
405
|
+
// See ensureApprovalDetector comment for tracking limitation rationale.
|
|
406
|
+
let lastErrorMessage = null;
|
|
346
407
|
const detector = new errorPopupDetector_1.ErrorPopupDetector({
|
|
347
408
|
cdpService: cdp,
|
|
348
409
|
pollIntervalMs: 3000,
|
|
410
|
+
onResolved: () => {
|
|
411
|
+
if (!lastErrorMessage)
|
|
412
|
+
return;
|
|
413
|
+
const msg = lastErrorMessage;
|
|
414
|
+
lastErrorMessage = null;
|
|
415
|
+
const originalEmbed = msg.embeds[0];
|
|
416
|
+
const updatedEmbed = originalEmbed
|
|
417
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
418
|
+
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Agent Error'));
|
|
419
|
+
updatedEmbed
|
|
420
|
+
.setColor(0x95A5A6)
|
|
421
|
+
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
422
|
+
msg.edit({
|
|
423
|
+
embeds: [updatedEmbed],
|
|
424
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
425
|
+
}).catch(logger_1.logger.error);
|
|
426
|
+
},
|
|
349
427
|
onErrorPopup: async (info) => {
|
|
350
|
-
logger_1.logger.
|
|
428
|
+
logger_1.logger.debug(`[ErrorPopupDetector:${projectName}] Error popup detected (title="${info.title}")`);
|
|
351
429
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
352
|
-
const targetChannel = resolveApprovalChannelForCurrentChat(bridge,
|
|
430
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
353
431
|
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
354
432
|
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
355
|
-
logger_1.logger.warn(`[ErrorPopupDetector:${
|
|
433
|
+
logger_1.logger.warn(`[ErrorPopupDetector:${projectName}] Skipped error popup notification because chat is not linked to a Discord session` +
|
|
356
434
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
357
435
|
return;
|
|
358
436
|
}
|
|
@@ -361,28 +439,50 @@ function ensureErrorPopupDetector(bridge, cdp, workspaceDirName, _client) {
|
|
|
361
439
|
.setTitle(info.title || (0, i18n_1.t)('Agent Error'))
|
|
362
440
|
.setDescription(bodyText.substring(0, 4096))
|
|
363
441
|
.setColor(0xE74C3C)
|
|
364
|
-
.addFields({ name: (0, i18n_1.t)('Buttons'), value: info.buttons.join(', ') || (0, i18n_1.t)('(None)'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value:
|
|
442
|
+
.addFields({ name: (0, i18n_1.t)('Buttons'), value: info.buttons.join(', ') || (0, i18n_1.t)('(None)'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true })
|
|
365
443
|
.setTimestamp();
|
|
366
444
|
const dismissBtn = new discord_js_1.ButtonBuilder()
|
|
367
|
-
.setCustomId(buildErrorPopupCustomId('dismiss',
|
|
445
|
+
.setCustomId(buildErrorPopupCustomId('dismiss', projectName, targetChannelId))
|
|
368
446
|
.setLabel((0, i18n_1.t)('Dismiss'))
|
|
369
447
|
.setStyle(discord_js_1.ButtonStyle.Secondary);
|
|
370
448
|
const copyDebugBtn = new discord_js_1.ButtonBuilder()
|
|
371
|
-
.setCustomId(buildErrorPopupCustomId('copy_debug',
|
|
449
|
+
.setCustomId(buildErrorPopupCustomId('copy_debug', projectName, targetChannelId))
|
|
372
450
|
.setLabel((0, i18n_1.t)('Copy debug info'))
|
|
373
451
|
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
374
452
|
const retryBtn = new discord_js_1.ButtonBuilder()
|
|
375
|
-
.setCustomId(buildErrorPopupCustomId('retry',
|
|
453
|
+
.setCustomId(buildErrorPopupCustomId('retry', projectName, targetChannelId))
|
|
376
454
|
.setLabel((0, i18n_1.t)('Retry'))
|
|
377
455
|
.setStyle(discord_js_1.ButtonStyle.Success);
|
|
378
456
|
const row = new discord_js_1.ActionRowBuilder().addComponents(dismissBtn, copyDebugBtn, retryBtn);
|
|
379
|
-
targetChannel.send({
|
|
457
|
+
const sent = await targetChannel.send({
|
|
380
458
|
embeds: [embed],
|
|
381
459
|
components: [row],
|
|
382
|
-
}).catch(logger_1.logger.error);
|
|
460
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
461
|
+
if (sent) {
|
|
462
|
+
lastErrorMessage = sent;
|
|
463
|
+
}
|
|
383
464
|
},
|
|
384
465
|
});
|
|
385
466
|
detector.start();
|
|
386
|
-
bridge.pool.registerErrorPopupDetector(
|
|
387
|
-
logger_1.logger.
|
|
467
|
+
bridge.pool.registerErrorPopupDetector(projectName, detector);
|
|
468
|
+
logger_1.logger.debug(`[ErrorPopupDetector:${projectName}] Started error popup detection`);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Helper to start a user message detector for a workspace.
|
|
472
|
+
* Detects messages typed directly in the Antigravity UI (e.g., from a PC)
|
|
473
|
+
* and mirrors them to a Discord channel.
|
|
474
|
+
* Does nothing if a detector for the same workspace is already running.
|
|
475
|
+
*/
|
|
476
|
+
function ensureUserMessageDetector(bridge, cdp, projectName, onUserMessage) {
|
|
477
|
+
const existing = bridge.pool.getUserMessageDetector(projectName);
|
|
478
|
+
if (existing && existing.isActive())
|
|
479
|
+
return;
|
|
480
|
+
const detector = new userMessageDetector_1.UserMessageDetector({
|
|
481
|
+
cdpService: cdp,
|
|
482
|
+
pollIntervalMs: 2000,
|
|
483
|
+
onUserMessage,
|
|
484
|
+
});
|
|
485
|
+
detector.start();
|
|
486
|
+
bridge.pool.registerUserMessageDetector(projectName, detector);
|
|
487
|
+
logger_1.logger.debug(`[UserMessageDetector:${projectName}] Started user message detection`);
|
|
388
488
|
}
|