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
|
@@ -1,28 +1,45 @@
|
|
|
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;
|
|
6
7
|
exports.buildApprovalCustomId = buildApprovalCustomId;
|
|
7
8
|
exports.parseApprovalCustomId = parseApprovalCustomId;
|
|
9
|
+
exports.buildPlanningCustomId = buildPlanningCustomId;
|
|
10
|
+
exports.parsePlanningCustomId = parsePlanningCustomId;
|
|
11
|
+
exports.buildErrorPopupCustomId = buildErrorPopupCustomId;
|
|
12
|
+
exports.parseErrorPopupCustomId = parseErrorPopupCustomId;
|
|
8
13
|
exports.initCdpBridge = initCdpBridge;
|
|
9
14
|
exports.getCurrentCdp = getCurrentCdp;
|
|
10
15
|
exports.ensureApprovalDetector = ensureApprovalDetector;
|
|
16
|
+
exports.ensurePlanningDetector = ensurePlanningDetector;
|
|
17
|
+
exports.ensureErrorPopupDetector = ensureErrorPopupDetector;
|
|
18
|
+
exports.ensureUserMessageDetector = ensureUserMessageDetector;
|
|
11
19
|
const discord_js_1 = require("discord.js");
|
|
12
20
|
const i18n_1 = require("../utils/i18n");
|
|
13
21
|
const logger_1 = require("../utils/logger");
|
|
22
|
+
const discordButtonUtils_1 = require("../utils/discordButtonUtils");
|
|
14
23
|
const approvalDetector_1 = require("./approvalDetector");
|
|
15
24
|
const autoAcceptService_1 = require("./autoAcceptService");
|
|
16
25
|
const cdpConnectionPool_1 = require("./cdpConnectionPool");
|
|
26
|
+
const errorPopupDetector_1 = require("./errorPopupDetector");
|
|
27
|
+
const planningDetector_1 = require("./planningDetector");
|
|
17
28
|
const quotaService_1 = require("./quotaService");
|
|
29
|
+
const userMessageDetector_1 = require("./userMessageDetector");
|
|
18
30
|
const APPROVE_ACTION_PREFIX = 'approve_action';
|
|
19
31
|
const ALWAYS_ALLOW_ACTION_PREFIX = 'always_allow_action';
|
|
20
32
|
const DENY_ACTION_PREFIX = 'deny_action';
|
|
33
|
+
const PLANNING_OPEN_ACTION_PREFIX = 'planning_open_action';
|
|
34
|
+
const PLANNING_PROCEED_ACTION_PREFIX = 'planning_proceed_action';
|
|
35
|
+
const ERROR_POPUP_DISMISS_ACTION_PREFIX = 'error_popup_dismiss_action';
|
|
36
|
+
const ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX = 'error_popup_copy_debug_action';
|
|
37
|
+
const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
21
38
|
function normalizeSessionTitle(title) {
|
|
22
39
|
return title.trim().toLowerCase();
|
|
23
40
|
}
|
|
24
|
-
function buildSessionRouteKey(
|
|
25
|
-
return `${
|
|
41
|
+
function buildSessionRouteKey(projectName, sessionTitle) {
|
|
42
|
+
return `${projectName}::${normalizeSessionTitle(sessionTitle)}`;
|
|
26
43
|
}
|
|
27
44
|
const GET_CURRENT_CHAT_TITLE_SCRIPT = `(() => {
|
|
28
45
|
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
@@ -54,57 +71,127 @@ async function getCurrentChatTitle(cdp) {
|
|
|
54
71
|
}
|
|
55
72
|
return null;
|
|
56
73
|
}
|
|
57
|
-
function registerApprovalWorkspaceChannel(bridge,
|
|
58
|
-
bridge.approvalChannelByWorkspace.set(
|
|
74
|
+
function registerApprovalWorkspaceChannel(bridge, projectName, channel) {
|
|
75
|
+
bridge.approvalChannelByWorkspace.set(projectName, channel);
|
|
59
76
|
}
|
|
60
|
-
function registerApprovalSessionChannel(bridge,
|
|
77
|
+
function registerApprovalSessionChannel(bridge, projectName, sessionTitle, channel) {
|
|
61
78
|
if (!sessionTitle || sessionTitle.trim().length === 0)
|
|
62
79
|
return;
|
|
63
|
-
bridge.approvalChannelBySession.set(buildSessionRouteKey(
|
|
64
|
-
bridge.approvalChannelByWorkspace.set(
|
|
80
|
+
bridge.approvalChannelBySession.set(buildSessionRouteKey(projectName, sessionTitle), channel);
|
|
81
|
+
bridge.approvalChannelByWorkspace.set(projectName, channel);
|
|
65
82
|
}
|
|
66
|
-
function resolveApprovalChannelForCurrentChat(bridge,
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
function resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle) {
|
|
84
|
+
// Try session-level match first (most precise routing)
|
|
85
|
+
if (currentChatTitle && currentChatTitle.trim().length > 0) {
|
|
86
|
+
const key = buildSessionRouteKey(projectName, currentChatTitle);
|
|
87
|
+
const sessionChannel = bridge.approvalChannelBySession.get(key);
|
|
88
|
+
if (sessionChannel)
|
|
89
|
+
return sessionChannel;
|
|
69
90
|
}
|
|
70
|
-
|
|
71
|
-
return bridge.
|
|
91
|
+
// Fall back to workspace-level routing
|
|
92
|
+
return bridge.approvalChannelByWorkspace.get(projectName) ?? null;
|
|
72
93
|
}
|
|
73
|
-
function buildApprovalCustomId(action,
|
|
94
|
+
function buildApprovalCustomId(action, projectName, channelId) {
|
|
74
95
|
const prefix = action === 'approve'
|
|
75
96
|
? APPROVE_ACTION_PREFIX
|
|
76
97
|
: action === 'always_allow'
|
|
77
98
|
? ALWAYS_ALLOW_ACTION_PREFIX
|
|
78
99
|
: DENY_ACTION_PREFIX;
|
|
79
100
|
if (channelId && channelId.trim().length > 0) {
|
|
80
|
-
return `${prefix}:${
|
|
101
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
81
102
|
}
|
|
82
|
-
return `${prefix}:${
|
|
103
|
+
return `${prefix}:${projectName}`;
|
|
83
104
|
}
|
|
84
105
|
function parseApprovalCustomId(customId) {
|
|
85
106
|
if (customId === APPROVE_ACTION_PREFIX) {
|
|
86
|
-
return { action: 'approve',
|
|
107
|
+
return { action: 'approve', projectName: null, channelId: null };
|
|
87
108
|
}
|
|
88
109
|
if (customId === ALWAYS_ALLOW_ACTION_PREFIX) {
|
|
89
|
-
return { action: 'always_allow',
|
|
110
|
+
return { action: 'always_allow', projectName: null, channelId: null };
|
|
90
111
|
}
|
|
91
112
|
if (customId === DENY_ACTION_PREFIX) {
|
|
92
|
-
return { action: 'deny',
|
|
113
|
+
return { action: 'deny', projectName: null, channelId: null };
|
|
93
114
|
}
|
|
94
115
|
if (customId.startsWith(`${APPROVE_ACTION_PREFIX}:`)) {
|
|
95
116
|
const rest = customId.substring(`${APPROVE_ACTION_PREFIX}:`.length);
|
|
96
|
-
const [
|
|
97
|
-
return { action: 'approve',
|
|
117
|
+
const [projectName, channelId] = rest.split(':');
|
|
118
|
+
return { action: 'approve', projectName: projectName || null, channelId: channelId || null };
|
|
98
119
|
}
|
|
99
120
|
if (customId.startsWith(`${ALWAYS_ALLOW_ACTION_PREFIX}:`)) {
|
|
100
121
|
const rest = customId.substring(`${ALWAYS_ALLOW_ACTION_PREFIX}:`.length);
|
|
101
|
-
const [
|
|
102
|
-
return { action: 'always_allow',
|
|
122
|
+
const [projectName, channelId] = rest.split(':');
|
|
123
|
+
return { action: 'always_allow', projectName: projectName || null, channelId: channelId || null };
|
|
103
124
|
}
|
|
104
125
|
if (customId.startsWith(`${DENY_ACTION_PREFIX}:`)) {
|
|
105
126
|
const rest = customId.substring(`${DENY_ACTION_PREFIX}:`.length);
|
|
106
|
-
const [
|
|
107
|
-
return { action: 'deny',
|
|
127
|
+
const [projectName, channelId] = rest.split(':');
|
|
128
|
+
return { action: 'deny', projectName: projectName || null, channelId: channelId || null };
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function buildPlanningCustomId(action, projectName, channelId) {
|
|
133
|
+
const prefix = action === 'open'
|
|
134
|
+
? PLANNING_OPEN_ACTION_PREFIX
|
|
135
|
+
: PLANNING_PROCEED_ACTION_PREFIX;
|
|
136
|
+
if (channelId && channelId.trim().length > 0) {
|
|
137
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
138
|
+
}
|
|
139
|
+
return `${prefix}:${projectName}`;
|
|
140
|
+
}
|
|
141
|
+
function parsePlanningCustomId(customId) {
|
|
142
|
+
if (customId === PLANNING_OPEN_ACTION_PREFIX) {
|
|
143
|
+
return { action: 'open', projectName: null, channelId: null };
|
|
144
|
+
}
|
|
145
|
+
if (customId === PLANNING_PROCEED_ACTION_PREFIX) {
|
|
146
|
+
return { action: 'proceed', projectName: null, channelId: null };
|
|
147
|
+
}
|
|
148
|
+
if (customId.startsWith(`${PLANNING_OPEN_ACTION_PREFIX}:`)) {
|
|
149
|
+
const rest = customId.substring(`${PLANNING_OPEN_ACTION_PREFIX}:`.length);
|
|
150
|
+
const [projectName, channelId] = rest.split(':');
|
|
151
|
+
return { action: 'open', projectName: projectName || null, channelId: channelId || null };
|
|
152
|
+
}
|
|
153
|
+
if (customId.startsWith(`${PLANNING_PROCEED_ACTION_PREFIX}:`)) {
|
|
154
|
+
const rest = customId.substring(`${PLANNING_PROCEED_ACTION_PREFIX}:`.length);
|
|
155
|
+
const [projectName, channelId] = rest.split(':');
|
|
156
|
+
return { action: 'proceed', projectName: projectName || null, channelId: channelId || null };
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function buildErrorPopupCustomId(action, projectName, channelId) {
|
|
161
|
+
const prefix = action === 'dismiss'
|
|
162
|
+
? ERROR_POPUP_DISMISS_ACTION_PREFIX
|
|
163
|
+
: action === 'copy_debug'
|
|
164
|
+
? ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX
|
|
165
|
+
: ERROR_POPUP_RETRY_ACTION_PREFIX;
|
|
166
|
+
if (channelId && channelId.trim().length > 0) {
|
|
167
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
168
|
+
}
|
|
169
|
+
return `${prefix}:${projectName}`;
|
|
170
|
+
}
|
|
171
|
+
function parseErrorPopupCustomId(customId) {
|
|
172
|
+
if (customId === ERROR_POPUP_DISMISS_ACTION_PREFIX) {
|
|
173
|
+
return { action: 'dismiss', projectName: null, channelId: null };
|
|
174
|
+
}
|
|
175
|
+
if (customId === ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX) {
|
|
176
|
+
return { action: 'copy_debug', projectName: null, channelId: null };
|
|
177
|
+
}
|
|
178
|
+
if (customId === ERROR_POPUP_RETRY_ACTION_PREFIX) {
|
|
179
|
+
return { action: 'retry', projectName: null, channelId: null };
|
|
180
|
+
}
|
|
181
|
+
if (customId.startsWith(`${ERROR_POPUP_DISMISS_ACTION_PREFIX}:`)) {
|
|
182
|
+
const rest = customId.substring(`${ERROR_POPUP_DISMISS_ACTION_PREFIX}:`.length);
|
|
183
|
+
const [projectName, channelId] = rest.split(':');
|
|
184
|
+
return { action: 'dismiss', projectName: projectName || null, channelId: channelId || null };
|
|
185
|
+
}
|
|
186
|
+
if (customId.startsWith(`${ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX}:`)) {
|
|
187
|
+
const rest = customId.substring(`${ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX}:`.length);
|
|
188
|
+
const [projectName, channelId] = rest.split(':');
|
|
189
|
+
return { action: 'copy_debug', projectName: projectName || null, channelId: channelId || null };
|
|
190
|
+
}
|
|
191
|
+
if (customId.startsWith(`${ERROR_POPUP_RETRY_ACTION_PREFIX}:`)) {
|
|
192
|
+
const rest = customId.substring(`${ERROR_POPUP_RETRY_ACTION_PREFIX}:`.length);
|
|
193
|
+
const [projectName, channelId] = rest.split(':');
|
|
194
|
+
return { action: 'retry', projectName: projectName || null, channelId: channelId || null };
|
|
108
195
|
}
|
|
109
196
|
return null;
|
|
110
197
|
}
|
|
@@ -143,20 +230,42 @@ function getCurrentCdp(bridge) {
|
|
|
143
230
|
* Helper to start an approval detector for each workspace.
|
|
144
231
|
* Does nothing if a detector for the same workspace is already running.
|
|
145
232
|
*/
|
|
146
|
-
function ensureApprovalDetector(bridge, cdp,
|
|
147
|
-
const existing = bridge.pool.getApprovalDetector(
|
|
233
|
+
function ensureApprovalDetector(bridge, cdp, projectName, client) {
|
|
234
|
+
const existing = bridge.pool.getApprovalDetector(projectName);
|
|
148
235
|
if (existing && existing.isActive())
|
|
149
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;
|
|
150
242
|
const detector = new approvalDetector_1.ApprovalDetector({
|
|
151
243
|
cdpService: cdp,
|
|
152
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
|
+
},
|
|
153
262
|
onApprovalRequired: async (info) => {
|
|
154
|
-
logger_1.logger.
|
|
263
|
+
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Approval button detected (allow="${info.approveText}", deny="${info.denyText}")`);
|
|
155
264
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
156
|
-
const targetChannel = resolveApprovalChannelForCurrentChat(bridge,
|
|
265
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
157
266
|
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
158
267
|
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
159
|
-
logger_1.logger.warn(`[ApprovalDetector:${
|
|
268
|
+
logger_1.logger.warn(`[ApprovalDetector:${projectName}] Skipped approval notification because chat is not linked to a Discord session` +
|
|
160
269
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
161
270
|
return;
|
|
162
271
|
}
|
|
@@ -164,10 +273,16 @@ function ensureApprovalDetector(bridge, cdp, workspaceDirName, client) {
|
|
|
164
273
|
const accepted = await detector.alwaysAllowButton() || await detector.approveButton();
|
|
165
274
|
const autoEmbed = new discord_js_1.EmbedBuilder()
|
|
166
275
|
.setTitle(accepted ? (0, i18n_1.t)('Auto-approved') : (0, i18n_1.t)('Auto-approve failed'))
|
|
167
|
-
.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.'))
|
|
168
277
|
.setColor(accepted ? 0x2ECC71 : 0xF39C12)
|
|
169
|
-
.addFields({ name: (0, i18n_1.t)('Auto-approve mode'), value: (0, i18n_1.t)('ON'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value:
|
|
170
|
-
|
|
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();
|
|
171
286
|
await targetChannel.send({ embeds: [autoEmbed] }).catch(logger_1.logger.error);
|
|
172
287
|
if (accepted) {
|
|
173
288
|
return;
|
|
@@ -177,28 +292,197 @@ function ensureApprovalDetector(bridge, cdp, workspaceDirName, client) {
|
|
|
177
292
|
.setTitle((0, i18n_1.t)('Approval Required'))
|
|
178
293
|
.setDescription(info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'))
|
|
179
294
|
.setColor(0xFFA500)
|
|
180
|
-
.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 })
|
|
181
296
|
.setTimestamp();
|
|
182
297
|
const approveBtn = new discord_js_1.ButtonBuilder()
|
|
183
|
-
.setCustomId(buildApprovalCustomId('approve',
|
|
298
|
+
.setCustomId(buildApprovalCustomId('approve', projectName, targetChannelId))
|
|
184
299
|
.setLabel((0, i18n_1.t)('Allow'))
|
|
185
300
|
.setStyle(discord_js_1.ButtonStyle.Success);
|
|
186
301
|
const alwaysAllowBtn = new discord_js_1.ButtonBuilder()
|
|
187
|
-
.setCustomId(buildApprovalCustomId('always_allow',
|
|
302
|
+
.setCustomId(buildApprovalCustomId('always_allow', projectName, targetChannelId))
|
|
188
303
|
.setLabel((0, i18n_1.t)('Allow Chat'))
|
|
189
304
|
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
190
305
|
const denyBtn = new discord_js_1.ButtonBuilder()
|
|
191
|
-
.setCustomId(buildApprovalCustomId('deny',
|
|
306
|
+
.setCustomId(buildApprovalCustomId('deny', projectName, targetChannelId))
|
|
192
307
|
.setLabel((0, i18n_1.t)('Deny'))
|
|
193
308
|
.setStyle(discord_js_1.ButtonStyle.Danger);
|
|
194
309
|
const row = new discord_js_1.ActionRowBuilder().addComponents(approveBtn, alwaysAllowBtn, denyBtn);
|
|
195
|
-
targetChannel.send({
|
|
310
|
+
const sent = await targetChannel.send({
|
|
196
311
|
embeds: [embed],
|
|
197
312
|
components: [row],
|
|
313
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
314
|
+
if (sent) {
|
|
315
|
+
lastButtonMessage = sent;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
detector.start();
|
|
320
|
+
bridge.pool.registerApprovalDetector(projectName, detector);
|
|
321
|
+
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Started approval button detection`);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Helper to start a planning detector for each workspace.
|
|
325
|
+
* Does nothing if a detector for the same workspace is already running.
|
|
326
|
+
*/
|
|
327
|
+
function ensurePlanningDetector(bridge, cdp, projectName, _client) {
|
|
328
|
+
const existing = bridge.pool.getPlanningDetector(projectName);
|
|
329
|
+
if (existing && existing.isActive())
|
|
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;
|
|
334
|
+
const detector = new planningDetector_1.PlanningDetector({
|
|
335
|
+
cdpService: cdp,
|
|
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),
|
|
198
352
|
}).catch(logger_1.logger.error);
|
|
199
353
|
},
|
|
354
|
+
onPlanningRequired: async (info) => {
|
|
355
|
+
logger_1.logger.debug(`[PlanningDetector:${projectName}] Planning buttons detected (title="${info.planTitle}")`);
|
|
356
|
+
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
357
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
358
|
+
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
359
|
+
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
360
|
+
logger_1.logger.warn(`[PlanningDetector:${projectName}] Skipped planning notification because chat is not linked to a Discord session` +
|
|
361
|
+
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const descriptionText = info.description || info.planSummary || (0, i18n_1.t)('A plan has been generated and is awaiting your review.');
|
|
365
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
366
|
+
.setTitle((0, i18n_1.t)('Planning Mode'))
|
|
367
|
+
.setDescription(descriptionText)
|
|
368
|
+
.setColor(0x3498DB)
|
|
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 })
|
|
370
|
+
.setTimestamp();
|
|
371
|
+
if (info.planSummary && info.description) {
|
|
372
|
+
embed.addFields({ name: (0, i18n_1.t)('Summary'), value: info.planSummary.substring(0, 1024), inline: false });
|
|
373
|
+
}
|
|
374
|
+
const openBtn = new discord_js_1.ButtonBuilder()
|
|
375
|
+
.setCustomId(buildPlanningCustomId('open', projectName, targetChannelId))
|
|
376
|
+
.setLabel((0, i18n_1.t)('Open'))
|
|
377
|
+
.setStyle(discord_js_1.ButtonStyle.Secondary);
|
|
378
|
+
const proceedBtn = new discord_js_1.ButtonBuilder()
|
|
379
|
+
.setCustomId(buildPlanningCustomId('proceed', projectName, targetChannelId))
|
|
380
|
+
.setLabel((0, i18n_1.t)('Proceed'))
|
|
381
|
+
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
382
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(openBtn, proceedBtn);
|
|
383
|
+
const sent = await targetChannel.send({
|
|
384
|
+
embeds: [embed],
|
|
385
|
+
components: [row],
|
|
386
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
387
|
+
if (sent) {
|
|
388
|
+
lastPlanningMessage = sent;
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
detector.start();
|
|
393
|
+
bridge.pool.registerPlanningDetector(projectName, detector);
|
|
394
|
+
logger_1.logger.debug(`[PlanningDetector:${projectName}] Started planning button detection`);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Helper to start an error popup detector for each workspace.
|
|
398
|
+
* Does nothing if a detector for the same workspace is already running.
|
|
399
|
+
*/
|
|
400
|
+
function ensureErrorPopupDetector(bridge, cdp, projectName, _client) {
|
|
401
|
+
const existing = bridge.pool.getErrorPopupDetector(projectName);
|
|
402
|
+
if (existing && existing.isActive())
|
|
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;
|
|
407
|
+
const detector = new errorPopupDetector_1.ErrorPopupDetector({
|
|
408
|
+
cdpService: cdp,
|
|
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
|
+
},
|
|
427
|
+
onErrorPopup: async (info) => {
|
|
428
|
+
logger_1.logger.debug(`[ErrorPopupDetector:${projectName}] Error popup detected (title="${info.title}")`);
|
|
429
|
+
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
430
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
431
|
+
const targetChannelId = targetChannel && 'id' in targetChannel ? String(targetChannel.id) : '';
|
|
432
|
+
if (!targetChannel || !targetChannelId || !('send' in targetChannel)) {
|
|
433
|
+
logger_1.logger.warn(`[ErrorPopupDetector:${projectName}] Skipped error popup notification because chat is not linked to a Discord session` +
|
|
434
|
+
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const bodyText = info.body || (0, i18n_1.t)('An error occurred in the Antigravity agent.');
|
|
438
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
439
|
+
.setTitle(info.title || (0, i18n_1.t)('Agent Error'))
|
|
440
|
+
.setDescription(bodyText.substring(0, 4096))
|
|
441
|
+
.setColor(0xE74C3C)
|
|
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 })
|
|
443
|
+
.setTimestamp();
|
|
444
|
+
const dismissBtn = new discord_js_1.ButtonBuilder()
|
|
445
|
+
.setCustomId(buildErrorPopupCustomId('dismiss', projectName, targetChannelId))
|
|
446
|
+
.setLabel((0, i18n_1.t)('Dismiss'))
|
|
447
|
+
.setStyle(discord_js_1.ButtonStyle.Secondary);
|
|
448
|
+
const copyDebugBtn = new discord_js_1.ButtonBuilder()
|
|
449
|
+
.setCustomId(buildErrorPopupCustomId('copy_debug', projectName, targetChannelId))
|
|
450
|
+
.setLabel((0, i18n_1.t)('Copy debug info'))
|
|
451
|
+
.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
452
|
+
const retryBtn = new discord_js_1.ButtonBuilder()
|
|
453
|
+
.setCustomId(buildErrorPopupCustomId('retry', projectName, targetChannelId))
|
|
454
|
+
.setLabel((0, i18n_1.t)('Retry'))
|
|
455
|
+
.setStyle(discord_js_1.ButtonStyle.Success);
|
|
456
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(dismissBtn, copyDebugBtn, retryBtn);
|
|
457
|
+
const sent = await targetChannel.send({
|
|
458
|
+
embeds: [embed],
|
|
459
|
+
components: [row],
|
|
460
|
+
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
461
|
+
if (sent) {
|
|
462
|
+
lastErrorMessage = sent;
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
detector.start();
|
|
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,
|
|
200
484
|
});
|
|
201
485
|
detector.start();
|
|
202
|
-
bridge.pool.
|
|
203
|
-
logger_1.logger.
|
|
486
|
+
bridge.pool.registerUserMessageDetector(projectName, detector);
|
|
487
|
+
logger_1.logger.debug(`[UserMessageDetector:${projectName}] Started user message detection`);
|
|
204
488
|
}
|