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
package/dist/bot/index.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -7,6 +40,7 @@ exports.startBot = exports.getResponseDeliveryModeForTest = void 0;
|
|
|
7
40
|
exports.createSerialTaskQueueForTest = createSerialTaskQueueForTest;
|
|
8
41
|
const i18n_1 = require("../utils/i18n");
|
|
9
42
|
const logger_1 = require("../utils/logger");
|
|
43
|
+
const logBuffer_1 = require("../utils/logBuffer");
|
|
10
44
|
const discord_js_1 = require("discord.js");
|
|
11
45
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
12
46
|
const config_1 = require("../utils/config");
|
|
@@ -23,9 +57,11 @@ const chatCommandHandler_1 = require("../commands/chatCommandHandler");
|
|
|
23
57
|
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
24
58
|
const channelManager_1 = require("../services/channelManager");
|
|
25
59
|
const titleGeneratorService_1 = require("../services/titleGeneratorService");
|
|
60
|
+
const joinCommandHandler_1 = require("../commands/joinCommandHandler");
|
|
26
61
|
const chatSessionService_1 = require("../services/chatSessionService");
|
|
27
62
|
const responseMonitor_1 = require("../services/responseMonitor");
|
|
28
63
|
const antigravityLauncher_1 = require("../services/antigravityLauncher");
|
|
64
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
29
65
|
const promptDispatcher_1 = require("../services/promptDispatcher");
|
|
30
66
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
31
67
|
const streamMessageFormatter_1 = require("../utils/streamMessageFormatter");
|
|
@@ -36,7 +72,10 @@ const modeUi_1 = require("../ui/modeUi");
|
|
|
36
72
|
const modelsUi_1 = require("../ui/modelsUi");
|
|
37
73
|
const templateUi_1 = require("../ui/templateUi");
|
|
38
74
|
const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
75
|
+
const outputUi_1 = require("../ui/outputUi");
|
|
39
76
|
const screenshotUi_1 = require("../ui/screenshotUi");
|
|
77
|
+
const userPreferenceRepository_1 = require("../database/userPreferenceRepository");
|
|
78
|
+
const plainTextFormatter_1 = require("../utils/plainTextFormatter");
|
|
40
79
|
const interactionCreateHandler_1 = require("../events/interactionCreateHandler");
|
|
41
80
|
const messageCreateHandler_1 = require("../events/messageCreateHandler");
|
|
42
81
|
// =============================================================================
|
|
@@ -60,6 +99,8 @@ const PHASE_ICONS = {
|
|
|
60
99
|
};
|
|
61
100
|
const MAX_OUTBOUND_GENERATED_IMAGES = 4;
|
|
62
101
|
const RESPONSE_DELIVERY_MODE = (0, config_1.resolveResponseDeliveryMode)();
|
|
102
|
+
/** Tracks channel IDs where /stop was explicitly invoked by the user */
|
|
103
|
+
const userStopRequestedChannels = new Set();
|
|
63
104
|
const getResponseDeliveryModeForTest = () => RESPONSE_DELIVERY_MODE;
|
|
64
105
|
exports.getResponseDeliveryModeForTest = getResponseDeliveryModeForTest;
|
|
65
106
|
function createSerialTaskQueueForTest(queueName, traceId) {
|
|
@@ -92,6 +133,17 @@ function createSerialTaskQueueForTest(queueName, traceId) {
|
|
|
92
133
|
* - Visualize the flow of planning/analysis/execution confirmation/implementation as logs
|
|
93
134
|
*/
|
|
94
135
|
async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService, modelService, inboundImages = [], options) {
|
|
136
|
+
// Completion signal â called exactly once when the entire prompt lifecycle ends
|
|
137
|
+
let completionSignaled = false;
|
|
138
|
+
const signalCompletion = (exitPath) => {
|
|
139
|
+
if (completionSignaled)
|
|
140
|
+
return;
|
|
141
|
+
completionSignaled = true;
|
|
142
|
+
logger_1.logger.debug(`[sendPrompt:${message.channelId}] signalCompletion via ${exitPath}`);
|
|
143
|
+
options?.onFullCompletion?.();
|
|
144
|
+
};
|
|
145
|
+
// Resolve output format once at the start (no mid-response switches)
|
|
146
|
+
const outputFormat = options?.userPrefRepo?.getOutputFormat(message.author.id) ?? 'embed';
|
|
95
147
|
// Add reaction to acknowledge command receipt
|
|
96
148
|
await message.react('đ').catch(() => { });
|
|
97
149
|
const channel = (message.channel && 'send' in message.channel) ? message.channel : null;
|
|
@@ -102,6 +154,13 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
102
154
|
const sendEmbed = (title, description, color, fields, footerText) => enqueueGeneral(async () => {
|
|
103
155
|
if (!channel)
|
|
104
156
|
return;
|
|
157
|
+
if (outputFormat === 'plain') {
|
|
158
|
+
const chunks = (0, plainTextFormatter_1.formatAsPlainText)({ title, description, fields, footerText });
|
|
159
|
+
for (const chunk of chunks) {
|
|
160
|
+
await channel.send({ content: chunk }).catch(() => { });
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
105
164
|
const embed = new discord_js_1.EmbedBuilder()
|
|
106
165
|
.setTitle(title)
|
|
107
166
|
.setDescription(description)
|
|
@@ -221,9 +280,10 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
221
280
|
}
|
|
222
281
|
};
|
|
223
282
|
if (!cdp.isConnected()) {
|
|
224
|
-
await sendEmbed(`${PHASE_ICONS.error} Connection Error`,
|
|
283
|
+
await sendEmbed(`${PHASE_ICONS.error} Connection Error`, `Not connected to Antigravity.\nStart with \`${(0, pathUtils_1.getAntigravityCdpHint)(9223)}\`, then send a message to auto-connect.`, PHASE_COLORS.error);
|
|
225
284
|
await clearWatchingReaction();
|
|
226
285
|
await message.react('â').catch(() => { });
|
|
286
|
+
signalCompletion('cdp-disconnected');
|
|
227
287
|
return;
|
|
228
288
|
}
|
|
229
289
|
const localMode = modeService.getCurrentMode();
|
|
@@ -276,6 +336,30 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
276
336
|
return;
|
|
277
337
|
if (!channel)
|
|
278
338
|
return;
|
|
339
|
+
if (outputFormat === 'plain') {
|
|
340
|
+
const formatted = (0, discordFormatter_1.formatForDiscord)((rawText || '').trim());
|
|
341
|
+
const plainChunks = (0, plainTextFormatter_1.splitPlainText)(`**${title}**\n${formatted}\n_${footerText}_`);
|
|
342
|
+
const renderKey = `${title}|plain|${footerText}|${plainChunks.join('\n<<<PAGE_BREAK>>>\n')}`;
|
|
343
|
+
if (renderKey === lastLiveResponseKey && liveResponseMessages.length > 0)
|
|
344
|
+
return;
|
|
345
|
+
lastLiveResponseKey = renderKey;
|
|
346
|
+
for (let i = 0; i < plainChunks.length; i++) {
|
|
347
|
+
if (!liveResponseMessages[i]) {
|
|
348
|
+
liveResponseMessages[i] = await channel.send({ content: plainChunks[i] }).catch(() => null);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
await liveResponseMessages[i].edit({ content: plainChunks[i] }).catch(async () => {
|
|
352
|
+
liveResponseMessages[i] = await channel.send({ content: plainChunks[i] }).catch(() => null);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
while (liveResponseMessages.length > plainChunks.length) {
|
|
356
|
+
const extra = liveResponseMessages.pop();
|
|
357
|
+
if (!extra)
|
|
358
|
+
continue;
|
|
359
|
+
await extra.delete().catch(() => { });
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
279
363
|
const descriptions = buildLiveResponseDescriptions(rawText);
|
|
280
364
|
const renderKey = `${title}|${color}|${footerText}|${descriptions.join('\n<<<PAGE_BREAK>>>\n')}`;
|
|
281
365
|
if (renderKey === lastLiveResponseKey && liveResponseMessages.length > 0) {
|
|
@@ -312,6 +396,31 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
312
396
|
return;
|
|
313
397
|
if (!channel)
|
|
314
398
|
return;
|
|
399
|
+
if (outputFormat === 'plain') {
|
|
400
|
+
const formatted = (0, discordFormatter_1.formatForDiscord)((rawText || '').trim());
|
|
401
|
+
const plainContent = `**${title}**\n${formatted}\n_${footerText}_`;
|
|
402
|
+
const plainChunks = (0, plainTextFormatter_1.splitPlainText)(plainContent);
|
|
403
|
+
const renderKey = `${title}|plain|${footerText}|${plainChunks.join('\n<<<PAGE_BREAK>>>\n')}`;
|
|
404
|
+
if (renderKey === lastLiveActivityKey && liveActivityMessages.length > 0)
|
|
405
|
+
return;
|
|
406
|
+
lastLiveActivityKey = renderKey;
|
|
407
|
+
for (let i = 0; i < plainChunks.length; i++) {
|
|
408
|
+
if (!liveActivityMessages[i]) {
|
|
409
|
+
liveActivityMessages[i] = await channel.send({ content: plainChunks[i] }).catch(() => null);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
await liveActivityMessages[i].edit({ content: plainChunks[i] }).catch(async () => {
|
|
413
|
+
liveActivityMessages[i] = await channel.send({ content: plainChunks[i] }).catch(() => null);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
while (liveActivityMessages.length > plainChunks.length) {
|
|
417
|
+
const extra = liveActivityMessages.pop();
|
|
418
|
+
if (!extra)
|
|
419
|
+
continue;
|
|
420
|
+
await extra.delete().catch(() => { });
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
315
424
|
const descriptions = buildLiveActivityDescriptions(rawText);
|
|
316
425
|
const renderKey = `${title}|${color}|${footerText}|${descriptions.join('\n<<<PAGE_BREAK>>>\n')}`;
|
|
317
426
|
if (renderKey === lastLiveActivityKey && liveActivityMessages.length > 0) {
|
|
@@ -357,6 +466,7 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
357
466
|
await sendEmbed(`${PHASE_ICONS.error} Message Injection Failed`, `Failed to send message: ${injectResult.error}`, PHASE_COLORS.error);
|
|
358
467
|
await clearWatchingReaction();
|
|
359
468
|
await message.react('â').catch(() => { });
|
|
469
|
+
signalCompletion('inject-failed');
|
|
360
470
|
return;
|
|
361
471
|
}
|
|
362
472
|
const startTime = Date.now();
|
|
@@ -387,7 +497,7 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
387
497
|
onProgress: (text) => {
|
|
388
498
|
if (isFinalized)
|
|
389
499
|
return;
|
|
390
|
-
//
|
|
500
|
+
// Live output streaming disabled: RESPONSE_TEXT currently includes process logs (see #1).
|
|
391
501
|
const separated = (0, discordFormatter_1.splitOutputAndLogs)(text);
|
|
392
502
|
if (separated.output && separated.output.trim().length > 0) {
|
|
393
503
|
lastProgressText = separated.output;
|
|
@@ -396,86 +506,126 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
396
506
|
onComplete: async (finalText) => {
|
|
397
507
|
isFinalized = true;
|
|
398
508
|
try {
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const finalResponseText = responseText && responseText.trim().length > 0
|
|
407
|
-
? responseText
|
|
408
|
-
: emergencyText;
|
|
409
|
-
const separated = (0, discordFormatter_1.splitOutputAndLogs)(finalResponseText);
|
|
410
|
-
const finalOutputText = separated.output || finalResponseText;
|
|
411
|
-
// Process logs are now collected by onProcessLog callback directly;
|
|
412
|
-
// sanitizeActivityLines is NOT applied because it would strip the very
|
|
413
|
-
// content we want to display (activity messages, tool names, etc.)
|
|
414
|
-
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
415
|
-
if (finalLogText && finalLogText.trim().length > 0) {
|
|
416
|
-
logger_1.logger.divider('Process Log');
|
|
417
|
-
console.info(finalLogText);
|
|
418
|
-
}
|
|
419
|
-
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
420
|
-
logger_1.logger.divider(`Output (${finalOutputText.length} chars)`);
|
|
421
|
-
console.info(finalOutputText);
|
|
422
|
-
}
|
|
423
|
-
logger_1.logger.divider();
|
|
424
|
-
liveActivityUpdateVersion += 1;
|
|
425
|
-
const activityVersion = liveActivityUpdateVersion;
|
|
426
|
-
await upsertLiveActivityEmbeds(`${PHASE_ICONS.thinking} Process Log`, finalLogText || ACTIVITY_PLACEHOLDER, PHASE_COLORS.thinking, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Process log`), {
|
|
427
|
-
source: 'complete',
|
|
428
|
-
expectedVersion: activityVersion,
|
|
429
|
-
});
|
|
430
|
-
liveResponseUpdateVersion += 1;
|
|
431
|
-
const responseVersion = liveResponseUpdateVersion;
|
|
432
|
-
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
433
|
-
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Final Output`, finalOutputText, PHASE_COLORS.complete, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Complete`), {
|
|
434
|
-
source: 'complete',
|
|
435
|
-
expectedVersion: responseVersion,
|
|
436
|
-
});
|
|
509
|
+
// If the user explicitly pressed /stop, skip output display entirely
|
|
510
|
+
const wasStoppedByUser = userStopRequestedChannels.delete(message.channelId);
|
|
511
|
+
if (wasStoppedByUser) {
|
|
512
|
+
logger_1.logger.info(`[sendPromptToAntigravity:${monitorTraceId}] Stopped by user â skipping output`);
|
|
513
|
+
await clearWatchingReaction();
|
|
514
|
+
await message.react('âšī¸').catch(() => { });
|
|
515
|
+
return;
|
|
437
516
|
}
|
|
438
|
-
|
|
439
|
-
|
|
517
|
+
try {
|
|
518
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
519
|
+
const isQuotaError = monitor.getPhase() === 'quotaReached' || monitor.getQuotaDetected();
|
|
520
|
+
// Quota early exit â skip text extraction, output logging, and embed entirely
|
|
521
|
+
if (isQuotaError) {
|
|
522
|
+
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
523
|
+
if (finalLogText && finalLogText.trim().length > 0) {
|
|
524
|
+
logger_1.logger.divider('Process Log');
|
|
525
|
+
console.info(finalLogText);
|
|
526
|
+
}
|
|
527
|
+
logger_1.logger.divider();
|
|
528
|
+
liveActivityUpdateVersion += 1;
|
|
529
|
+
await upsertLiveActivityEmbeds(`${PHASE_ICONS.thinking} Process Log`, finalLogText || ACTIVITY_PLACEHOLDER, PHASE_COLORS.thinking, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Process log`), {
|
|
530
|
+
source: 'complete',
|
|
531
|
+
expectedVersion: liveActivityUpdateVersion,
|
|
532
|
+
});
|
|
533
|
+
liveResponseUpdateVersion += 1;
|
|
534
|
+
await upsertLiveResponseEmbeds('â ī¸ Model Quota Reached', 'Model quota limit reached. Please wait or switch to a different model.', 0xFF6B6B, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Quota Reached`), {
|
|
535
|
+
source: 'complete',
|
|
536
|
+
expectedVersion: liveResponseUpdateVersion,
|
|
537
|
+
});
|
|
538
|
+
try {
|
|
539
|
+
const modelsPayload = await (0, modelsUi_1.buildModelsUI)(cdp, () => bridge.quota.fetchQuota());
|
|
540
|
+
if (modelsPayload && channel) {
|
|
541
|
+
await channel.send({ ...modelsPayload });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (e) {
|
|
545
|
+
logger_1.logger.error('[Quota] Failed to send model selection UI:', e);
|
|
546
|
+
}
|
|
547
|
+
await clearWatchingReaction();
|
|
548
|
+
await message.react('â ī¸').catch(() => { });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
// Normal path â extract final text
|
|
552
|
+
const responseText = (finalText && finalText.trim().length > 0)
|
|
553
|
+
? finalText
|
|
554
|
+
: lastProgressText;
|
|
555
|
+
const emergencyText = (!responseText || responseText.trim().length === 0)
|
|
556
|
+
? await tryEmergencyExtractText()
|
|
557
|
+
: '';
|
|
558
|
+
const finalResponseText = responseText && responseText.trim().length > 0
|
|
559
|
+
? responseText
|
|
560
|
+
: emergencyText;
|
|
561
|
+
const separated = (0, discordFormatter_1.splitOutputAndLogs)(finalResponseText);
|
|
562
|
+
const finalOutputText = separated.output || finalResponseText;
|
|
563
|
+
// Process logs are now collected by onProcessLog callback directly;
|
|
564
|
+
// sanitizeActivityLines is NOT applied because it would strip the very
|
|
565
|
+
// content we want to display (activity messages, tool names, etc.)
|
|
566
|
+
const finalLogText = lastActivityLogText || processLogBuffer.snapshot();
|
|
567
|
+
if (finalLogText && finalLogText.trim().length > 0) {
|
|
568
|
+
logger_1.logger.divider('Process Log');
|
|
569
|
+
console.info(finalLogText);
|
|
570
|
+
}
|
|
571
|
+
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
572
|
+
logger_1.logger.divider(`Output (${finalOutputText.length} chars)`);
|
|
573
|
+
console.info(finalOutputText);
|
|
574
|
+
}
|
|
575
|
+
logger_1.logger.divider();
|
|
576
|
+
liveActivityUpdateVersion += 1;
|
|
577
|
+
const activityVersion = liveActivityUpdateVersion;
|
|
578
|
+
await upsertLiveActivityEmbeds(`${PHASE_ICONS.thinking} Process Log`, finalLogText || ACTIVITY_PLACEHOLDER, PHASE_COLORS.thinking, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Process log`), {
|
|
440
579
|
source: 'complete',
|
|
441
|
-
expectedVersion:
|
|
580
|
+
expectedVersion: activityVersion,
|
|
442
581
|
});
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
582
|
+
liveResponseUpdateVersion += 1;
|
|
583
|
+
const responseVersion = liveResponseUpdateVersion;
|
|
584
|
+
if (finalOutputText && finalOutputText.trim().length > 0) {
|
|
585
|
+
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Final Output`, finalOutputText, PHASE_COLORS.complete, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Complete`), {
|
|
586
|
+
source: 'complete',
|
|
587
|
+
expectedVersion: responseVersion,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
await upsertLiveResponseEmbeds(`${PHASE_ICONS.complete} Complete`, (0, i18n_1.t)('Failed to extract response. Use `/screenshot` to verify.'), PHASE_COLORS.complete, (0, i18n_1.t)(`âąī¸ Time: ${elapsed}s | Complete`), {
|
|
592
|
+
source: 'complete',
|
|
593
|
+
expectedVersion: responseVersion,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (options && message.guild) {
|
|
597
|
+
try {
|
|
598
|
+
const sessionInfo = await options.chatSessionService.getCurrentSessionInfo(cdp);
|
|
599
|
+
if (sessionInfo && sessionInfo.hasActiveChat && sessionInfo.title && sessionInfo.title !== (0, i18n_1.t)('(Untitled)')) {
|
|
600
|
+
const session = options.chatSessionRepo.findByChannelId(message.channelId);
|
|
601
|
+
const projectName = session
|
|
602
|
+
? bridge.pool.extractProjectName(session.workspacePath)
|
|
603
|
+
: cdp.getCurrentWorkspaceName();
|
|
604
|
+
if (projectName) {
|
|
605
|
+
(0, cdpBridgeManager_1.registerApprovalSessionChannel)(bridge, projectName, sessionInfo.title, message.channel);
|
|
606
|
+
}
|
|
607
|
+
const newName = options.titleGenerator.sanitizeForChannelName(sessionInfo.title);
|
|
608
|
+
if (session && session.displayName !== sessionInfo.title) {
|
|
609
|
+
const formattedName = `${session.sessionNumber}-${newName}`;
|
|
610
|
+
await options.channelManager.renameChannel(message.guild, message.channelId, formattedName);
|
|
611
|
+
options.chatSessionRepo.updateDisplayName(message.channelId, sessionInfo.title);
|
|
612
|
+
}
|
|
460
613
|
}
|
|
461
614
|
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
logger_1.logger.error('[Rename] Failed to get title from Antigravity and rename:', e);
|
|
617
|
+
}
|
|
462
618
|
}
|
|
463
|
-
|
|
464
|
-
logger_1.logger.error('[Rename] Failed to get title from Antigravity and rename:', e);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (monitor.getPhase() === 'quotaReached' || monitor.getQuotaDetected()) {
|
|
468
|
-
await sendEmbed('â ī¸ Model Quota Reached', 'Model quota limit reached. Please wait or switch to a different model with `/model`.', 0xFF6B6B, undefined, 'Quota Reached â consider switching models');
|
|
619
|
+
await sendGeneratedImages(finalOutputText || '');
|
|
469
620
|
await clearWatchingReaction();
|
|
470
|
-
await message.react('â ī¸').catch(() => { });
|
|
471
|
-
|
|
621
|
+
await message.react(finalOutputText && finalOutputText.trim().length > 0 ? 'â
' : 'â ī¸').catch(() => { });
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
logger_1.logger.error(`[sendPromptToAntigravity:${monitorTraceId}] onComplete failed:`, error);
|
|
472
625
|
}
|
|
473
|
-
await sendGeneratedImages(finalOutputText || '');
|
|
474
|
-
await clearWatchingReaction();
|
|
475
|
-
await message.react(finalOutputText && finalOutputText.trim().length > 0 ? 'â
' : 'â ī¸').catch(() => { });
|
|
476
626
|
}
|
|
477
|
-
|
|
478
|
-
|
|
627
|
+
finally {
|
|
628
|
+
signalCompletion('onComplete');
|
|
479
629
|
}
|
|
480
630
|
},
|
|
481
631
|
onTimeout: async (lastText) => {
|
|
@@ -508,27 +658,48 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
|
|
|
508
658
|
catch (error) {
|
|
509
659
|
logger_1.logger.error(`[sendPromptToAntigravity:${monitorTraceId}] onTimeout failed:`, error);
|
|
510
660
|
}
|
|
661
|
+
finally {
|
|
662
|
+
signalCompletion('onTimeout');
|
|
663
|
+
}
|
|
511
664
|
},
|
|
512
665
|
});
|
|
513
666
|
await monitor.start();
|
|
667
|
+
// 1-second elapsed timer â updates footer independently of process log events
|
|
668
|
+
const elapsedTimer = setInterval(() => {
|
|
669
|
+
if (isFinalized) {
|
|
670
|
+
clearInterval(elapsedTimer);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
674
|
+
liveActivityUpdateVersion += 1;
|
|
675
|
+
const activityVersion = liveActivityUpdateVersion;
|
|
676
|
+
upsertLiveActivityEmbeds(`${PHASE_ICONS.thinking} Process Log`, lastActivityLogText || ACTIVITY_PLACEHOLDER, PHASE_COLORS.thinking, (0, i18n_1.t)(`âąī¸ Elapsed: ${elapsed}s | Process log`), {
|
|
677
|
+
source: 'elapsed-tick',
|
|
678
|
+
expectedVersion: activityVersion,
|
|
679
|
+
skipWhenFinalized: true,
|
|
680
|
+
}).catch(() => { });
|
|
681
|
+
}, 1000);
|
|
514
682
|
}
|
|
515
683
|
catch (e) {
|
|
516
684
|
isFinalized = true;
|
|
517
685
|
await sendEmbed(`${PHASE_ICONS.error} Error`, (0, i18n_1.t)(`Error occurred during processing: ${e.message}`), PHASE_COLORS.error);
|
|
518
686
|
await clearWatchingReaction();
|
|
519
687
|
await message.react('â').catch(() => { });
|
|
688
|
+
signalCompletion('top-level-catch');
|
|
520
689
|
}
|
|
521
690
|
}
|
|
522
691
|
// =============================================================================
|
|
523
692
|
// Bot main entry point
|
|
524
693
|
// =============================================================================
|
|
525
|
-
const startBot = async () => {
|
|
694
|
+
const startBot = async (cliLogLevel) => {
|
|
526
695
|
const config = (0, config_1.loadConfig)();
|
|
696
|
+
logger_1.logger.setLogLevel(cliLogLevel ?? config.logLevel);
|
|
527
697
|
const dbPath = process.env.NODE_ENV === 'test' ? ':memory:' : 'antigravity.db';
|
|
528
698
|
const db = new better_sqlite3_1.default(dbPath);
|
|
529
699
|
const modeService = new modeService_1.ModeService();
|
|
530
700
|
const modelService = new modelService_1.ModelService();
|
|
531
701
|
const templateRepo = new templateRepository_1.TemplateRepository(db);
|
|
702
|
+
const userPrefRepo = new userPreferenceRepository_1.UserPreferenceRepository(db);
|
|
532
703
|
const workspaceBindingRepo = new workspaceBindingRepository_1.WorkspaceBindingRepository(db);
|
|
533
704
|
const chatSessionRepo = new chatSessionRepository_1.ChatSessionRepository(db);
|
|
534
705
|
const workspaceService = new workspaceService_1.WorkspaceService(config.workspaceBaseDir);
|
|
@@ -546,7 +717,7 @@ const startBot = async () => {
|
|
|
546
717
|
modelService,
|
|
547
718
|
sendPromptImpl: sendPromptToAntigravity,
|
|
548
719
|
});
|
|
549
|
-
// Initialize command handlers
|
|
720
|
+
// Initialize command handlers (joinHandler is created after client, see below)
|
|
550
721
|
const wsHandler = new workspaceCommandHandler_1.WorkspaceCommandHandler(workspaceBindingRepo, chatSessionRepo, workspaceService, channelManager);
|
|
551
722
|
const chatHandler = new chatCommandHandler_1.ChatCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, workspaceService, bridge.pool);
|
|
552
723
|
const cleanupHandler = new cleanupCommandHandler_1.CleanupCommandHandler(chatSessionRepo, workspaceBindingRepo);
|
|
@@ -558,14 +729,45 @@ const startBot = async () => {
|
|
|
558
729
|
discord_js_1.GatewayIntentBits.MessageContent,
|
|
559
730
|
]
|
|
560
731
|
});
|
|
732
|
+
const joinHandler = new joinCommandHandler_1.JoinCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, bridge.pool, workspaceService, client);
|
|
561
733
|
client.once(discord_js_1.Events.ClientReady, async (readyClient) => {
|
|
562
|
-
logger_1.logger.info(`Ready! Logged in as ${readyClient.user.tag}`);
|
|
734
|
+
logger_1.logger.info(`Ready! Logged in as ${readyClient.user.tag} | extractionMode=${config.extractionMode}`);
|
|
563
735
|
try {
|
|
564
736
|
await (0, registerSlashCommands_1.registerSlashCommands)(config.discordToken, config.clientId, config.guildId);
|
|
565
737
|
}
|
|
566
738
|
catch (error) {
|
|
567
739
|
logger_1.logger.warn('Failed to register slash commands, but text commands remain available.');
|
|
568
740
|
}
|
|
741
|
+
// Startup dashboard embed
|
|
742
|
+
try {
|
|
743
|
+
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
744
|
+
const pkg = await Promise.resolve().then(() => __importStar(require('../../package.json')));
|
|
745
|
+
const version = pkg.default?.version ?? pkg.version ?? 'unknown';
|
|
746
|
+
const projects = workspaceService.scanWorkspaces();
|
|
747
|
+
// Check CDP connection status
|
|
748
|
+
const activeWorkspaces = bridge.pool.getActiveWorkspaceNames();
|
|
749
|
+
const cdpStatus = activeWorkspaces.length > 0
|
|
750
|
+
? `Connected (${activeWorkspaces.join(', ')})`
|
|
751
|
+
: 'Not connected';
|
|
752
|
+
const dashboardEmbed = new discord_js_1.EmbedBuilder()
|
|
753
|
+
.setTitle('LazyGravity Online')
|
|
754
|
+
.setColor(0x57F287)
|
|
755
|
+
.addFields({ name: 'Version', value: version, inline: true }, { name: 'Node.js', value: process.versions.node, inline: true }, { name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true }, { name: 'CDP', value: cdpStatus, inline: true }, { name: 'Model', value: modelService.getCurrentModel(), inline: true }, { name: 'Mode', value: modeService.getCurrentMode(), inline: true }, { name: 'Projects', value: `${projects.length} registered`, inline: true }, { name: 'Extraction', value: config.extractionMode, inline: true })
|
|
756
|
+
.setFooter({ text: `Started at ${new Date().toLocaleString()}` })
|
|
757
|
+
.setTimestamp();
|
|
758
|
+
// Send to the first available text channel in the guild
|
|
759
|
+
const guild = readyClient.guilds.cache.first();
|
|
760
|
+
if (guild) {
|
|
761
|
+
const channel = guild.channels.cache.find((ch) => ch.isTextBased() && !ch.isVoiceBased() && ch.permissionsFor(readyClient.user)?.has('SendMessages'));
|
|
762
|
+
if (channel && channel.isTextBased()) {
|
|
763
|
+
await channel.send({ embeds: [dashboardEmbed] });
|
|
764
|
+
logger_1.logger.info('Startup dashboard embed sent.');
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
logger_1.logger.warn('Failed to send startup dashboard embed:', error);
|
|
770
|
+
}
|
|
569
771
|
});
|
|
570
772
|
// [Discord Interactions API] Slash command interaction handler
|
|
571
773
|
client.on(discord_js_1.Events.InteractionCreate, (0, interactionCreateHandler_1.createInteractionCreateHandler)({
|
|
@@ -583,7 +785,11 @@ const startBot = async () => {
|
|
|
583
785
|
sendAutoAcceptUI: autoAcceptUi_1.sendAutoAcceptUI,
|
|
584
786
|
getCurrentCdp: cdpBridgeManager_1.getCurrentCdp,
|
|
585
787
|
parseApprovalCustomId: cdpBridgeManager_1.parseApprovalCustomId,
|
|
586
|
-
|
|
788
|
+
parseErrorPopupCustomId: cdpBridgeManager_1.parseErrorPopupCustomId,
|
|
789
|
+
parsePlanningCustomId: cdpBridgeManager_1.parsePlanningCustomId,
|
|
790
|
+
joinHandler,
|
|
791
|
+
userPrefRepo,
|
|
792
|
+
handleSlashInteraction: async (interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg) => handleSlashInteraction(interaction, handler, bridgeArg, wsHandlerArg, chatHandlerArg, cleanupHandlerArg, modeServiceArg, modelServiceArg, autoAcceptServiceArg, clientArg, promptDispatcher, templateRepo, joinHandler, userPrefRepo),
|
|
587
793
|
handleTemplateUse: async (interaction, templateId) => {
|
|
588
794
|
const template = templateRepo.findById(templateId);
|
|
589
795
|
if (!template) {
|
|
@@ -600,15 +806,17 @@ const startBot = async () => {
|
|
|
600
806
|
if (workspacePath) {
|
|
601
807
|
try {
|
|
602
808
|
cdp = await bridge.pool.getOrConnect(workspacePath);
|
|
603
|
-
const
|
|
604
|
-
bridge.lastActiveWorkspace =
|
|
809
|
+
const projectName = bridge.pool.extractProjectName(workspacePath);
|
|
810
|
+
bridge.lastActiveWorkspace = projectName;
|
|
605
811
|
bridge.lastActiveChannel = interaction.channel;
|
|
606
|
-
(0, cdpBridgeManager_1.registerApprovalWorkspaceChannel)(bridge,
|
|
812
|
+
(0, cdpBridgeManager_1.registerApprovalWorkspaceChannel)(bridge, projectName, interaction.channel);
|
|
607
813
|
const session = chatSessionRepo.findByChannelId(channelId);
|
|
608
814
|
if (session?.displayName) {
|
|
609
|
-
(0, cdpBridgeManager_1.registerApprovalSessionChannel)(bridge,
|
|
815
|
+
(0, cdpBridgeManager_1.registerApprovalSessionChannel)(bridge, projectName, session.displayName, interaction.channel);
|
|
610
816
|
}
|
|
611
|
-
(0, cdpBridgeManager_1.ensureApprovalDetector)(bridge, cdp,
|
|
817
|
+
(0, cdpBridgeManager_1.ensureApprovalDetector)(bridge, cdp, projectName, client);
|
|
818
|
+
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(bridge, cdp, projectName, client);
|
|
819
|
+
(0, cdpBridgeManager_1.ensurePlanningDetector)(bridge, cdp, projectName, client);
|
|
612
820
|
}
|
|
613
821
|
catch (e) {
|
|
614
822
|
await interaction.followUp({
|
|
@@ -642,6 +850,7 @@ const startBot = async () => {
|
|
|
642
850
|
chatSessionRepo,
|
|
643
851
|
channelManager,
|
|
644
852
|
titleGenerator,
|
|
853
|
+
userPrefRepo,
|
|
645
854
|
},
|
|
646
855
|
});
|
|
647
856
|
}
|
|
@@ -669,6 +878,7 @@ const startBot = async () => {
|
|
|
669
878
|
}),
|
|
670
879
|
autoRenameChannel,
|
|
671
880
|
handleScreenshot: screenshotUi_1.handleScreenshot,
|
|
881
|
+
userPrefRepo,
|
|
672
882
|
}));
|
|
673
883
|
await client.login(config.discordToken);
|
|
674
884
|
};
|
|
@@ -696,55 +906,82 @@ async function autoRenameChannel(message, chatSessionRepo, titleGenerator, chann
|
|
|
696
906
|
/**
|
|
697
907
|
* Handle Discord Interactions API slash commands
|
|
698
908
|
*/
|
|
699
|
-
async function handleSlashInteraction(interaction, handler, bridge, wsHandler, chatHandler, cleanupHandler, modeService, modelService, autoAcceptService, _client, promptDispatcher, templateRepo) {
|
|
909
|
+
async function handleSlashInteraction(interaction, handler, bridge, wsHandler, chatHandler, cleanupHandler, modeService, modelService, autoAcceptService, _client, promptDispatcher, templateRepo, joinHandler, userPrefRepo) {
|
|
700
910
|
const commandName = interaction.commandName;
|
|
701
911
|
switch (commandName) {
|
|
702
912
|
case 'help': {
|
|
913
|
+
const helpFields = [
|
|
914
|
+
{
|
|
915
|
+
name: 'đŦ Chat', value: [
|
|
916
|
+
'`/new` â Start a new chat session',
|
|
917
|
+
'`/chat` â Show current session info + list',
|
|
918
|
+
].join('\n')
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
name: 'đ Session', value: [
|
|
922
|
+
'`/join` â Join an existing Antigravity session',
|
|
923
|
+
'`/mirror` â Toggle PCâDiscord mirroring ON/OFF',
|
|
924
|
+
].join('\n')
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
name: 'âšī¸ Control', value: [
|
|
928
|
+
'`/stop` â Interrupt active LLM generation',
|
|
929
|
+
'`/screenshot` â Capture Antigravity screen',
|
|
930
|
+
].join('\n')
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: 'âī¸ Settings', value: [
|
|
934
|
+
'`/mode` â Display and change execution mode',
|
|
935
|
+
'`/model [name]` â Display and change LLM model',
|
|
936
|
+
'`/output [format]` â Toggle Embed / Plain Text output',
|
|
937
|
+
].join('\n')
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: 'đ Projects', value: [
|
|
941
|
+
'`/project` â Display project list',
|
|
942
|
+
'`/project create <name>` â Create a new project',
|
|
943
|
+
].join('\n')
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
name: 'đ Templates', value: [
|
|
947
|
+
'`/template list` â Show templates with execute buttons (click to run)',
|
|
948
|
+
'`/template add <name> <prompt>` â Register a template',
|
|
949
|
+
'`/template delete <name>` â Delete a template',
|
|
950
|
+
].join('\n')
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: 'đ§ System', value: [
|
|
954
|
+
'`/status` â Display overall bot status',
|
|
955
|
+
'`/autoaccept` â Toggle auto-approve mode for approval dialogs via buttons',
|
|
956
|
+
'`/logs [lines] [level]` â View recent bot logs',
|
|
957
|
+
'`/cleanup [days]` â Clean up unused channels/categories',
|
|
958
|
+
'`/help` â Show this help',
|
|
959
|
+
].join('\n')
|
|
960
|
+
},
|
|
961
|
+
];
|
|
962
|
+
const helpOutputFormat = userPrefRepo?.getOutputFormat(interaction.user.id) ?? 'embed';
|
|
963
|
+
if (helpOutputFormat === 'plain') {
|
|
964
|
+
const chunks = (0, plainTextFormatter_1.formatAsPlainText)({
|
|
965
|
+
title: 'đ LazyGravity Commands',
|
|
966
|
+
description: 'Commands for controlling Antigravity from Discord.',
|
|
967
|
+
fields: helpFields,
|
|
968
|
+
footerText: 'Text messages are sent directly to Antigravity',
|
|
969
|
+
});
|
|
970
|
+
await interaction.editReply({ content: chunks[0] });
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
703
973
|
const embed = new discord_js_1.EmbedBuilder()
|
|
704
974
|
.setTitle('đ LazyGravity Commands')
|
|
705
975
|
.setColor(0x5865F2)
|
|
706
976
|
.setDescription('Commands for controlling Antigravity from Discord.')
|
|
707
|
-
.addFields(
|
|
708
|
-
name: 'đŦ Chat', value: [
|
|
709
|
-
'`/new` â Start a new chat session',
|
|
710
|
-
'`/chat` â Show current session info + list',
|
|
711
|
-
].join('\n')
|
|
712
|
-
}, {
|
|
713
|
-
name: 'âšī¸ Control', value: [
|
|
714
|
-
'`/stop` â Interrupt active LLM generation',
|
|
715
|
-
'`/screenshot` â Capture Antigravity screen',
|
|
716
|
-
].join('\n')
|
|
717
|
-
}, {
|
|
718
|
-
name: 'âī¸ Settings', value: [
|
|
719
|
-
'`/mode` â Display and change execution mode',
|
|
720
|
-
'`/model [name]` â Display and change LLM model',
|
|
721
|
-
].join('\n')
|
|
722
|
-
}, {
|
|
723
|
-
name: 'đ Projects', value: [
|
|
724
|
-
'`/project` â Display project list',
|
|
725
|
-
'`/project create <name>` â Create a new project',
|
|
726
|
-
].join('\n')
|
|
727
|
-
}, {
|
|
728
|
-
name: 'đ Templates', value: [
|
|
729
|
-
'`/template list` â Show templates with execute buttons (click to run)',
|
|
730
|
-
'`/template add <name> <prompt>` â Register a template',
|
|
731
|
-
'`/template delete <name>` â Delete a template',
|
|
732
|
-
].join('\n')
|
|
733
|
-
}, {
|
|
734
|
-
name: 'đ§ System', value: [
|
|
735
|
-
'`/status` â Display overall bot status',
|
|
736
|
-
'`/autoaccept` â Toggle auto-approve mode for approval dialogs via buttons',
|
|
737
|
-
'`/cleanup [days]` â Clean up unused channels/categories',
|
|
738
|
-
'`/help` â Show this help',
|
|
739
|
-
].join('\n')
|
|
740
|
-
})
|
|
977
|
+
.addFields(...helpFields)
|
|
741
978
|
.setFooter({ text: 'Text messages are sent directly to Antigravity' })
|
|
742
979
|
.setTimestamp();
|
|
743
980
|
await interaction.editReply({ embeds: [embed] });
|
|
744
981
|
break;
|
|
745
982
|
}
|
|
746
983
|
case 'mode': {
|
|
747
|
-
await (0, modeUi_1.sendModeUI)(interaction, modeService);
|
|
984
|
+
await (0, modeUi_1.sendModeUI)(interaction, modeService, { getCurrentCdp: () => (0, cdpBridgeManager_1.getCurrentCdp)(bridge) });
|
|
748
985
|
break;
|
|
749
986
|
}
|
|
750
987
|
case 'model': {
|
|
@@ -805,23 +1042,46 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
805
1042
|
return cdp ? 'CDP Connected' : 'Disconnected';
|
|
806
1043
|
})();
|
|
807
1044
|
const currentMode = modeService.getCurrentMode();
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1045
|
+
const mirroringWorkspaces = activeNames.filter((name) => bridge.pool.getUserMessageDetector(name)?.isActive());
|
|
1046
|
+
const mirrorStatus = mirroringWorkspaces.length > 0
|
|
1047
|
+
? `đĄ ON (${mirroringWorkspaces.join(', ')})`
|
|
1048
|
+
: 'âĒ OFF';
|
|
1049
|
+
const statusFields = [
|
|
1050
|
+
{ name: 'CDP Connection', value: activeNames.length > 0 ? `đĸ ${activeNames.length} project(s) connected` : 'âĒ Disconnected', inline: true },
|
|
1051
|
+
{ name: 'Mode', value: modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode, inline: true },
|
|
1052
|
+
{ name: 'Auto Approve', value: autoAcceptService.isEnabled() ? 'đĸ ON' : 'âĒ OFF', inline: true },
|
|
1053
|
+
{ name: 'Mirroring', value: mirrorStatus, inline: true },
|
|
1054
|
+
];
|
|
1055
|
+
let statusDescription = '';
|
|
813
1056
|
if (activeNames.length > 0) {
|
|
814
1057
|
const lines = activeNames.map((name) => {
|
|
815
1058
|
const cdp = bridge.pool.getConnected(name);
|
|
816
1059
|
const contexts = cdp ? cdp.getContexts().length : 0;
|
|
817
1060
|
const detectorActive = bridge.pool.getApprovalDetector(name)?.isActive() ? ' [Detecting]' : '';
|
|
818
|
-
|
|
1061
|
+
const mirrorActive = bridge.pool.getUserMessageDetector(name)?.isActive() ? ' [Mirror]' : '';
|
|
1062
|
+
return `âĸ **${name}** â Contexts: ${contexts}${detectorActive}${mirrorActive}`;
|
|
819
1063
|
});
|
|
820
|
-
|
|
1064
|
+
statusDescription = `**Connected Projects:**\n${lines.join('\n')}`;
|
|
821
1065
|
}
|
|
822
1066
|
else {
|
|
823
|
-
|
|
1067
|
+
statusDescription = 'Send a message to auto-connect to a project.';
|
|
1068
|
+
}
|
|
1069
|
+
const statusOutputFormat = userPrefRepo?.getOutputFormat(interaction.user.id) ?? 'embed';
|
|
1070
|
+
if (statusOutputFormat === 'plain') {
|
|
1071
|
+
const chunks = (0, plainTextFormatter_1.formatAsPlainText)({
|
|
1072
|
+
title: 'đ§ Bot Status',
|
|
1073
|
+
description: statusDescription,
|
|
1074
|
+
fields: statusFields,
|
|
1075
|
+
});
|
|
1076
|
+
await interaction.editReply({ content: chunks[0] });
|
|
1077
|
+
break;
|
|
824
1078
|
}
|
|
1079
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
1080
|
+
.setTitle('đ§ Bot Status')
|
|
1081
|
+
.setColor(activeNames.length > 0 ? 0x00CC88 : 0x888888)
|
|
1082
|
+
.addFields(...statusFields)
|
|
1083
|
+
.setDescription(statusDescription)
|
|
1084
|
+
.setTimestamp();
|
|
825
1085
|
await interaction.editReply({ embeds: [embed] });
|
|
826
1086
|
break;
|
|
827
1087
|
}
|
|
@@ -835,6 +1095,23 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
835
1095
|
await interaction.editReply({ content: result.message });
|
|
836
1096
|
break;
|
|
837
1097
|
}
|
|
1098
|
+
case 'output': {
|
|
1099
|
+
if (!userPrefRepo) {
|
|
1100
|
+
await interaction.editReply({ content: 'Output preference service not available.' });
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
const requestedFormat = interaction.options.getString('format');
|
|
1104
|
+
if (!requestedFormat) {
|
|
1105
|
+
const currentFormat = userPrefRepo.getOutputFormat(interaction.user.id);
|
|
1106
|
+
await (0, outputUi_1.sendOutputUI)(interaction, currentFormat);
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
const format = requestedFormat === 'plain' ? 'plain' : 'embed';
|
|
1110
|
+
userPrefRepo.setOutputFormat(interaction.user.id, format);
|
|
1111
|
+
const label = format === 'plain' ? 'Plain Text' : 'Embed';
|
|
1112
|
+
await interaction.editReply({ content: `Output format changed to **${label}**.` });
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
838
1115
|
case 'screenshot': {
|
|
839
1116
|
await (0, screenshotUi_1.handleScreenshot)(interaction, (0, cdpBridgeManager_1.getCurrentCdp)(bridge));
|
|
840
1117
|
break;
|
|
@@ -858,6 +1135,7 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
858
1135
|
const result = await cdp.call('Runtime.evaluate', callParams);
|
|
859
1136
|
const value = result?.result?.value;
|
|
860
1137
|
if (value?.ok) {
|
|
1138
|
+
userStopRequestedChannels.add(interaction.channelId);
|
|
861
1139
|
const embed = new discord_js_1.EmbedBuilder()
|
|
862
1140
|
.setTitle('âšī¸ Generation Interrupted')
|
|
863
1141
|
.setDescription('AI response generation was safely stopped.')
|
|
@@ -902,10 +1180,51 @@ async function handleSlashInteraction(interaction, handler, bridge, wsHandler, c
|
|
|
902
1180
|
await chatHandler.handleChat(interaction);
|
|
903
1181
|
break;
|
|
904
1182
|
}
|
|
1183
|
+
case 'join': {
|
|
1184
|
+
if (joinHandler) {
|
|
1185
|
+
await joinHandler.handleJoin(interaction, bridge);
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
await interaction.editReply({ content: (0, i18n_1.t)('â ī¸ Join handler not available.') });
|
|
1189
|
+
}
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
case 'mirror': {
|
|
1193
|
+
if (joinHandler) {
|
|
1194
|
+
await joinHandler.handleMirror(interaction, bridge);
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
await interaction.editReply({ content: (0, i18n_1.t)('â ī¸ Mirror handler not available.') });
|
|
1198
|
+
}
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
905
1201
|
case 'cleanup': {
|
|
906
1202
|
await cleanupHandler.handleCleanup(interaction);
|
|
907
1203
|
break;
|
|
908
1204
|
}
|
|
1205
|
+
case 'ping': {
|
|
1206
|
+
const apiLatency = interaction.client.ws.ping;
|
|
1207
|
+
await interaction.editReply({ content: `đ Pong! API Latency is **${apiLatency}ms**.` });
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
case 'logs': {
|
|
1211
|
+
const lines = interaction.options.getInteger('lines') ?? 50;
|
|
1212
|
+
const level = interaction.options.getString('level');
|
|
1213
|
+
const entries = logBuffer_1.logBuffer.getRecent(lines, level ?? undefined);
|
|
1214
|
+
if (entries.length === 0) {
|
|
1215
|
+
await interaction.editReply({ content: 'No log entries found.' });
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
const formatted = entries
|
|
1219
|
+
.map((e) => `${e.timestamp.slice(11, 19)} ${e.message}`)
|
|
1220
|
+
.join('\n');
|
|
1221
|
+
const MAX_CONTENT = 1900;
|
|
1222
|
+
const codeBlock = formatted.length <= MAX_CONTENT
|
|
1223
|
+
? `\`\`\`\n${formatted}\n\`\`\``
|
|
1224
|
+
: `\`\`\`\n${formatted.slice(0, MAX_CONTENT)}\n\`\`\`\n(truncated â showing ${MAX_CONTENT} chars of ${formatted.length})`;
|
|
1225
|
+
await interaction.editReply({ content: codeBlock });
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
909
1228
|
default:
|
|
910
1229
|
await interaction.editReply({
|
|
911
1230
|
content: `Unknown command: /${commandName}`,
|