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
|
@@ -40,7 +40,7 @@ const GET_NEW_CHAT_BUTTON_SCRIPT = `(() => {
|
|
|
40
40
|
* The title element is a div with the text-ellipsis class inside the header.
|
|
41
41
|
*/
|
|
42
42
|
const GET_CHAT_TITLE_SCRIPT = `(() => {
|
|
43
|
-
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
43
|
+
const panel = document.querySelector('.antigravity-agent-side-panel') || document.body;
|
|
44
44
|
if (!panel) return { title: '', hasActiveChat: false };
|
|
45
45
|
const header = panel.querySelector('div[class*="border-b"]');
|
|
46
46
|
if (!header) return { title: '', hasActiveChat: false };
|
|
@@ -58,7 +58,7 @@ const GET_CHAT_TITLE_SCRIPT = `(() => {
|
|
|
58
58
|
return { title: title || '(Untitled)', hasActiveChat };
|
|
59
59
|
})()`;
|
|
60
60
|
const GET_SESSION_VIEW_STATE_SCRIPT = `(() => {
|
|
61
|
-
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
61
|
+
const panel = document.querySelector('.antigravity-agent-side-panel') || document.body;
|
|
62
62
|
if (!panel) {
|
|
63
63
|
return {
|
|
64
64
|
panelFound: false,
|
|
@@ -169,7 +169,8 @@ const SCRAPE_PAST_CONVERSATIONS_SCRIPT = `(() => {
|
|
|
169
169
|
// Try the visible QuickInput dialog first, then fall back to the side panel.
|
|
170
170
|
const quickInputPanels = Array.from(document.querySelectorAll('div[class*="bg-quickinput-background"]'));
|
|
171
171
|
const panel = quickInputPanels.find((el) => isVisible(el))
|
|
172
|
-
|| document.querySelector('.antigravity-agent-side-panel')
|
|
172
|
+
|| document.querySelector('.antigravity-agent-side-panel')
|
|
173
|
+
|| document.body;
|
|
173
174
|
if (!panel) return null;
|
|
174
175
|
|
|
175
176
|
const items = [];
|
|
@@ -276,9 +277,11 @@ function buildActivateChatByTitleScript(title) {
|
|
|
276
277
|
for (const node of nodes) {
|
|
277
278
|
const text = normalize(node.textContent || '');
|
|
278
279
|
if (!text) continue;
|
|
280
|
+
|
|
281
|
+
const isShort = wanted.length < 5;
|
|
279
282
|
if (text === wanted) {
|
|
280
283
|
exact.push({ node, textLength: text.length });
|
|
281
|
-
} else if (text.includes(wanted)) {
|
|
284
|
+
} else if (!isShort && text.includes(wanted)) {
|
|
282
285
|
includes.push({ node, textLength: text.length });
|
|
283
286
|
} else if (wordsMatch(text, wantedRaw)) {
|
|
284
287
|
fuzzy.push({ node, textLength: text.length });
|
|
@@ -369,10 +372,12 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
369
372
|
if (!pattern) continue;
|
|
370
373
|
const p = normalize(pattern);
|
|
371
374
|
const pLoose = normalizeLoose(pattern);
|
|
375
|
+
const isShort = p.length < 5;
|
|
376
|
+
|
|
372
377
|
if (
|
|
373
378
|
text === p ||
|
|
374
|
-
|
|
375
|
-
(
|
|
379
|
+
(pLoose && textLoose === pLoose) ||
|
|
380
|
+
(!isShort && (text.includes(p) || (pLoose && textLoose.includes(pLoose))))
|
|
376
381
|
) {
|
|
377
382
|
matched.push({ el, score: Math.abs(text.length - pattern.length) });
|
|
378
383
|
break;
|
|
@@ -611,7 +616,8 @@ class ChatSessionService {
|
|
|
611
616
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
612
617
|
const quickInputPanels = Array.from(document.querySelectorAll('div[class*="bg-quickinput-background"]'));
|
|
613
618
|
const panel = quickInputPanels.find((el) => isVisible(el))
|
|
614
|
-
|| document.querySelector('.antigravity-agent-side-panel')
|
|
619
|
+
|| document.querySelector('.antigravity-agent-side-panel')
|
|
620
|
+
|| document.body;
|
|
615
621
|
if (!panel) return false;
|
|
616
622
|
const containers = Array.from(
|
|
617
623
|
panel.querySelectorAll('div[class*="overflow-auto"], div[class*="overflow-y-scroll"]')
|
|
@@ -1095,12 +1101,13 @@ class ChatSessionService {
|
|
|
1095
1101
|
/**
|
|
1096
1102
|
* Rename the current chat in the Antigravity UI directly by updating the DOM.
|
|
1097
1103
|
* Note: This is a cosmetic change until Antigravity persists a rename.
|
|
1104
|
+
* IDE syncing for chat renaming is strictly "best-effort".
|
|
1098
1105
|
*/
|
|
1099
1106
|
async renameCurrentChatInUI(cdpService, newTitle) {
|
|
1100
1107
|
try {
|
|
1101
1108
|
const contextId = cdpService.getPrimaryContextId();
|
|
1102
1109
|
const RENAME_SCRIPT = `(() => {
|
|
1103
|
-
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
1110
|
+
const panel = document.querySelector('.antigravity-agent-side-panel') || document.body;
|
|
1104
1111
|
if (!panel) return { ok: false, error: 'Panel not found' };
|
|
1105
1112
|
const header = panel.querySelector('div[class*="border-b"]');
|
|
1106
1113
|
const titleEl = header?.querySelector('div[class*="text-ellipsis"]');
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeartbeatService = void 0;
|
|
4
|
+
exports.parseInterval = parseInterval;
|
|
5
|
+
exports.formatDuration = formatDuration;
|
|
6
|
+
exports.formatRelativeTime = formatRelativeTime;
|
|
7
|
+
const discord_js_1 = require("discord.js");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
9
|
+
const configLoader_1 = require("../utils/configLoader");
|
|
10
|
+
class HeartbeatService {
|
|
11
|
+
client = null;
|
|
12
|
+
bridge = null;
|
|
13
|
+
intervalId = null;
|
|
14
|
+
generationToken = 0;
|
|
15
|
+
isSending = false;
|
|
16
|
+
botStartTime = Date.now();
|
|
17
|
+
lastActivityTimestamp = Date.now();
|
|
18
|
+
constructor() { }
|
|
19
|
+
init(client, bridge) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.bridge = bridge;
|
|
22
|
+
this.botStartTime = Date.now();
|
|
23
|
+
this.lastActivityTimestamp = Date.now();
|
|
24
|
+
}
|
|
25
|
+
recordActivity() {
|
|
26
|
+
this.lastActivityTimestamp = Date.now();
|
|
27
|
+
}
|
|
28
|
+
start() {
|
|
29
|
+
this.stop();
|
|
30
|
+
const config = configLoader_1.ConfigLoader.load();
|
|
31
|
+
if (!config.heartbeatEnabled) {
|
|
32
|
+
logger_1.logger.info('[HeartbeatService] Periodic heartbeat is disabled.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const channelId = config.heartbeatChannelId;
|
|
36
|
+
if (!channelId) {
|
|
37
|
+
logger_1.logger.warn('[HeartbeatService] Enabled but heartbeatChannelId is not configured.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let interval = config.heartbeatIntervalMs ?? 3600000;
|
|
41
|
+
if (interval < 10000) {
|
|
42
|
+
logger_1.logger.warn(`[HeartbeatService] Interval ${interval}ms is too low. Clamping to 10000ms.`);
|
|
43
|
+
interval = 10000;
|
|
44
|
+
}
|
|
45
|
+
else if (interval > 2147483647) {
|
|
46
|
+
logger_1.logger.warn(`[HeartbeatService] Interval ${interval}ms is too high (max is 2147483647ms). Clamping to 2147483647ms.`);
|
|
47
|
+
interval = 2147483647;
|
|
48
|
+
}
|
|
49
|
+
logger_1.logger.info(`[HeartbeatService] Starting periodic heartbeat every ${interval}ms to channel ${channelId}`);
|
|
50
|
+
// Run immediately on start
|
|
51
|
+
this.sendHeartbeat().catch(err => {
|
|
52
|
+
logger_1.logger.error('[HeartbeatService] Failed to send initial heartbeat:', err);
|
|
53
|
+
});
|
|
54
|
+
this.intervalId = setInterval(() => {
|
|
55
|
+
this.sendHeartbeat().catch(err => {
|
|
56
|
+
logger_1.logger.error('[HeartbeatService] Failed to send periodic heartbeat:', err);
|
|
57
|
+
});
|
|
58
|
+
}, interval);
|
|
59
|
+
}
|
|
60
|
+
stop() {
|
|
61
|
+
this.generationToken++;
|
|
62
|
+
if (this.intervalId) {
|
|
63
|
+
clearInterval(this.intervalId);
|
|
64
|
+
this.intervalId = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async updateConfig(enabled, intervalMs, channelId) {
|
|
68
|
+
this.generationToken++;
|
|
69
|
+
const config = configLoader_1.ConfigLoader.load();
|
|
70
|
+
// If channel changed, clear the last message ID
|
|
71
|
+
if (config.heartbeatChannelId !== channelId) {
|
|
72
|
+
if (config.heartbeatChannelId && config.heartbeatLastMessageId) {
|
|
73
|
+
try {
|
|
74
|
+
const oldChannel = await this.client?.channels.fetch(config.heartbeatChannelId);
|
|
75
|
+
if (oldChannel && oldChannel.isTextBased()) {
|
|
76
|
+
const oldMsg = await oldChannel.messages.fetch(config.heartbeatLastMessageId);
|
|
77
|
+
if (oldMsg) {
|
|
78
|
+
await oldMsg.delete().catch(() => { });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
logger_1.logger.debug('[HeartbeatService] Failed to delete old heartbeat message from previous channel:', err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
configLoader_1.ConfigLoader.save({ heartbeatLastMessageId: undefined });
|
|
87
|
+
}
|
|
88
|
+
// Save to config.json
|
|
89
|
+
configLoader_1.ConfigLoader.save({
|
|
90
|
+
heartbeatEnabled: enabled,
|
|
91
|
+
heartbeatIntervalMs: intervalMs,
|
|
92
|
+
heartbeatChannelId: channelId,
|
|
93
|
+
});
|
|
94
|
+
logger_1.logger.info(`[HeartbeatService] Config updated: enabled=${enabled}, interval=${intervalMs}ms, channel=${channelId}`);
|
|
95
|
+
// Restart loop
|
|
96
|
+
this.start();
|
|
97
|
+
}
|
|
98
|
+
async disable() {
|
|
99
|
+
this.generationToken++;
|
|
100
|
+
const config = configLoader_1.ConfigLoader.load();
|
|
101
|
+
if (config.heartbeatChannelId && config.heartbeatLastMessageId) {
|
|
102
|
+
try {
|
|
103
|
+
const oldChannel = await this.client?.channels.fetch(config.heartbeatChannelId);
|
|
104
|
+
if (oldChannel && oldChannel.isTextBased()) {
|
|
105
|
+
const oldMsg = await oldChannel.messages.fetch(config.heartbeatLastMessageId);
|
|
106
|
+
if (oldMsg) {
|
|
107
|
+
await oldMsg.delete().catch(() => { });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
logger_1.logger.debug('[HeartbeatService] Failed to delete heartbeat message upon disabling:', err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
configLoader_1.ConfigLoader.save({
|
|
116
|
+
heartbeatEnabled: false,
|
|
117
|
+
heartbeatLastMessageId: undefined,
|
|
118
|
+
});
|
|
119
|
+
this.stop();
|
|
120
|
+
logger_1.logger.info('[HeartbeatService] Heartbeat disabled.');
|
|
121
|
+
}
|
|
122
|
+
async sendHeartbeat() {
|
|
123
|
+
if (!this.client || !this.bridge) {
|
|
124
|
+
logger_1.logger.warn('[HeartbeatService] Cannot send heartbeat: client or bridge not initialized.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (this.isSending) {
|
|
128
|
+
logger_1.logger.debug('[HeartbeatService] Heartbeat send already in flight. Skipping overlapping execution.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const gen = this.generationToken;
|
|
132
|
+
this.isSending = true;
|
|
133
|
+
const config = configLoader_1.ConfigLoader.load();
|
|
134
|
+
const channelId = config.heartbeatChannelId;
|
|
135
|
+
if (!channelId) {
|
|
136
|
+
this.isSending = false;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const channel = await this.client.channels.fetch(channelId);
|
|
141
|
+
if (gen !== this.generationToken) {
|
|
142
|
+
logger_1.logger.debug(`[HeartbeatService] Aborting heartbeat send: stale generation (expected ${gen}, current ${this.generationToken})`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!channel || !channel.isTextBased()) {
|
|
146
|
+
logger_1.logger.warn(`[HeartbeatService] Channel ${channelId} not found or is not a text channel.`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const embed = this.buildHeartbeatEmbed();
|
|
150
|
+
const lastMessageId = config.heartbeatLastMessageId;
|
|
151
|
+
if (lastMessageId) {
|
|
152
|
+
try {
|
|
153
|
+
const message = await channel.messages.fetch(lastMessageId);
|
|
154
|
+
if (gen !== this.generationToken) {
|
|
155
|
+
logger_1.logger.debug('[HeartbeatService] Aborting message edit: stale generation');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (message && message.author.id === this.client.user?.id) {
|
|
159
|
+
await message.edit({ embeds: [embed] });
|
|
160
|
+
logger_1.logger.debug(`[HeartbeatService] Updated heartbeat message in-place: ${lastMessageId}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (fetchErr) {
|
|
165
|
+
logger_1.logger.debug(`[HeartbeatService] Failed to fetch or edit message ${lastMessageId}, sending new one.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (gen !== this.generationToken) {
|
|
169
|
+
logger_1.logger.debug('[HeartbeatService] Aborting message send: stale generation');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Send new message
|
|
173
|
+
const newMsg = await channel.send({ embeds: [embed] });
|
|
174
|
+
if (gen !== this.generationToken) {
|
|
175
|
+
logger_1.logger.debug(`[HeartbeatService] Stale generation after send. Deleting new message ${newMsg.id} to avoid leakage.`);
|
|
176
|
+
await newMsg.delete().catch(() => { });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
configLoader_1.ConfigLoader.save({ heartbeatLastMessageId: newMsg.id });
|
|
180
|
+
logger_1.logger.info(`[HeartbeatService] Sent new heartbeat message: ${newMsg.id}`);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
logger_1.logger.error('[HeartbeatService] Error in sendHeartbeat:', error);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
this.isSending = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
buildHeartbeatEmbed() {
|
|
190
|
+
const uptimeMs = Date.now() - this.botStartTime;
|
|
191
|
+
const uptimeStr = formatDuration(uptimeMs);
|
|
192
|
+
const activeWorkspaces = this.bridge?.pool.getActiveWorkspaceNames() || [];
|
|
193
|
+
const activeCount = activeWorkspaces.length;
|
|
194
|
+
const activeList = activeCount > 0 ? activeWorkspaces.join(', ') : 'None';
|
|
195
|
+
const lastActivityStr = formatRelativeTime(this.lastActivityTimestamp);
|
|
196
|
+
return new discord_js_1.EmbedBuilder()
|
|
197
|
+
.setTitle('💓 LazyGravity Heartbeat')
|
|
198
|
+
.setColor(0x00CC88)
|
|
199
|
+
.addFields({ name: 'Status', value: '🟢 Running', inline: true }, { name: 'Uptime', value: uptimeStr, inline: true }, { name: 'Active Sessions', value: `${activeCount} (${activeList})`, inline: true }, { name: 'Last Activity', value: lastActivityStr, inline: true })
|
|
200
|
+
.setFooter({ text: `Last updated: ${new Date().toLocaleString()}` })
|
|
201
|
+
.setTimestamp();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.HeartbeatService = HeartbeatService;
|
|
205
|
+
/**
|
|
206
|
+
* Parse a duration string like "1h", "6h", "30m", or "2d" to milliseconds.
|
|
207
|
+
* Requiring a unit prevents ambiguity with bare numbers.
|
|
208
|
+
*/
|
|
209
|
+
function parseInterval(str) {
|
|
210
|
+
const match = str.trim().toLowerCase().match(/^(\d+)(ms|s|m|h|d)$/);
|
|
211
|
+
if (!match)
|
|
212
|
+
return null;
|
|
213
|
+
const value = parseInt(match[1], 10);
|
|
214
|
+
const unit = match[2];
|
|
215
|
+
switch (unit) {
|
|
216
|
+
case 'ms': return value;
|
|
217
|
+
case 's': return value * 1000;
|
|
218
|
+
case 'm': return value * 60 * 1000;
|
|
219
|
+
case 'h': return value * 60 * 60 * 1000;
|
|
220
|
+
case 'd': return value * 24 * 60 * 60 * 1000;
|
|
221
|
+
default: return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Format milliseconds to a human-readable duration (e.g. "2h 15m")
|
|
226
|
+
*/
|
|
227
|
+
function formatDuration(ms) {
|
|
228
|
+
const seconds = Math.floor((ms / 1000) % 60);
|
|
229
|
+
const minutes = Math.floor((ms / (1000 * 60)) % 60);
|
|
230
|
+
const hours = Math.floor((ms / (1000 * 60 * 60)) % 24);
|
|
231
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
232
|
+
const parts = [];
|
|
233
|
+
if (days > 0)
|
|
234
|
+
parts.push(`${days}d`);
|
|
235
|
+
if (hours > 0)
|
|
236
|
+
parts.push(`${hours}h`);
|
|
237
|
+
if (minutes > 0)
|
|
238
|
+
parts.push(`${minutes}m`);
|
|
239
|
+
if (seconds > 0 || parts.length === 0)
|
|
240
|
+
parts.push(`${seconds}s`);
|
|
241
|
+
return parts.join(' ');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Format a past timestamp to a relative string (e.g. "5m ago")
|
|
245
|
+
*/
|
|
246
|
+
function formatRelativeTime(timestamp) {
|
|
247
|
+
const diff = Date.now() - timestamp;
|
|
248
|
+
if (diff < 5000)
|
|
249
|
+
return 'just now';
|
|
250
|
+
const seconds = Math.floor(diff / 1000);
|
|
251
|
+
if (seconds < 60)
|
|
252
|
+
return `${seconds}s ago`;
|
|
253
|
+
const minutes = Math.floor(seconds / 60);
|
|
254
|
+
if (minutes < 60)
|
|
255
|
+
return `${minutes}m ago`;
|
|
256
|
+
const hours = Math.floor(minutes / 60);
|
|
257
|
+
if (hours < 24)
|
|
258
|
+
return `${hours}h ${minutes % 60}m ago`;
|
|
259
|
+
const days = Math.floor(hours / 24);
|
|
260
|
+
return `${days}d ago`;
|
|
261
|
+
}
|
|
@@ -82,7 +82,7 @@ function customId(prefix, projectName, channelId) {
|
|
|
82
82
|
// ---------------------------------------------------------------------------
|
|
83
83
|
/** Build the approval notification message. */
|
|
84
84
|
function buildApprovalNotification(opts) {
|
|
85
|
-
const { title, description, projectName, channelId, toolNames, extraFields, hasAlwaysAllow, alwaysAllowText } = opts;
|
|
85
|
+
const { title, description, projectName, channelId, toolNames, extraFields, hasAlwaysAllow, alwaysAllowText, walkthroughCustomId, taskCustomId } = opts;
|
|
86
86
|
const richContent = (0, richContentBuilder_1.pipe)((0, richContentBuilder_1.createRichContent)(), (rc) => (0, richContentBuilder_1.withTitle)(rc, title), (rc) => (0, richContentBuilder_1.withDescription)(rc, description), (rc) => (0, richContentBuilder_1.withColor)(rc, COLOR_APPROVAL), (rc) => (0, richContentBuilder_1.addField)(rc, 'Project', projectName, true), (rc) => toolNames && toolNames.length > 0
|
|
87
87
|
? (0, richContentBuilder_1.addField)(rc, 'Tools', toolNames.join(', '), true)
|
|
88
88
|
: rc, (rc) => extraFields
|
|
@@ -98,6 +98,16 @@ function buildApprovalNotification(opts) {
|
|
|
98
98
|
const components = [
|
|
99
99
|
buttonRow(...buttons),
|
|
100
100
|
];
|
|
101
|
+
const artifactButtons = [];
|
|
102
|
+
if (walkthroughCustomId) {
|
|
103
|
+
artifactButtons.push(button(walkthroughCustomId, 'Review walkthrough.md', 'success'));
|
|
104
|
+
}
|
|
105
|
+
if (taskCustomId) {
|
|
106
|
+
artifactButtons.push(button(taskCustomId, 'Review task.md', 'primary'));
|
|
107
|
+
}
|
|
108
|
+
if (artifactButtons.length > 0) {
|
|
109
|
+
components.push(buttonRow(...artifactButtons));
|
|
110
|
+
}
|
|
101
111
|
return { richContent, components };
|
|
102
112
|
}
|
|
103
113
|
/** Build the planning mode notification message. */
|
|
@@ -107,8 +117,8 @@ function buildPlanningNotification(opts) {
|
|
|
107
117
|
? extraFields.reduce((acc, f) => (0, richContentBuilder_1.addField)(acc, f.name, f.value, f.inline), rc)
|
|
108
118
|
: rc, (rc) => (0, richContentBuilder_1.withFooter)(rc, 'Planning mode detected'), (rc) => (0, richContentBuilder_1.withTimestamp)(rc));
|
|
109
119
|
const buttons = [];
|
|
110
|
-
const openLabel = opts.openText || 'Open';
|
|
111
120
|
if (opts.hasOpenButton !== false) {
|
|
121
|
+
const openLabel = opts.openText || 'Open Plan';
|
|
112
122
|
buttons.push(button(customId(PLANNING_OPEN_ACTION_PREFIX, projectName, channelId), openLabel, 'primary'));
|
|
113
123
|
}
|
|
114
124
|
const buttonLabel = opts.proceedText || 'Proceed';
|
|
@@ -247,7 +247,7 @@ class PlanningDetector {
|
|
|
247
247
|
* @returns true if click succeeded
|
|
248
248
|
*/
|
|
249
249
|
async clickRejectButton(buttonText) {
|
|
250
|
-
const text = buttonText
|
|
250
|
+
const text = buttonText || this.lastDetectedInfo?.rejectText || 'Reject All';
|
|
251
251
|
return this.clickButton(text);
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PromptDispatcher = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
4
5
|
/**
|
|
5
6
|
* Dispatcher that calls the existing sendPromptToAntigravity.
|
|
6
7
|
* Unifies dependency injection on the caller side and simplifies event handlers.
|
|
7
8
|
*/
|
|
8
9
|
class PromptDispatcher {
|
|
9
10
|
deps;
|
|
11
|
+
activeMonitors = new Map();
|
|
10
12
|
constructor(deps) {
|
|
11
13
|
this.deps = deps;
|
|
12
14
|
}
|
|
@@ -17,7 +19,25 @@ class PromptDispatcher {
|
|
|
17
19
|
await this._dispatch(req, { ...req.options, resumeOnly: true });
|
|
18
20
|
}
|
|
19
21
|
async _dispatch(req, options) {
|
|
20
|
-
|
|
22
|
+
const channelId = req.message.channelId;
|
|
23
|
+
const existing = this.activeMonitors.get(channelId);
|
|
24
|
+
if (existing) {
|
|
25
|
+
logger_1.logger.info(`[PromptDispatcher] Aborting previous active monitor for channel ${channelId}`);
|
|
26
|
+
await existing.stop().catch((err) => logger_1.logger.error('[PromptDispatcher] Error stopping monitor:', err));
|
|
27
|
+
this.activeMonitors.delete(channelId);
|
|
28
|
+
}
|
|
29
|
+
const wrappedOptions = {
|
|
30
|
+
...(options || {}),
|
|
31
|
+
onMonitorCreated: (monitor) => {
|
|
32
|
+
this.activeMonitors.set(channelId, monitor);
|
|
33
|
+
options?.onMonitorCreated?.(monitor);
|
|
34
|
+
},
|
|
35
|
+
onFullCompletion: () => {
|
|
36
|
+
this.activeMonitors.delete(channelId);
|
|
37
|
+
options?.onFullCompletion?.();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
await this.deps.sendPromptImpl(this.deps.bridge, req.message, req.prompt, req.cdp, this.deps.modeService, this.deps.modelService, req.inboundImages ?? [], wrappedOptions);
|
|
21
41
|
}
|
|
22
42
|
}
|
|
23
43
|
exports.PromptDispatcher = PromptDispatcher;
|