lazy-gravity 0.4.0 → 0.5.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/dist/bot/index.js +4 -0
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/events/interactionCreateHandler.js +66 -0
- package/dist/events/messageCreateHandler.js +2 -0
- package/dist/handlers/runCommandButtonAction.js +84 -0
- package/dist/services/cdpBridgeManager.js +102 -0
- package/dist/services/cdpConnectionPool.js +27 -0
- package/dist/services/chatSessionService.js +13 -4
- package/dist/services/notificationSender.js +18 -0
- package/dist/services/runCommandDetector.js +258 -0
- package/dist/services/updateCheckService.js +15 -0
- package/package.json +1 -1
package/dist/bot/index.js
CHANGED
|
@@ -92,6 +92,7 @@ const selectHandler_1 = require("../handlers/selectHandler");
|
|
|
92
92
|
const approvalButtonAction_1 = require("../handlers/approvalButtonAction");
|
|
93
93
|
const planningButtonAction_1 = require("../handlers/planningButtonAction");
|
|
94
94
|
const errorPopupButtonAction_1 = require("../handlers/errorPopupButtonAction");
|
|
95
|
+
const runCommandButtonAction_1 = require("../handlers/runCommandButtonAction");
|
|
95
96
|
const modelButtonAction_1 = require("../handlers/modelButtonAction");
|
|
96
97
|
const autoAcceptButtonAction_1 = require("../handlers/autoAcceptButtonAction");
|
|
97
98
|
const templateButtonAction_1 = require("../handlers/templateButtonAction");
|
|
@@ -831,6 +832,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
831
832
|
parseApprovalCustomId: cdpBridgeManager_1.parseApprovalCustomId,
|
|
832
833
|
parseErrorPopupCustomId: cdpBridgeManager_1.parseErrorPopupCustomId,
|
|
833
834
|
parsePlanningCustomId: cdpBridgeManager_1.parsePlanningCustomId,
|
|
835
|
+
parseRunCommandCustomId: cdpBridgeManager_1.parseRunCommandCustomId,
|
|
834
836
|
joinHandler,
|
|
835
837
|
userPrefRepo,
|
|
836
838
|
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),
|
|
@@ -862,6 +864,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
862
864
|
(0, cdpBridgeManager_1.ensureApprovalDetector)(bridge, cdp, projectName);
|
|
863
865
|
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(bridge, cdp, projectName);
|
|
864
866
|
(0, cdpBridgeManager_1.ensurePlanningDetector)(bridge, cdp, projectName);
|
|
867
|
+
(0, cdpBridgeManager_1.ensureRunCommandDetector)(bridge, cdp, projectName);
|
|
865
868
|
}
|
|
866
869
|
catch (e) {
|
|
867
870
|
await interaction.followUp({
|
|
@@ -998,6 +1001,7 @@ const startBot = async (cliLogLevel) => {
|
|
|
998
1001
|
(0, approvalButtonAction_1.createApprovalButtonAction)({ bridge }),
|
|
999
1002
|
(0, planningButtonAction_1.createPlanningButtonAction)({ bridge }),
|
|
1000
1003
|
(0, errorPopupButtonAction_1.createErrorPopupButtonAction)({ bridge }),
|
|
1004
|
+
(0, runCommandButtonAction_1.createRunCommandButtonAction)({ bridge }),
|
|
1001
1005
|
(0, modelButtonAction_1.createModelButtonAction)({ bridge, fetchQuota: () => bridge.quota.fetchQuota(), modelService, userPrefRepo }),
|
|
1002
1006
|
(0, autoAcceptButtonAction_1.createAutoAcceptButtonAction)({ autoAcceptService: bridge.autoAccept }),
|
|
1003
1007
|
(0, templateButtonAction_1.createTemplateButtonAction)({ bridge, templateRepo }),
|
|
@@ -134,6 +134,7 @@ function createTelegramMessageHandler(deps) {
|
|
|
134
134
|
(0, cdpBridgeManager_1.ensureApprovalDetector)(deps.bridge, cdp, projectName);
|
|
135
135
|
(0, cdpBridgeManager_1.ensureErrorPopupDetector)(deps.bridge, cdp, projectName);
|
|
136
136
|
(0, cdpBridgeManager_1.ensurePlanningDetector)(deps.bridge, cdp, projectName);
|
|
137
|
+
(0, cdpBridgeManager_1.ensureRunCommandDetector)(deps.bridge, cdp, projectName);
|
|
137
138
|
// Acknowledge receipt
|
|
138
139
|
await message.react('\u{1F440}').catch(() => { });
|
|
139
140
|
// Download image attachments if present
|
|
@@ -366,6 +366,72 @@ function createInteractionCreateHandler(deps) {
|
|
|
366
366
|
}
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
|
+
const runCommandAction = deps.parseRunCommandCustomId(interaction.customId);
|
|
370
|
+
if (runCommandAction) {
|
|
371
|
+
if (runCommandAction.channelId && runCommandAction.channelId !== interaction.channelId) {
|
|
372
|
+
await interaction.reply({
|
|
373
|
+
content: (0, i18n_1.t)('This run command action is linked to a different session channel.'),
|
|
374
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
375
|
+
}).catch(logger_1.logger.error);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const runCmdWorkspace = runCommandAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
379
|
+
const runCmdDetector = runCmdWorkspace
|
|
380
|
+
? deps.bridge.pool.getRunCommandDetector(runCmdWorkspace)
|
|
381
|
+
: undefined;
|
|
382
|
+
if (!runCmdDetector) {
|
|
383
|
+
try {
|
|
384
|
+
await interaction.reply({ content: (0, i18n_1.t)('Run command detector not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
385
|
+
}
|
|
386
|
+
catch { /* ignore */ }
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
let success = false;
|
|
390
|
+
let actionLabel = '';
|
|
391
|
+
if (runCommandAction.action === 'run') {
|
|
392
|
+
success = await runCmdDetector.runButton();
|
|
393
|
+
actionLabel = (0, i18n_1.t)('Run');
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
success = await runCmdDetector.rejectButton();
|
|
397
|
+
actionLabel = (0, i18n_1.t)('Reject');
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
if (success) {
|
|
401
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
402
|
+
const updatedEmbed = originalEmbed
|
|
403
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
404
|
+
: new discord_js_1.EmbedBuilder().setTitle('Run Command');
|
|
405
|
+
const historyText = `${actionLabel} by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
406
|
+
updatedEmbed
|
|
407
|
+
.setColor(runCommandAction.action === 'reject' ? 0xE74C3C : 0x2ECC71)
|
|
408
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
409
|
+
.setTimestamp();
|
|
410
|
+
await interaction.update({
|
|
411
|
+
embeds: [updatedEmbed],
|
|
412
|
+
components: (0, discordButtonUtils_1.disableAllButtons)(interaction.message.components),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
await interaction.reply({ content: (0, i18n_1.t)('Run command button not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (interactionError) {
|
|
420
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
421
|
+
logger_1.logger.warn('[RunCommand] Interaction expired. Responding directly in the channel.');
|
|
422
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
423
|
+
const fallbackMessage = success
|
|
424
|
+
? `${actionLabel} completed.`
|
|
425
|
+
: (0, i18n_1.t)('Run command button not found.');
|
|
426
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
throw interactionError;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
369
435
|
if (interaction.customId === cleanupCommandHandler_1.CLEANUP_ARCHIVE_BTN) {
|
|
370
436
|
await deps.cleanupHandler.handleArchive(interaction);
|
|
371
437
|
return;
|
|
@@ -14,6 +14,7 @@ function createMessageCreateHandler(deps) {
|
|
|
14
14
|
const ensureApprovalDetector = deps.ensureApprovalDetector ?? cdpBridgeManager_1.ensureApprovalDetector;
|
|
15
15
|
const ensureErrorPopupDetector = deps.ensureErrorPopupDetector ?? cdpBridgeManager_1.ensureErrorPopupDetector;
|
|
16
16
|
const ensurePlanningDetector = deps.ensurePlanningDetector ?? cdpBridgeManager_1.ensurePlanningDetector;
|
|
17
|
+
const ensureRunCommandDetector = deps.ensureRunCommandDetector ?? cdpBridgeManager_1.ensureRunCommandDetector;
|
|
17
18
|
const registerApprovalWorkspaceChannel = deps.registerApprovalWorkspaceChannel ?? cdpBridgeManager_1.registerApprovalWorkspaceChannel;
|
|
18
19
|
const registerApprovalSessionChannel = deps.registerApprovalSessionChannel ?? cdpBridgeManager_1.registerApprovalSessionChannel;
|
|
19
20
|
const downloadInboundImageAttachments = deps.downloadInboundImageAttachments ?? imageHandler_1.downloadInboundImageAttachments;
|
|
@@ -170,6 +171,7 @@ function createMessageCreateHandler(deps) {
|
|
|
170
171
|
ensureApprovalDetector(deps.bridge, cdp, projectName);
|
|
171
172
|
ensureErrorPopupDetector(deps.bridge, cdp, projectName);
|
|
172
173
|
ensurePlanningDetector(deps.bridge, cdp, projectName);
|
|
174
|
+
ensureRunCommandDetector(deps.bridge, cdp, projectName);
|
|
173
175
|
const session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
174
176
|
if (session?.displayName) {
|
|
175
177
|
registerApprovalSessionChannel(deps.bridge, projectName, session.displayName, platformChannel);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Platform-agnostic run command button action.
|
|
4
|
+
*
|
|
5
|
+
* Handles Run / Reject button presses for the "Run command?"
|
|
6
|
+
* dialog from both Discord and Telegram using the ButtonAction interface.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createRunCommandButtonAction = createRunCommandButtonAction;
|
|
10
|
+
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
11
|
+
const logger_1 = require("../utils/logger");
|
|
12
|
+
function createRunCommandButtonAction(deps) {
|
|
13
|
+
return {
|
|
14
|
+
match(customId) {
|
|
15
|
+
const parsed = (0, cdpBridgeManager_1.parseRunCommandCustomId)(customId);
|
|
16
|
+
if (!parsed)
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
action: parsed.action,
|
|
20
|
+
projectName: parsed.projectName ?? '',
|
|
21
|
+
channelId: parsed.channelId ?? '',
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
async execute(interaction, params) {
|
|
25
|
+
const { action, channelId } = params;
|
|
26
|
+
// Acknowledge immediately so Telegram doesn't time out
|
|
27
|
+
await interaction.deferUpdate().catch(() => { });
|
|
28
|
+
if (channelId && channelId !== interaction.channel.id) {
|
|
29
|
+
await interaction
|
|
30
|
+
.reply({ text: 'This run command action is linked to a different session channel.' })
|
|
31
|
+
.catch(() => { });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const projectName = params.projectName || deps.bridge.lastActiveWorkspace;
|
|
35
|
+
const detector = projectName
|
|
36
|
+
? deps.bridge.pool.getRunCommandDetector(projectName)
|
|
37
|
+
: undefined;
|
|
38
|
+
if (!detector) {
|
|
39
|
+
logger_1.logger.warn(`[RunCommandAction] No detector for project=${projectName}`);
|
|
40
|
+
await interaction
|
|
41
|
+
.reply({ text: 'Run command detector not found.' })
|
|
42
|
+
.catch(() => { });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
let success = false;
|
|
46
|
+
let actionLabel = '';
|
|
47
|
+
try {
|
|
48
|
+
if (action === 'run') {
|
|
49
|
+
success = await detector.runButton();
|
|
50
|
+
actionLabel = 'Run';
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
success = await detector.rejectButton();
|
|
54
|
+
actionLabel = 'Reject';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
+
logger_1.logger.error(`[RunCommandAction] CDP click failed: ${msg}`);
|
|
60
|
+
await interaction
|
|
61
|
+
.reply({ text: `Run command action failed: ${msg}` })
|
|
62
|
+
.catch(() => { });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (success) {
|
|
66
|
+
await interaction
|
|
67
|
+
.update({ text: `${action === 'run' ? '▶️' : '⛔'} ${actionLabel} completed`, components: [] })
|
|
68
|
+
.catch((err) => {
|
|
69
|
+
logger_1.logger.warn('[RunCommandAction] update failed, trying editReply:', err);
|
|
70
|
+
interaction.editReply({ text: `${action === 'run' ? '▶️' : '⛔'} ${actionLabel} completed`, components: [] })
|
|
71
|
+
.catch((editErr) => {
|
|
72
|
+
logger_1.logger.warn('[RunCommandAction] editReply failed, sending followUp:', editErr);
|
|
73
|
+
interaction.followUp({ text: `${action === 'run' ? '▶️' : '⛔'} ${actionLabel} completed` }).catch(() => { });
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await interaction
|
|
79
|
+
.reply({ text: 'Run command button not found.' })
|
|
80
|
+
.catch(() => { });
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -10,11 +10,14 @@ exports.buildPlanningCustomId = buildPlanningCustomId;
|
|
|
10
10
|
exports.parsePlanningCustomId = parsePlanningCustomId;
|
|
11
11
|
exports.buildErrorPopupCustomId = buildErrorPopupCustomId;
|
|
12
12
|
exports.parseErrorPopupCustomId = parseErrorPopupCustomId;
|
|
13
|
+
exports.buildRunCommandCustomId = buildRunCommandCustomId;
|
|
14
|
+
exports.parseRunCommandCustomId = parseRunCommandCustomId;
|
|
13
15
|
exports.initCdpBridge = initCdpBridge;
|
|
14
16
|
exports.getCurrentCdp = getCurrentCdp;
|
|
15
17
|
exports.ensureApprovalDetector = ensureApprovalDetector;
|
|
16
18
|
exports.ensurePlanningDetector = ensurePlanningDetector;
|
|
17
19
|
exports.ensureErrorPopupDetector = ensureErrorPopupDetector;
|
|
20
|
+
exports.ensureRunCommandDetector = ensureRunCommandDetector;
|
|
18
21
|
exports.ensureUserMessageDetector = ensureUserMessageDetector;
|
|
19
22
|
const i18n_1 = require("../utils/i18n");
|
|
20
23
|
const logger_1 = require("../utils/logger");
|
|
@@ -24,6 +27,7 @@ const autoAcceptService_1 = require("./autoAcceptService");
|
|
|
24
27
|
const cdpConnectionPool_1 = require("./cdpConnectionPool");
|
|
25
28
|
const errorPopupDetector_1 = require("./errorPopupDetector");
|
|
26
29
|
const planningDetector_1 = require("./planningDetector");
|
|
30
|
+
const runCommandDetector_1 = require("./runCommandDetector");
|
|
27
31
|
const quotaService_1 = require("./quotaService");
|
|
28
32
|
const userMessageDetector_1 = require("./userMessageDetector");
|
|
29
33
|
const APPROVE_ACTION_PREFIX = 'approve_action';
|
|
@@ -34,6 +38,8 @@ const PLANNING_PROCEED_ACTION_PREFIX = 'planning_proceed_action';
|
|
|
34
38
|
const ERROR_POPUP_DISMISS_ACTION_PREFIX = 'error_popup_dismiss_action';
|
|
35
39
|
const ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX = 'error_popup_copy_debug_action';
|
|
36
40
|
const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
41
|
+
const RUN_COMMAND_RUN_ACTION_PREFIX = 'run_command_run_action';
|
|
42
|
+
const RUN_COMMAND_REJECT_ACTION_PREFIX = 'run_command_reject_action';
|
|
37
43
|
function normalizeSessionTitle(title) {
|
|
38
44
|
return title.trim().toLowerCase();
|
|
39
45
|
}
|
|
@@ -194,6 +200,34 @@ function parseErrorPopupCustomId(customId) {
|
|
|
194
200
|
}
|
|
195
201
|
return null;
|
|
196
202
|
}
|
|
203
|
+
function buildRunCommandCustomId(action, projectName, channelId) {
|
|
204
|
+
const prefix = action === 'run'
|
|
205
|
+
? RUN_COMMAND_RUN_ACTION_PREFIX
|
|
206
|
+
: RUN_COMMAND_REJECT_ACTION_PREFIX;
|
|
207
|
+
if (channelId && channelId.trim().length > 0) {
|
|
208
|
+
return `${prefix}:${projectName}:${channelId}`;
|
|
209
|
+
}
|
|
210
|
+
return `${prefix}:${projectName}`;
|
|
211
|
+
}
|
|
212
|
+
function parseRunCommandCustomId(customId) {
|
|
213
|
+
if (customId === RUN_COMMAND_RUN_ACTION_PREFIX) {
|
|
214
|
+
return { action: 'run', projectName: null, channelId: null };
|
|
215
|
+
}
|
|
216
|
+
if (customId === RUN_COMMAND_REJECT_ACTION_PREFIX) {
|
|
217
|
+
return { action: 'reject', projectName: null, channelId: null };
|
|
218
|
+
}
|
|
219
|
+
if (customId.startsWith(`${RUN_COMMAND_RUN_ACTION_PREFIX}:`)) {
|
|
220
|
+
const rest = customId.substring(`${RUN_COMMAND_RUN_ACTION_PREFIX}:`.length);
|
|
221
|
+
const [projectName, channelId] = rest.split(':');
|
|
222
|
+
return { action: 'run', projectName: projectName || null, channelId: channelId || null };
|
|
223
|
+
}
|
|
224
|
+
if (customId.startsWith(`${RUN_COMMAND_REJECT_ACTION_PREFIX}:`)) {
|
|
225
|
+
const rest = customId.substring(`${RUN_COMMAND_REJECT_ACTION_PREFIX}:`.length);
|
|
226
|
+
const [projectName, channelId] = rest.split(':');
|
|
227
|
+
return { action: 'reject', projectName: projectName || null, channelId: channelId || null };
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
197
231
|
/** Initialize the CDP bridge (lazy connection: pool creation only) */
|
|
198
232
|
function initCdpBridge(autoApproveDefault) {
|
|
199
233
|
const pool = new cdpConnectionPool_1.CdpConnectionPool({
|
|
@@ -412,6 +446,74 @@ function ensureErrorPopupDetector(bridge, cdp, projectName) {
|
|
|
412
446
|
bridge.pool.registerErrorPopupDetector(projectName, detector);
|
|
413
447
|
logger_1.logger.debug(`[ErrorPopupDetector:${projectName}] Started error popup detection`);
|
|
414
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Helper to start a run command detector for each workspace.
|
|
451
|
+
* Detects "Run command?" confirmation dialogs and forwards them to Discord.
|
|
452
|
+
* Does nothing if a detector for the same workspace is already running.
|
|
453
|
+
*/
|
|
454
|
+
function ensureRunCommandDetector(bridge, cdp, projectName) {
|
|
455
|
+
const existing = bridge.pool.getRunCommandDetector(projectName);
|
|
456
|
+
if (existing && existing.isActive())
|
|
457
|
+
return;
|
|
458
|
+
let lastNotification = null;
|
|
459
|
+
const detector = new runCommandDetector_1.RunCommandDetector({
|
|
460
|
+
cdpService: cdp,
|
|
461
|
+
pollIntervalMs: 2000,
|
|
462
|
+
onResolved: () => {
|
|
463
|
+
if (!lastNotification)
|
|
464
|
+
return;
|
|
465
|
+
const { sent, payload } = lastNotification;
|
|
466
|
+
lastNotification = null;
|
|
467
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Resolved in Antigravity'));
|
|
468
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
469
|
+
},
|
|
470
|
+
onRunCommandRequired: async (info) => {
|
|
471
|
+
logger_1.logger.debug(`[RunCommandDetector:${projectName}] Run command detected`);
|
|
472
|
+
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
473
|
+
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
474
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
475
|
+
if (!targetChannel || !targetChannelId) {
|
|
476
|
+
logger_1.logger.warn(`[RunCommandDetector:${projectName}] Skipped run command notification because chat is not linked to a session` +
|
|
477
|
+
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (bridge.autoAccept.isEnabled()) {
|
|
481
|
+
const accepted = await detector.runButton();
|
|
482
|
+
const autoPayload = (0, notificationSender_1.buildAutoApprovedNotification)({
|
|
483
|
+
accepted,
|
|
484
|
+
projectName,
|
|
485
|
+
description: `Run: ${info.commandText}`,
|
|
486
|
+
approveText: info.runText ?? 'Run',
|
|
487
|
+
});
|
|
488
|
+
await targetChannel.send(autoPayload).catch(logger_1.logger.error);
|
|
489
|
+
if (accepted) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const payload = (0, notificationSender_1.buildRunCommandNotification)({
|
|
494
|
+
title: (0, i18n_1.t)('Run Command?'),
|
|
495
|
+
commandText: info.commandText,
|
|
496
|
+
workingDirectory: info.workingDirectory,
|
|
497
|
+
projectName,
|
|
498
|
+
channelId: targetChannelId,
|
|
499
|
+
extraFields: [
|
|
500
|
+
{ name: (0, i18n_1.t)('Run button'), value: info.runText, inline: true },
|
|
501
|
+
{ name: (0, i18n_1.t)('Reject button'), value: info.rejectText, inline: true },
|
|
502
|
+
],
|
|
503
|
+
});
|
|
504
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
505
|
+
logger_1.logger.error(err);
|
|
506
|
+
return null;
|
|
507
|
+
});
|
|
508
|
+
if (sent) {
|
|
509
|
+
lastNotification = { sent, payload };
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
detector.start();
|
|
514
|
+
bridge.pool.registerRunCommandDetector(projectName, detector);
|
|
515
|
+
logger_1.logger.debug(`[RunCommandDetector:${projectName}] Started run command detection`);
|
|
516
|
+
}
|
|
415
517
|
/**
|
|
416
518
|
* Helper to start a user message detector for a workspace.
|
|
417
519
|
* Detects messages typed directly in the Antigravity UI (e.g., from a PC)
|
|
@@ -16,6 +16,7 @@ class CdpConnectionPool {
|
|
|
16
16
|
approvalDetectors = new Map();
|
|
17
17
|
errorPopupDetectors = new Map();
|
|
18
18
|
planningDetectors = new Map();
|
|
19
|
+
runCommandDetectors = new Map();
|
|
19
20
|
userMessageDetectors = new Map();
|
|
20
21
|
connectingPromises = new Map();
|
|
21
22
|
cdpOptions;
|
|
@@ -92,6 +93,11 @@ class CdpConnectionPool {
|
|
|
92
93
|
planningDetector.stop();
|
|
93
94
|
this.planningDetectors.delete(projectName);
|
|
94
95
|
}
|
|
96
|
+
const runCmdDetector = this.runCommandDetectors.get(projectName);
|
|
97
|
+
if (runCmdDetector) {
|
|
98
|
+
runCmdDetector.stop();
|
|
99
|
+
this.runCommandDetectors.delete(projectName);
|
|
100
|
+
}
|
|
95
101
|
const userMsgDetector = this.userMessageDetectors.get(projectName);
|
|
96
102
|
if (userMsgDetector) {
|
|
97
103
|
userMsgDetector.stop();
|
|
@@ -157,6 +163,22 @@ class CdpConnectionPool {
|
|
|
157
163
|
getPlanningDetector(projectName) {
|
|
158
164
|
return this.planningDetectors.get(projectName);
|
|
159
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Register a run command detector for a workspace.
|
|
168
|
+
*/
|
|
169
|
+
registerRunCommandDetector(projectName, detector) {
|
|
170
|
+
const existing = this.runCommandDetectors.get(projectName);
|
|
171
|
+
if (existing && existing.isActive()) {
|
|
172
|
+
existing.stop();
|
|
173
|
+
}
|
|
174
|
+
this.runCommandDetectors.set(projectName, detector);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the run command detector for a workspace.
|
|
178
|
+
*/
|
|
179
|
+
getRunCommandDetector(projectName) {
|
|
180
|
+
return this.runCommandDetectors.get(projectName);
|
|
181
|
+
}
|
|
160
182
|
/**
|
|
161
183
|
* Register a user message detector for a workspace.
|
|
162
184
|
*/
|
|
@@ -226,6 +248,11 @@ class CdpConnectionPool {
|
|
|
226
248
|
planDetector.stop();
|
|
227
249
|
this.planningDetectors.delete(projectName);
|
|
228
250
|
}
|
|
251
|
+
const runCmdDetector = this.runCommandDetectors.get(projectName);
|
|
252
|
+
if (runCmdDetector) {
|
|
253
|
+
runCmdDetector.stop();
|
|
254
|
+
this.runCommandDetectors.delete(projectName);
|
|
255
|
+
}
|
|
229
256
|
const userMsgDetector = this.userMessageDetectors.get(projectName);
|
|
230
257
|
if (userMsgDetector) {
|
|
231
258
|
userMsgDetector.stop();
|
|
@@ -78,8 +78,11 @@ const SCRAPE_PAST_CONVERSATIONS_SCRIPT = `(() => {
|
|
|
78
78
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
79
79
|
const normalize = (text) => (text || '').trim();
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
81
|
+
// Past Conversations opens as a floating QuickInput dialog, not inside the side panel.
|
|
82
|
+
// Try the visible QuickInput dialog first, then fall back to the side panel.
|
|
83
|
+
const quickInputPanels = Array.from(document.querySelectorAll('div[class*="bg-quickinput-background"]'));
|
|
84
|
+
const panel = quickInputPanels.find((el) => isVisible(el))
|
|
85
|
+
|| document.querySelector('.antigravity-agent-side-panel');
|
|
83
86
|
if (!panel) return null;
|
|
84
87
|
|
|
85
88
|
const items = [];
|
|
@@ -136,7 +139,11 @@ const SCRAPE_PAST_CONVERSATIONS_SCRIPT = `(() => {
|
|
|
136
139
|
*/
|
|
137
140
|
const FIND_SHOW_MORE_BUTTON_SCRIPT = `(() => {
|
|
138
141
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
139
|
-
const
|
|
142
|
+
const quickInputPanels = Array.from(document.querySelectorAll('div[class*="bg-quickinput-background"]'));
|
|
143
|
+
const root = quickInputPanels.find((el) => isVisible(el))
|
|
144
|
+
|| document.querySelector('.antigravity-agent-side-panel')
|
|
145
|
+
|| document;
|
|
146
|
+
const els = Array.from(root.querySelectorAll('div, span'));
|
|
140
147
|
for (const el of els) {
|
|
141
148
|
if (!isVisible(el)) continue;
|
|
142
149
|
const text = (el.textContent || '').trim();
|
|
@@ -454,7 +461,9 @@ class ChatSessionService {
|
|
|
454
461
|
// Step 3: Wait for panel to render (poll for content, up to 3s)
|
|
455
462
|
const PANEL_READY_CHECK = `(() => {
|
|
456
463
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
457
|
-
const
|
|
464
|
+
const quickInputPanels = Array.from(document.querySelectorAll('div[class*="bg-quickinput-background"]'));
|
|
465
|
+
const panel = quickInputPanels.find((el) => isVisible(el))
|
|
466
|
+
|| document.querySelector('.antigravity-agent-side-panel');
|
|
458
467
|
if (!panel) return false;
|
|
459
468
|
const containers = Array.from(
|
|
460
469
|
panel.querySelectorAll('div[class*="overflow-auto"], div[class*="overflow-y-scroll"]')
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.buildApprovalNotification = buildApprovalNotification;
|
|
10
10
|
exports.buildPlanningNotification = buildPlanningNotification;
|
|
11
11
|
exports.buildErrorPopupNotification = buildErrorPopupNotification;
|
|
12
|
+
exports.buildRunCommandNotification = buildRunCommandNotification;
|
|
12
13
|
exports.buildAutoApprovedNotification = buildAutoApprovedNotification;
|
|
13
14
|
exports.buildResolvedOverlay = buildResolvedOverlay;
|
|
14
15
|
exports.buildStatusNotification = buildStatusNotification;
|
|
@@ -25,6 +26,8 @@ const PLANNING_PROCEED_ACTION_PREFIX = 'planning_proceed_action';
|
|
|
25
26
|
const ERROR_POPUP_DISMISS_ACTION_PREFIX = 'error_popup_dismiss_action';
|
|
26
27
|
const ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX = 'error_popup_copy_debug_action';
|
|
27
28
|
const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
29
|
+
const RUN_COMMAND_RUN_ACTION_PREFIX = 'run_command_run_action';
|
|
30
|
+
const RUN_COMMAND_REJECT_ACTION_PREFIX = 'run_command_reject_action';
|
|
28
31
|
// ---------------------------------------------------------------------------
|
|
29
32
|
// Notification colours
|
|
30
33
|
// ---------------------------------------------------------------------------
|
|
@@ -107,6 +110,21 @@ function buildErrorPopupNotification(opts) {
|
|
|
107
110
|
];
|
|
108
111
|
return { richContent, components };
|
|
109
112
|
}
|
|
113
|
+
/** Build the run command notification message. */
|
|
114
|
+
function buildRunCommandNotification(opts) {
|
|
115
|
+
const { title, commandText, workingDirectory, projectName, channelId, extraFields } = opts;
|
|
116
|
+
const safeCommandText = (commandText || '')
|
|
117
|
+
.replace(/```/g, '`\u200b``')
|
|
118
|
+
.slice(0, 3800);
|
|
119
|
+
const safeWorkingDirectory = (workingDirectory || '(unknown)').slice(0, 1024);
|
|
120
|
+
const richContent = (0, richContentBuilder_1.pipe)((0, richContentBuilder_1.createRichContent)(), (rc) => (0, richContentBuilder_1.withTitle)(rc, title), (rc) => (0, richContentBuilder_1.withDescription)(rc, `\`\`\`\n${safeCommandText}\n\`\`\``), (rc) => (0, richContentBuilder_1.withColor)(rc, COLOR_APPROVAL), (rc) => (0, richContentBuilder_1.addField)(rc, 'Directory', safeWorkingDirectory, true), (rc) => (0, richContentBuilder_1.addField)(rc, 'Project', projectName, true), (rc) => extraFields
|
|
121
|
+
? extraFields.reduce((acc, f) => (0, richContentBuilder_1.addField)(acc, f.name, f.value, f.inline), rc)
|
|
122
|
+
: rc, (rc) => (0, richContentBuilder_1.withFooter)(rc, 'Run command approval required'), (rc) => (0, richContentBuilder_1.withTimestamp)(rc));
|
|
123
|
+
const components = [
|
|
124
|
+
buttonRow(button(customId(RUN_COMMAND_RUN_ACTION_PREFIX, projectName, channelId), 'Run', 'success'), button(customId(RUN_COMMAND_REJECT_ACTION_PREFIX, projectName, channelId), 'Reject', 'danger')),
|
|
125
|
+
];
|
|
126
|
+
return { richContent, components };
|
|
127
|
+
}
|
|
110
128
|
/** Build an auto-approved notification (shown when auto-accept fires). */
|
|
111
129
|
function buildAutoApprovedNotification(opts) {
|
|
112
130
|
const { accepted, projectName, description, approveText } = opts;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RunCommandDetector = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const approvalDetector_1 = require("./approvalDetector");
|
|
6
|
+
/**
|
|
7
|
+
* CDP detection script for the "Run command?" dialog in Claude Code / Antigravity.
|
|
8
|
+
*
|
|
9
|
+
* DOM structure (from user-provided inspection):
|
|
10
|
+
* <div class="flex flex-col gap-2 border-gray-500/25 border rounded-lg my-1">
|
|
11
|
+
* <div>
|
|
12
|
+
* <div class="... border-b ..."><span class="opacity-60">Run command?</span></div>
|
|
13
|
+
* <div class="...">
|
|
14
|
+
* <pre class="whitespace-pre-wrap break-all font-mono text-sm">
|
|
15
|
+
* <span class="... opacity-50">~/Code/login</span>
|
|
16
|
+
* <span class="opacity-50"> $ </span>python3 -m http.server 8000
|
|
17
|
+
* </pre>
|
|
18
|
+
* </div>
|
|
19
|
+
* <div class="... border-t ...">
|
|
20
|
+
* <button>Reject</button>
|
|
21
|
+
* <button>Run</button> (split button with chevron for options)
|
|
22
|
+
* </div>
|
|
23
|
+
* </div>
|
|
24
|
+
* </div>
|
|
25
|
+
*/
|
|
26
|
+
const DETECT_RUN_COMMAND_SCRIPT = `(() => {
|
|
27
|
+
const RUN_COMMAND_HEADER_PATTERNS = [
|
|
28
|
+
'run command?', 'run command', 'execute command',
|
|
29
|
+
'コマンドを実行', 'コマンド実行'
|
|
30
|
+
];
|
|
31
|
+
const RUN_PATTERNS = ['run', '実行', 'execute'];
|
|
32
|
+
const REJECT_PATTERNS = ['reject', 'cancel', '拒否', 'キャンセル'];
|
|
33
|
+
|
|
34
|
+
const normalize = (text) => (text || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
35
|
+
|
|
36
|
+
// Find the "Run command?" header span (reverse order to prefer newest card)
|
|
37
|
+
const allSpans = Array.from(document.querySelectorAll('span')).reverse();
|
|
38
|
+
const headerSpan = allSpans.find(span => {
|
|
39
|
+
if (!span.offsetParent && span.offsetParent !== document.body) {
|
|
40
|
+
const rect = span.getBoundingClientRect();
|
|
41
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
42
|
+
}
|
|
43
|
+
const t = normalize(span.textContent || '');
|
|
44
|
+
return RUN_COMMAND_HEADER_PATTERNS.some(p => t.includes(p));
|
|
45
|
+
});
|
|
46
|
+
if (!headerSpan) return null;
|
|
47
|
+
|
|
48
|
+
// Navigate up to the rounded-lg container
|
|
49
|
+
const container = headerSpan.closest('div[class*="rounded-lg"][class*="border"]')
|
|
50
|
+
|| headerSpan.closest('div[class*="gap-2"]')
|
|
51
|
+
|| headerSpan.parentElement?.parentElement?.parentElement;
|
|
52
|
+
if (!container) return null;
|
|
53
|
+
|
|
54
|
+
// Extract command text from <pre> element
|
|
55
|
+
const pre = container.querySelector('pre');
|
|
56
|
+
if (!pre) return null;
|
|
57
|
+
|
|
58
|
+
const preText = (pre.textContent || '').trim();
|
|
59
|
+
// Format: "~/Code/login $ python3 -m http.server 8000"
|
|
60
|
+
// Split on " $ " to separate working directory from command
|
|
61
|
+
const dollarIdx = preText.indexOf(' $ ');
|
|
62
|
+
let commandText = preText;
|
|
63
|
+
let workingDirectory = '';
|
|
64
|
+
if (dollarIdx >= 0) {
|
|
65
|
+
workingDirectory = preText.substring(0, dollarIdx).trim();
|
|
66
|
+
commandText = preText.substring(dollarIdx + 3).trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Find Run and Reject buttons within the container
|
|
70
|
+
const containerButtons = Array.from(container.querySelectorAll('button'))
|
|
71
|
+
.filter(btn => {
|
|
72
|
+
if (btn.offsetParent !== null) return true;
|
|
73
|
+
const rect = btn.getBoundingClientRect();
|
|
74
|
+
return rect.width > 0 && rect.height > 0;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const runBtn = containerButtons.find(btn => {
|
|
78
|
+
const t = normalize(btn.textContent || '');
|
|
79
|
+
// Exclude buttons that are clearly not the Run button (dropdowns, copy, etc.)
|
|
80
|
+
if (t === '' || t.length > 30) return false;
|
|
81
|
+
return RUN_PATTERNS.some(p => t === p || t.startsWith(p));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const rejectBtn = containerButtons.find(btn => {
|
|
85
|
+
const t = normalize(btn.textContent || '');
|
|
86
|
+
if (t === '' || t.length > 30) return false;
|
|
87
|
+
return REJECT_PATTERNS.some(p => t === p || t.startsWith(p));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!runBtn || !rejectBtn) return null;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
commandText,
|
|
94
|
+
workingDirectory,
|
|
95
|
+
runText: (runBtn.textContent || '').trim(),
|
|
96
|
+
rejectText: (rejectBtn.textContent || '').trim(),
|
|
97
|
+
};
|
|
98
|
+
})()`;
|
|
99
|
+
/**
|
|
100
|
+
* Class that detects "Run command?" dialogs in the Antigravity UI via polling.
|
|
101
|
+
*
|
|
102
|
+
* Notifies detected dialog info through the onRunCommandRequired callback,
|
|
103
|
+
* and performs the actual click operations via runButton() / rejectButton() methods.
|
|
104
|
+
*/
|
|
105
|
+
class RunCommandDetector {
|
|
106
|
+
cdpService;
|
|
107
|
+
pollIntervalMs;
|
|
108
|
+
onRunCommandRequired;
|
|
109
|
+
onResolved;
|
|
110
|
+
pollTimer = null;
|
|
111
|
+
isRunning = false;
|
|
112
|
+
/** Key of the last detected dialog (for duplicate notification prevention) */
|
|
113
|
+
lastDetectedKey = null;
|
|
114
|
+
/** Full RunCommandInfo from the last detection (used for clicking) */
|
|
115
|
+
lastDetectedInfo = null;
|
|
116
|
+
constructor(options) {
|
|
117
|
+
this.cdpService = options.cdpService;
|
|
118
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
119
|
+
this.onRunCommandRequired = options.onRunCommandRequired;
|
|
120
|
+
this.onResolved = options.onResolved;
|
|
121
|
+
}
|
|
122
|
+
/** Start monitoring. */
|
|
123
|
+
start() {
|
|
124
|
+
if (this.isRunning)
|
|
125
|
+
return;
|
|
126
|
+
this.isRunning = true;
|
|
127
|
+
this.lastDetectedKey = null;
|
|
128
|
+
this.lastDetectedInfo = null;
|
|
129
|
+
this.schedulePoll();
|
|
130
|
+
}
|
|
131
|
+
/** Stop monitoring. */
|
|
132
|
+
async stop() {
|
|
133
|
+
this.isRunning = false;
|
|
134
|
+
if (this.pollTimer) {
|
|
135
|
+
clearTimeout(this.pollTimer);
|
|
136
|
+
this.pollTimer = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Return the last detected run command info. */
|
|
140
|
+
getLastDetectedInfo() {
|
|
141
|
+
return this.lastDetectedInfo;
|
|
142
|
+
}
|
|
143
|
+
/** Schedule the next poll */
|
|
144
|
+
schedulePoll() {
|
|
145
|
+
if (!this.isRunning)
|
|
146
|
+
return;
|
|
147
|
+
this.pollTimer = setTimeout(async () => {
|
|
148
|
+
await this.poll();
|
|
149
|
+
if (this.isRunning) {
|
|
150
|
+
this.schedulePoll();
|
|
151
|
+
}
|
|
152
|
+
}, this.pollIntervalMs);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Single poll iteration:
|
|
156
|
+
* 1. Detect run command dialog in DOM (with contextId)
|
|
157
|
+
* 2. Notify via callback only on new detection (prevent duplicates)
|
|
158
|
+
* 3. Reset when dialog disappears
|
|
159
|
+
*/
|
|
160
|
+
async poll() {
|
|
161
|
+
try {
|
|
162
|
+
const contextId = this.cdpService.getPrimaryContextId();
|
|
163
|
+
const callParams = {
|
|
164
|
+
expression: DETECT_RUN_COMMAND_SCRIPT,
|
|
165
|
+
returnByValue: true,
|
|
166
|
+
awaitPromise: false,
|
|
167
|
+
};
|
|
168
|
+
if (contextId !== null) {
|
|
169
|
+
callParams.contextId = contextId;
|
|
170
|
+
}
|
|
171
|
+
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
172
|
+
const info = result?.result?.value ?? null;
|
|
173
|
+
if (info) {
|
|
174
|
+
// Duplicate prevention: use commandText as key
|
|
175
|
+
const key = `${info.commandText}::${info.workingDirectory}`;
|
|
176
|
+
if (key !== this.lastDetectedKey) {
|
|
177
|
+
this.lastDetectedKey = key;
|
|
178
|
+
this.lastDetectedInfo = info;
|
|
179
|
+
this.onRunCommandRequired(info);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const wasDetected = this.lastDetectedKey !== null;
|
|
184
|
+
this.lastDetectedKey = null;
|
|
185
|
+
this.lastDetectedInfo = null;
|
|
186
|
+
if (wasDetected && this.onResolved) {
|
|
187
|
+
this.onResolved();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
if (message.includes('WebSocket is not connected')) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
logger_1.logger.error('[RunCommandDetector] Error during polling:', error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Click the Run button via CDP.
|
|
201
|
+
* @param buttonText Text of the button to click (default: detected runText or "Run")
|
|
202
|
+
* @returns true if click succeeded
|
|
203
|
+
*/
|
|
204
|
+
async runButton(buttonText) {
|
|
205
|
+
const text = buttonText ?? this.lastDetectedInfo?.runText ?? 'Run';
|
|
206
|
+
return this.clickButton(text);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Click the Reject button via CDP.
|
|
210
|
+
* @param buttonText Text of the button to click (default: detected rejectText or "Reject")
|
|
211
|
+
* @returns true if click succeeded
|
|
212
|
+
*/
|
|
213
|
+
async rejectButton(buttonText) {
|
|
214
|
+
const text = buttonText ?? this.lastDetectedInfo?.rejectText ?? 'Reject';
|
|
215
|
+
return this.clickButton(text);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Internal click handler (shared implementation for runButton / rejectButton).
|
|
219
|
+
*/
|
|
220
|
+
async clickButton(buttonText) {
|
|
221
|
+
try {
|
|
222
|
+
const script = (0, approvalDetector_1.buildClickScript)(buttonText);
|
|
223
|
+
const result = await this.runEvaluateScript(script);
|
|
224
|
+
if (result?.ok !== true) {
|
|
225
|
+
logger_1.logger.warn(`[RunCommandDetector] Click failed for "${buttonText}":`, result?.error ?? 'unknown');
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
logger_1.logger.debug(`[RunCommandDetector] Click OK for "${buttonText}"`);
|
|
229
|
+
}
|
|
230
|
+
return result?.ok === true;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
logger_1.logger.error('[RunCommandDetector] Error while clicking button:', error);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Execute Runtime.evaluate with contextId and return result.value.
|
|
239
|
+
*/
|
|
240
|
+
async runEvaluateScript(expression) {
|
|
241
|
+
const contextId = this.cdpService.getPrimaryContextId();
|
|
242
|
+
const callParams = {
|
|
243
|
+
expression,
|
|
244
|
+
returnByValue: true,
|
|
245
|
+
awaitPromise: false,
|
|
246
|
+
};
|
|
247
|
+
if (contextId !== null) {
|
|
248
|
+
callParams.contextId = contextId;
|
|
249
|
+
}
|
|
250
|
+
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
251
|
+
return result?.result?.value;
|
|
252
|
+
}
|
|
253
|
+
/** Returns whether monitoring is currently active */
|
|
254
|
+
isActive() {
|
|
255
|
+
return this.isRunning;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
exports.RunCommandDetector = RunCommandDetector;
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.COOLDOWN_MS = exports.UPDATE_CHECK_FILE = void 0;
|
|
37
37
|
exports.shouldCheckForUpdates = shouldCheckForUpdates;
|
|
38
38
|
exports.fetchLatestVersion = fetchLatestVersion;
|
|
39
|
+
exports.isGlobalInstall = isGlobalInstall;
|
|
39
40
|
exports.checkForUpdates = checkForUpdates;
|
|
40
41
|
const https = __importStar(require("https"));
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
@@ -127,11 +128,25 @@ function compareSemver(a, b) {
|
|
|
127
128
|
}
|
|
128
129
|
return 0;
|
|
129
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Detect whether the process is running from a global npm install
|
|
133
|
+
* (as opposed to a local dev checkout via `ts-node`, `tsx`, etc.).
|
|
134
|
+
*/
|
|
135
|
+
function isGlobalInstall() {
|
|
136
|
+
const execPath = process.argv[1] || '';
|
|
137
|
+
// Global installs run from a path containing node_modules
|
|
138
|
+
// Local dev runs from the source tree (no node_modules/.bin in argv[1])
|
|
139
|
+
const globalIndicators = ['/lib/node_modules/', '\\node_modules\\lazy-gravity\\'];
|
|
140
|
+
return globalIndicators.some((indicator) => execPath.includes(indicator));
|
|
141
|
+
}
|
|
130
142
|
/**
|
|
131
143
|
* Non-blocking update check. Call at startup (fire-and-forget).
|
|
132
144
|
* Respects a 24-hour cooldown via a local cache file.
|
|
145
|
+
* Skipped when running from source (dev/local checkout).
|
|
133
146
|
*/
|
|
134
147
|
async function checkForUpdates(currentVersion) {
|
|
148
|
+
if (!isGlobalInstall())
|
|
149
|
+
return;
|
|
135
150
|
if (!shouldCheckForUpdates())
|
|
136
151
|
return;
|
|
137
152
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazy-gravity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Control Antigravity from anywhere — a local, secure bot (Discord + Telegram) that lets you remotely operate Antigravity on your home PC from your smartphone.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|