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
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createMessageCreateHandler = createMessageCreateHandler;
|
|
4
4
|
const discord_js_1 = require("discord.js");
|
|
5
5
|
const messageParser_1 = require("../commands/messageParser");
|
|
6
|
+
const plainTextFormatter_1 = require("../utils/plainTextFormatter");
|
|
6
7
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
7
8
|
const modeService_1 = require("../services/modeService");
|
|
8
9
|
const imageHandler_1 = require("../utils/imageHandler");
|
|
@@ -10,11 +11,30 @@ const logger_1 = require("../utils/logger");
|
|
|
10
11
|
function createMessageCreateHandler(deps) {
|
|
11
12
|
const getCurrentCdp = deps.getCurrentCdp ?? cdpBridgeManager_1.getCurrentCdp;
|
|
12
13
|
const ensureApprovalDetector = deps.ensureApprovalDetector ?? cdpBridgeManager_1.ensureApprovalDetector;
|
|
14
|
+
const ensureErrorPopupDetector = deps.ensureErrorPopupDetector ?? cdpBridgeManager_1.ensureErrorPopupDetector;
|
|
15
|
+
const ensurePlanningDetector = deps.ensurePlanningDetector ?? cdpBridgeManager_1.ensurePlanningDetector;
|
|
13
16
|
const registerApprovalWorkspaceChannel = deps.registerApprovalWorkspaceChannel ?? cdpBridgeManager_1.registerApprovalWorkspaceChannel;
|
|
14
17
|
const registerApprovalSessionChannel = deps.registerApprovalSessionChannel ?? cdpBridgeManager_1.registerApprovalSessionChannel;
|
|
15
18
|
const downloadInboundImageAttachments = deps.downloadInboundImageAttachments ?? imageHandler_1.downloadInboundImageAttachments;
|
|
16
19
|
const cleanupInboundImageAttachments = deps.cleanupInboundImageAttachments ?? imageHandler_1.cleanupInboundImageAttachments;
|
|
17
20
|
const isImageAttachment = deps.isImageAttachment ?? imageHandler_1.isImageAttachment;
|
|
21
|
+
// Per-workspace prompt queue: serializes sendβresponse cycles
|
|
22
|
+
const workspaceQueues = new Map();
|
|
23
|
+
const workspaceQueueDepths = new Map();
|
|
24
|
+
function enqueueForWorkspace(workspacePath, task) {
|
|
25
|
+
// .catch: ensure a prior rejection never stalls the chain
|
|
26
|
+
const current = (workspaceQueues.get(workspacePath) ?? Promise.resolve()).catch(() => { });
|
|
27
|
+
const next = current.then(async () => {
|
|
28
|
+
try {
|
|
29
|
+
await task();
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
logger_1.logger.error('[WorkspaceQueue] task error:', err?.message || err);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
workspaceQueues.set(workspacePath, next);
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
18
38
|
return async (message) => {
|
|
19
39
|
if (message.author.bot)
|
|
20
40
|
return;
|
|
@@ -36,12 +56,12 @@ function createMessageCreateHandler(deps) {
|
|
|
36
56
|
if (parsed.commandName === 'status') {
|
|
37
57
|
const activeNames = deps.bridge.pool.getActiveWorkspaceNames();
|
|
38
58
|
const currentMode = deps.modeService.getCurrentMode();
|
|
39
|
-
const
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
const statusFields = [
|
|
60
|
+
{ name: 'CDP Connection', value: activeNames.length > 0 ? `π’ ${activeNames.length} project(s) connected` : 'βͺ Disconnected', inline: true },
|
|
61
|
+
{ name: 'Mode', value: modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode, inline: true },
|
|
62
|
+
{ name: 'Auto Approve', value: deps.bridge.autoAccept.isEnabled() ? 'π’ ON' : 'βͺ OFF', inline: true },
|
|
63
|
+
];
|
|
64
|
+
let statusDescription = '';
|
|
45
65
|
if (activeNames.length > 0) {
|
|
46
66
|
const lines = activeNames.map((name) => {
|
|
47
67
|
const cdp = deps.bridge.pool.getConnected(name);
|
|
@@ -49,15 +69,33 @@ function createMessageCreateHandler(deps) {
|
|
|
49
69
|
const detectorActive = deps.bridge.pool.getApprovalDetector(name)?.isActive() ? ' [Detecting]' : '';
|
|
50
70
|
return `β’ **${name}** β Contexts: ${contexts}${detectorActive}`;
|
|
51
71
|
});
|
|
52
|
-
|
|
72
|
+
statusDescription = `**Connected Projects:**\n${lines.join('\n')}`;
|
|
53
73
|
}
|
|
54
74
|
else {
|
|
55
|
-
|
|
75
|
+
statusDescription = 'Send a message to auto-connect to a project.';
|
|
56
76
|
}
|
|
77
|
+
const statusOutputFormat = deps.userPrefRepo?.getOutputFormat(message.author.id) ?? 'embed';
|
|
78
|
+
if (statusOutputFormat === 'plain') {
|
|
79
|
+
const chunks = (0, plainTextFormatter_1.formatAsPlainText)({
|
|
80
|
+
title: 'π§ Bot Status',
|
|
81
|
+
description: statusDescription,
|
|
82
|
+
fields: statusFields,
|
|
83
|
+
footerText: 'Use the slash command /status for more detailed information',
|
|
84
|
+
});
|
|
85
|
+
await message.reply({ content: chunks[0] });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
89
|
+
.setTitle('π§ Bot Status')
|
|
90
|
+
.setColor(activeNames.length > 0 ? 0x00CC88 : 0x888888)
|
|
91
|
+
.addFields(...statusFields)
|
|
92
|
+
.setDescription(statusDescription)
|
|
93
|
+
.setFooter({ text: 'π‘ Use the slash command /status for more detailed information' })
|
|
94
|
+
.setTimestamp();
|
|
57
95
|
await message.reply({ embeds: [embed] });
|
|
58
96
|
return;
|
|
59
97
|
}
|
|
60
|
-
const slashOnlyCommands = ['help', 'stop', 'model', 'mode', 'project', 'chat', 'new', 'cleanup'];
|
|
98
|
+
const slashOnlyCommands = ['help', 'stop', 'model', 'mode', 'project', 'chat', 'new', 'cleanup', 'join', 'mirror', 'output'];
|
|
61
99
|
if (slashOnlyCommands.includes(parsed.commandName)) {
|
|
62
100
|
await message.reply({
|
|
63
101
|
content: `π‘ Please use \`/${parsed.commandName}\` as a slash command.\nType \`/${parsed.commandName}\` in the Discord input field to see suggestions.`,
|
|
@@ -76,6 +114,7 @@ function createMessageCreateHandler(deps) {
|
|
|
76
114
|
chatSessionRepo: deps.chatSessionRepo,
|
|
77
115
|
channelManager: deps.channelManager,
|
|
78
116
|
titleGenerator: deps.titleGenerator,
|
|
117
|
+
userPrefRepo: deps.userPrefRepo,
|
|
79
118
|
});
|
|
80
119
|
}
|
|
81
120
|
else {
|
|
@@ -96,51 +135,126 @@ function createMessageCreateHandler(deps) {
|
|
|
96
135
|
const workspacePath = deps.wsHandler.getWorkspaceForChannel(message.channelId);
|
|
97
136
|
try {
|
|
98
137
|
if (workspacePath) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
const projectLabel = deps.bridge.pool.extractProjectName(workspacePath);
|
|
139
|
+
// Track queue depth for hourglass reactions
|
|
140
|
+
const currentDepth = workspaceQueueDepths.get(workspacePath) ?? 0;
|
|
141
|
+
workspaceQueueDepths.set(workspacePath, currentDepth + 1);
|
|
142
|
+
const newDepth = currentDepth + 1;
|
|
143
|
+
if (currentDepth > 0) {
|
|
144
|
+
logger_1.logger.info(`[Queue:${projectLabel}] Enqueued (depth: ${newDepth}, channel: ${message.channelId})`);
|
|
145
|
+
await message.react('β³').catch(() => { });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
logger_1.logger.info(`[Queue:${projectLabel}] Processing immediately (depth: ${newDepth}, channel: ${message.channelId})`);
|
|
149
|
+
}
|
|
150
|
+
const queueStartTime = Date.now();
|
|
151
|
+
await enqueueForWorkspace(workspacePath, async () => {
|
|
152
|
+
const waitMs = Date.now() - queueStartTime;
|
|
153
|
+
if (waitMs > 100) {
|
|
154
|
+
logger_1.logger.info(`[Queue:${projectLabel}] Task started after ${Math.round(waitMs / 1000)}s wait (channel: ${message.channelId})`);
|
|
109
155
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await message.reply(`β οΈ Could not route this message to the bound session (${session.displayName}). ` +
|
|
115
|
-
`Please open /chat and verify the session${reason}.`).catch(() => { });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
156
|
+
// Remove hourglass when task starts processing
|
|
157
|
+
const botId = message.client.user?.id;
|
|
158
|
+
if (botId) {
|
|
159
|
+
await message.reactions.resolve('β³')?.users.remove(botId).catch(() => { });
|
|
118
160
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
try {
|
|
162
|
+
const cdp = await deps.bridge.pool.getOrConnect(workspacePath);
|
|
163
|
+
const projectName = deps.bridge.pool.extractProjectName(workspacePath);
|
|
164
|
+
deps.bridge.lastActiveWorkspace = projectName;
|
|
165
|
+
deps.bridge.lastActiveChannel = message.channel;
|
|
166
|
+
registerApprovalWorkspaceChannel(deps.bridge, projectName, message.channel);
|
|
167
|
+
ensureApprovalDetector(deps.bridge, cdp, projectName, deps.client);
|
|
168
|
+
ensureErrorPopupDetector(deps.bridge, cdp, projectName, deps.client);
|
|
169
|
+
ensurePlanningDetector(deps.bridge, cdp, projectName, deps.client);
|
|
170
|
+
const session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
171
|
+
if (session?.displayName) {
|
|
172
|
+
registerApprovalSessionChannel(deps.bridge, projectName, session.displayName, message.channel);
|
|
173
|
+
}
|
|
174
|
+
if (session?.isRenamed && session.displayName) {
|
|
175
|
+
const activationResult = await deps.chatSessionService.activateSessionByTitle(cdp, session.displayName);
|
|
176
|
+
if (!activationResult.ok) {
|
|
177
|
+
const reason = activationResult.error ? ` (${activationResult.error})` : '';
|
|
178
|
+
await message.reply(`β οΈ Could not route this message to the bound session (${session.displayName}). ` +
|
|
179
|
+
`Please open /chat and verify the session${reason}.`).catch(() => { });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (session && !session.isRenamed) {
|
|
184
|
+
try {
|
|
185
|
+
const chatResult = await deps.chatSessionService.startNewChat(cdp);
|
|
186
|
+
if (!chatResult.ok) {
|
|
187
|
+
logger_1.logger.warn('[MessageCreate] Failed to start new chat in Antigravity:', chatResult.error);
|
|
188
|
+
message.channel.send(`β οΈ Could not open a new chat in Antigravity. Sending to existing chat.`).catch(() => { });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
logger_1.logger.error('[MessageCreate] startNewChat error:', err);
|
|
124
193
|
message.channel.send(`β οΈ Could not open a new chat in Antigravity. Sending to existing chat.`).catch(() => { });
|
|
125
194
|
}
|
|
126
195
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
await deps.autoRenameChannel(message, deps.chatSessionRepo, deps.titleGenerator, deps.channelManager, cdp);
|
|
197
|
+
// Re-register session channel after autoRenameChannel sets displayName
|
|
198
|
+
const updatedSession = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
199
|
+
if (updatedSession?.displayName) {
|
|
200
|
+
registerApprovalSessionChannel(deps.bridge, projectName, updatedSession.displayName, message.channel);
|
|
130
201
|
}
|
|
202
|
+
// Register echo hash so UserMessageDetector skips this message
|
|
203
|
+
const userMsgDetector = deps.bridge.pool.getUserMessageDetector?.(projectName);
|
|
204
|
+
if (userMsgDetector) {
|
|
205
|
+
userMsgDetector.addEchoHash(promptText);
|
|
206
|
+
}
|
|
207
|
+
// Wait for full response cycle (onComplete/onTimeout) before releasing the queue.
|
|
208
|
+
// Safety timeout (360s) prevents permanent queue deadlock if onFullCompletion
|
|
209
|
+
// is never called due to a bug.
|
|
210
|
+
const QUEUE_SAFETY_TIMEOUT_MS = 360_000;
|
|
211
|
+
const promptStartTime = Date.now();
|
|
212
|
+
await new Promise((resolve) => {
|
|
213
|
+
const safetyTimer = setTimeout(() => {
|
|
214
|
+
logger_1.logger.warn(`[Queue:${projectName}] Safety timeout β releasing queue after 360s ` +
|
|
215
|
+
`(channel: ${message.channelId})`);
|
|
216
|
+
resolve();
|
|
217
|
+
}, QUEUE_SAFETY_TIMEOUT_MS);
|
|
218
|
+
let settled = false;
|
|
219
|
+
const settle = () => {
|
|
220
|
+
if (settled)
|
|
221
|
+
return;
|
|
222
|
+
settled = true;
|
|
223
|
+
clearTimeout(safetyTimer);
|
|
224
|
+
const elapsed = Math.round((Date.now() - promptStartTime) / 1000);
|
|
225
|
+
logger_1.logger.info(`[Queue:${projectName}] Prompt completed in ${elapsed}s ` +
|
|
226
|
+
`(channel: ${message.channelId})`);
|
|
227
|
+
resolve();
|
|
228
|
+
};
|
|
229
|
+
deps.sendPromptToAntigravity(deps.bridge, message, promptText, cdp, deps.modeService, deps.modelService, inboundImages, {
|
|
230
|
+
chatSessionService: deps.chatSessionService,
|
|
231
|
+
chatSessionRepo: deps.chatSessionRepo,
|
|
232
|
+
channelManager: deps.channelManager,
|
|
233
|
+
titleGenerator: deps.titleGenerator,
|
|
234
|
+
userPrefRepo: deps.userPrefRepo,
|
|
235
|
+
onFullCompletion: settle,
|
|
236
|
+
}).catch((err) => {
|
|
237
|
+
// sendPromptToAntigravity rejected before onFullCompletion fired
|
|
238
|
+
// (e.g. setup code threw before top-level try/catch).
|
|
239
|
+
// Release the queue immediately instead of waiting for safety timeout.
|
|
240
|
+
logger_1.logger.error(`[Queue:${projectName}] sendPromptToAntigravity rejected early ` +
|
|
241
|
+
`(channel: ${message.channelId}):`, err?.message || err);
|
|
242
|
+
settle();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
131
245
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
logger_1.logger.error(`[Queue:${projectLabel}] Task failed (channel: ${message.channelId}):`, e.message);
|
|
248
|
+
await message.reply(`Failed to connect to workspace: ${e.message}`);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
const remainingDepth = (workspaceQueueDepths.get(workspacePath) ?? 1) - 1;
|
|
252
|
+
workspaceQueueDepths.set(workspacePath, remainingDepth);
|
|
253
|
+
if (remainingDepth > 0) {
|
|
254
|
+
logger_1.logger.info(`[Queue:${projectLabel}] Task done, ${remainingDepth} remaining`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
144
258
|
}
|
|
145
259
|
else {
|
|
146
260
|
await message.reply('No project is configured for this channel. Please create or select one with `/project`.');
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.ensureAntigravityRunning = ensureAntigravityRunning;
|
|
37
37
|
const logger_1 = require("../utils/logger");
|
|
38
38
|
const cdpPorts_1 = require("../utils/cdpPorts");
|
|
39
|
+
const pathUtils_1 = require("../utils/pathUtils");
|
|
39
40
|
const http = __importStar(require("http"));
|
|
40
41
|
/**
|
|
41
42
|
* Check if CDP responds on the specified port.
|
|
@@ -69,10 +70,10 @@ function checkPort(port) {
|
|
|
69
70
|
* Called during Bot initialization.
|
|
70
71
|
*/
|
|
71
72
|
async function ensureAntigravityRunning() {
|
|
72
|
-
logger_1.logger.
|
|
73
|
+
logger_1.logger.debug('[AntigravityLauncher] Checking CDP ports...');
|
|
73
74
|
for (const port of cdpPorts_1.CDP_PORTS) {
|
|
74
75
|
if (await checkPort(port)) {
|
|
75
|
-
logger_1.logger.
|
|
76
|
+
logger_1.logger.debug(`[AntigravityLauncher] OK β Port ${port} responding`);
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -83,7 +84,7 @@ async function ensureAntigravityRunning() {
|
|
|
83
84
|
logger_1.logger.warn(' Please run AntigravityDebug.command before starting the Bot');
|
|
84
85
|
logger_1.logger.warn('');
|
|
85
86
|
logger_1.logger.warn(' Or manually:');
|
|
86
|
-
logger_1.logger.warn(
|
|
87
|
+
logger_1.logger.warn(` ${(0, pathUtils_1.getAntigravityCdpHint)(9222)}`);
|
|
87
88
|
logger_1.logger.warn('='.repeat(70));
|
|
88
89
|
logger_1.logger.warn('');
|
|
89
90
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ApprovalDetector = void 0;
|
|
4
|
+
exports.buildClickScript = buildClickScript;
|
|
4
5
|
const logger_1 = require("../utils/logger");
|
|
5
6
|
/**
|
|
6
7
|
* Approval button detection script for the Antigravity UI
|
|
@@ -205,6 +206,7 @@ class ApprovalDetector {
|
|
|
205
206
|
cdpService;
|
|
206
207
|
pollIntervalMs;
|
|
207
208
|
onApprovalRequired;
|
|
209
|
+
onResolved;
|
|
208
210
|
pollTimer = null;
|
|
209
211
|
isRunning = false;
|
|
210
212
|
/** Key of the last detected button info (for duplicate notification prevention) */
|
|
@@ -215,6 +217,7 @@ class ApprovalDetector {
|
|
|
215
217
|
this.cdpService = options.cdpService;
|
|
216
218
|
this.pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
217
219
|
this.onApprovalRequired = options.onApprovalRequired;
|
|
220
|
+
this.onResolved = options.onResolved;
|
|
218
221
|
}
|
|
219
222
|
/**
|
|
220
223
|
* Start monitoring.
|
|
@@ -285,8 +288,12 @@ class ApprovalDetector {
|
|
|
285
288
|
}
|
|
286
289
|
else {
|
|
287
290
|
// Reset when buttons disappear (prepare for next approval detection)
|
|
291
|
+
const wasDetected = this.lastDetectedKey !== null;
|
|
288
292
|
this.lastDetectedKey = null;
|
|
289
293
|
this.lastDetectedInfo = null;
|
|
294
|
+
if (wasDetected && this.onResolved) {
|
|
295
|
+
this.onResolved();
|
|
296
|
+
}
|
|
290
297
|
}
|
|
291
298
|
}
|
|
292
299
|
catch (error) {
|