lazy-gravity 0.9.1 → 0.9.2
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
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.handleJoin = handleJoin;
|
|
4
4
|
exports.handleTelegramJoinSelect = handleTelegramJoinSelect;
|
|
5
5
|
exports.handleMirror = handleMirror;
|
|
6
|
+
exports.startResponseMirror = startResponseMirror;
|
|
6
7
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
7
8
|
const responseMonitor_1 = require("../services/responseMonitor");
|
|
8
9
|
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
@@ -137,6 +138,14 @@ async function routeMirroredMessage(deps, cdp, workspacePath, info, channel) {
|
|
|
137
138
|
startResponseMirror(deps, cdp, workspacePath, channel, chatTitle || 'Unknown');
|
|
138
139
|
}
|
|
139
140
|
function startResponseMirror(deps, cdp, workspacePath, channel, chatTitle) {
|
|
141
|
+
// If the primary message handler already has an active monitor for this workspace,
|
|
142
|
+
// skip the passive mirror — the primary handler delivers the response itself, and
|
|
143
|
+
// running both would send the same AI response twice.
|
|
144
|
+
const projectName = deps.bridge.pool.extractProjectName(workspacePath);
|
|
145
|
+
if (deps.activeMonitors?.has(projectName)) {
|
|
146
|
+
logger_1.logger.debug(`[TelegramMirror] Skipping passive monitor — primary monitor active for ${projectName}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
140
149
|
const prev = activeResponseMonitors.get(workspacePath);
|
|
141
150
|
if (prev?.isActive()) {
|
|
142
151
|
prev.stop().catch(() => { });
|
|
@@ -173,6 +173,12 @@ function createTelegramMessageHandler(deps) {
|
|
|
173
173
|
// Determine the prompt text — use default for image-only messages
|
|
174
174
|
const effectivePrompt = promptText || 'Please review the attached images and respond accordingly.';
|
|
175
175
|
const baseline = await (0, responseMonitor_1.captureResponseMonitorBaseline)(cdp);
|
|
176
|
+
// Register the echo hash BEFORE injecting (the Discord path registers it later):
|
|
177
|
+
// UserMessageDetector polls the DOM every ~2s, so registering only after
|
|
178
|
+
// injectMessage() resolves leaves a window where the injected prompt is detected
|
|
179
|
+
// as PC-side input and mirrored back to Telegram. If injection fails, the stale
|
|
180
|
+
// hash is harmless — it expires via its 60s TTL.
|
|
181
|
+
deps.bridge.pool.getUserMessageDetector?.(projectName, selectedAccount)?.addEchoHash(effectivePrompt);
|
|
176
182
|
// Inject prompt (with or without images) into Antigravity
|
|
177
183
|
logger_1.logger.prompt(effectivePrompt);
|
|
178
184
|
let injectResult;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ChatSessionService = void 0;
|
|
4
|
+
exports.buildActivateViaPastConversationsScript = buildActivateViaPastConversationsScript;
|
|
4
5
|
const logger_1 = require("../utils/logger");
|
|
5
6
|
/** Script to get the state of the new chat button */
|
|
6
7
|
const GET_NEW_CHAT_BUTTON_SCRIPT = `(() => {
|
|
@@ -290,9 +291,9 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
290
291
|
|
|
291
292
|
const isVisible = (el) => !!el && el instanceof HTMLElement && el.offsetParent !== null;
|
|
292
293
|
const asArray = (nodeList) => Array.from(nodeList || []);
|
|
293
|
-
const
|
|
294
|
-
if (!el || !(el instanceof Element)) return
|
|
295
|
-
|
|
294
|
+
const getLabelParts = (el) => {
|
|
295
|
+
if (!el || !(el instanceof Element)) return [];
|
|
296
|
+
return [
|
|
296
297
|
el.textContent || '',
|
|
297
298
|
el.getAttribute('aria-label') || '',
|
|
298
299
|
el.getAttribute('title') || '',
|
|
@@ -300,7 +301,26 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
300
301
|
el.getAttribute('data-tooltip-content') || '',
|
|
301
302
|
el.getAttribute('data-testid') || '',
|
|
302
303
|
];
|
|
303
|
-
|
|
304
|
+
};
|
|
305
|
+
const getLabelText = (el) => getLabelParts(el).filter(Boolean).join(' ');
|
|
306
|
+
// Verify the option that was actually selected really is the requested
|
|
307
|
+
// title. getLabelText concatenates several attributes, so a naive
|
|
308
|
+
// whole-string comparison can fail even for a correct pick (e.g. when
|
|
309
|
+
// textContent and aria-label duplicate the same value). Instead, check
|
|
310
|
+
// each label component individually for an exact (or loose-exact) match.
|
|
311
|
+
// If any component matches, report the requested title; otherwise report
|
|
312
|
+
// the actual visible text so the acceptance gate rejects a mere
|
|
313
|
+
// substring/loose selection.
|
|
314
|
+
const resolveMatchedTitle = (el) => {
|
|
315
|
+
const parts = getLabelParts(el);
|
|
316
|
+
for (const part of parts) {
|
|
317
|
+
if (!part) continue;
|
|
318
|
+
if (normalize(part) === wanted || (wantedLoose && normalizeLoose(part) === wantedLoose)) {
|
|
319
|
+
return wantedRaw;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const visible = (parts[0] || '').trim();
|
|
323
|
+
return visible || null;
|
|
304
324
|
};
|
|
305
325
|
const getClickable = (el) => {
|
|
306
326
|
if (!el || !(el instanceof Element)) return null;
|
|
@@ -475,22 +495,23 @@ function buildActivateViaPastConversationsScript(title) {
|
|
|
475
495
|
await wait(260);
|
|
476
496
|
}
|
|
477
497
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
if (!selected) {
|
|
498
|
+
// Resolve the exact option element, then click + focus + press Enter
|
|
499
|
+
// on that same element so the click target and the verification
|
|
500
|
+
// target can never drift apart. Mirror clickByPatterns' scoped
|
|
501
|
+
// selector -> broad fallback behaviour.
|
|
502
|
+
const optionSelector = '[role="option"], li, button, [data-testid*="conversation"]';
|
|
503
|
+
const scopedOptions = asArray(document.querySelectorAll(optionSelector));
|
|
504
|
+
const broadOptions = asArray(document.querySelectorAll('button, [role="button"], a, li, div, span'));
|
|
505
|
+
const optionSource = scopedOptions.length > 0 ? scopedOptions : broadOptions;
|
|
506
|
+
const selectedOption = pickBest(optionSource, [wanted, wantedLoose]);
|
|
507
|
+
const selectedClickable = getClickable(selectedOption);
|
|
508
|
+
if (!selectedClickable) {
|
|
491
509
|
return { ok: false, error: 'Conversation not found in Past Conversations' };
|
|
492
510
|
}
|
|
493
|
-
|
|
511
|
+
selectedClickable.click();
|
|
512
|
+
selectedClickable.focus();
|
|
513
|
+
pressEnter(selectedClickable);
|
|
514
|
+
return { ok: true, matchedTitle: resolveMatchedTitle(selectedOption) };
|
|
494
515
|
})();
|
|
495
516
|
})()`;
|
|
496
517
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazy-gravity",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@inquirer/select": "^5.1.0",
|
|
51
51
|
"better-sqlite3": "^12.6.2",
|
|
52
|
-
"commander": "^
|
|
52
|
+
"commander": "^15.0.0",
|
|
53
53
|
"discord.js": "^14.25.1",
|
|
54
54
|
"dotenv": "^17.3.1",
|
|
55
55
|
"grammy": "^1.41.1",
|