lazy-gravity 0.9.1 → 0.10.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 +233 -41
- package/dist/bot/telegramJoinCommand.js +9 -0
- package/dist/bot/telegramMessageHandler.js +7 -0
- package/dist/commands/chatCommandHandler.js +71 -1
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +324 -26
- package/dist/events/messageCreateHandler.js +53 -4
- package/dist/handlers/__tests__/fileChangeButtonAction.test.js +85 -0
- package/dist/handlers/approvalButtonAction.js +2 -1
- package/dist/handlers/errorPopupButtonAction.js +2 -1
- package/dist/handlers/fileChangeButtonAction.js +69 -0
- package/dist/handlers/genericActionButtonAction.js +74 -0
- package/dist/handlers/planningButtonAction.js +24 -9
- package/dist/handlers/questionSelectAction.js +69 -0
- package/dist/handlers/questionSkipAction.js +67 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/services/approvalDetector.js +70 -24
- package/dist/services/artifactService.js +57 -31
- package/dist/services/assistantDomExtractor.js +174 -3
- package/dist/services/cdpBridgeManager.js +114 -6
- package/dist/services/cdpConnectionPool.js +15 -0
- package/dist/services/cdpService.js +21 -7
- package/dist/services/chatSessionService.js +148 -27
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +70 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +7 -1
- package/dist/services/questionDetector.js +376 -0
- package/dist/services/responseMonitor.js +28 -2
- package/dist/services/runCommandDetector.js +14 -7
- package/dist/ui/artifactsUi.js +4 -3
- package/dist/utils/consecutiveEmptyPollGate.js +21 -0
- package/dist/utils/fileOpenCache.js +33 -0
- package/dist/utils/htmlToDiscordMarkdown.js +5 -2
- package/dist/utils/pathUtils.js +12 -0
- package/dist/utils/projectResolver.js +10 -0
- package/dist/utils/questionActionUtils.js +25 -0
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ErrorPopupDetector = void 0;
|
|
4
|
+
const consecutiveEmptyPollGate_1 = require("../utils/consecutiveEmptyPollGate");
|
|
4
5
|
const logger_1 = require("../utils/logger");
|
|
5
6
|
const approvalDetector_1 = require("./approvalDetector");
|
|
6
7
|
/**
|
|
@@ -107,8 +108,10 @@ class ErrorPopupDetector {
|
|
|
107
108
|
lastDetectedInfo = null;
|
|
108
109
|
/** Timestamp of last notification (for cooldown-based dedup) */
|
|
109
110
|
lastNotifiedAt = 0;
|
|
110
|
-
/** Cooldown period in ms to suppress duplicate notifications
|
|
111
|
-
static COOLDOWN_MS =
|
|
111
|
+
/** Cooldown period in ms to suppress duplicate notifications */
|
|
112
|
+
static COOLDOWN_MS = 5000;
|
|
113
|
+
/** Gate for empty polls before reset */
|
|
114
|
+
emptyPollGate = new consecutiveEmptyPollGate_1.ConsecutiveEmptyPollGate(3);
|
|
112
115
|
constructor(options) {
|
|
113
116
|
this.cdpService = options.cdpService;
|
|
114
117
|
this.pollIntervalMs = options.pollIntervalMs ?? 3000;
|
|
@@ -123,6 +126,7 @@ class ErrorPopupDetector {
|
|
|
123
126
|
this.lastDetectedKey = null;
|
|
124
127
|
this.lastDetectedInfo = null;
|
|
125
128
|
this.lastNotifiedAt = 0;
|
|
129
|
+
this.emptyPollGate.reset();
|
|
126
130
|
this.schedulePoll();
|
|
127
131
|
}
|
|
128
132
|
/** Stop monitoring. */
|
|
@@ -208,28 +212,36 @@ class ErrorPopupDetector {
|
|
|
208
212
|
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
209
213
|
const info = result?.result?.value ?? null;
|
|
210
214
|
if (info) {
|
|
215
|
+
this.emptyPollGate.recordDetection();
|
|
211
216
|
// Duplicate prevention: use title + body snippet as key
|
|
212
217
|
const key = `${info.title}::${info.body.slice(0, 100)}`;
|
|
213
218
|
const now = Date.now();
|
|
214
219
|
const withinCooldown = (now - this.lastNotifiedAt) < ErrorPopupDetector.COOLDOWN_MS;
|
|
215
|
-
if (key !== this.lastDetectedKey
|
|
220
|
+
if (key !== this.lastDetectedKey) {
|
|
221
|
+
this.lastNotifiedAt = now;
|
|
216
222
|
this.lastDetectedKey = key;
|
|
217
223
|
this.lastDetectedInfo = info;
|
|
224
|
+
this.onErrorPopup(info);
|
|
225
|
+
}
|
|
226
|
+
else if (!withinCooldown) {
|
|
218
227
|
this.lastNotifiedAt = now;
|
|
228
|
+
this.lastDetectedInfo = info;
|
|
219
229
|
this.onErrorPopup(info);
|
|
220
230
|
}
|
|
221
|
-
else
|
|
222
|
-
// Same key -- update stored info silently
|
|
231
|
+
else {
|
|
232
|
+
// Same key, within cooldown -- update stored info silently
|
|
223
233
|
this.lastDetectedInfo = info;
|
|
224
234
|
}
|
|
225
235
|
}
|
|
226
236
|
else {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.onResolved
|
|
237
|
+
if (this.emptyPollGate.recordEmptyPoll()) {
|
|
238
|
+
// Reset when popup disappears (prepare for next detection)
|
|
239
|
+
const wasDetected = this.lastDetectedKey !== null;
|
|
240
|
+
this.lastDetectedKey = null;
|
|
241
|
+
this.lastDetectedInfo = null;
|
|
242
|
+
if (wasDetected && this.onResolved) {
|
|
243
|
+
this.onResolved();
|
|
244
|
+
}
|
|
233
245
|
}
|
|
234
246
|
}
|
|
235
247
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* They return a `MessagePayload` that any platform adapter can render.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.QUESTION_SKIP_ACTION_PREFIX = exports.QUESTION_SELECT_ACTION_PREFIX = exports.FEEDBACK_ACTION_PREFIX = void 0;
|
|
9
10
|
exports.buildApprovalNotification = buildApprovalNotification;
|
|
10
11
|
exports.buildPlanningNotification = buildPlanningNotification;
|
|
11
12
|
exports.buildErrorPopupNotification = buildErrorPopupNotification;
|
|
@@ -13,6 +14,7 @@ exports.buildRunCommandNotification = buildRunCommandNotification;
|
|
|
13
14
|
exports.buildAutoApprovedNotification = buildAutoApprovedNotification;
|
|
14
15
|
exports.buildResolvedOverlay = buildResolvedOverlay;
|
|
15
16
|
exports.buildStatusNotification = buildStatusNotification;
|
|
17
|
+
exports.buildQuestionNotification = buildQuestionNotification;
|
|
16
18
|
exports.buildProgressNotification = buildProgressNotification;
|
|
17
19
|
const richContentBuilder_1 = require("../platform/richContentBuilder");
|
|
18
20
|
// ---------------------------------------------------------------------------
|
|
@@ -28,6 +30,9 @@ const ERROR_POPUP_COPY_DEBUG_ACTION_PREFIX = 'error_popup_copy_debug_action';
|
|
|
28
30
|
const ERROR_POPUP_RETRY_ACTION_PREFIX = 'error_popup_retry_action';
|
|
29
31
|
const RUN_COMMAND_RUN_ACTION_PREFIX = 'run_command_run_action';
|
|
30
32
|
const RUN_COMMAND_REJECT_ACTION_PREFIX = 'run_command_reject_action';
|
|
33
|
+
exports.FEEDBACK_ACTION_PREFIX = 'feedback_action';
|
|
34
|
+
exports.QUESTION_SELECT_ACTION_PREFIX = 'question_select_action';
|
|
35
|
+
exports.QUESTION_SKIP_ACTION_PREFIX = 'question_skip_action';
|
|
31
36
|
// ---------------------------------------------------------------------------
|
|
32
37
|
// Notification colours
|
|
33
38
|
// ---------------------------------------------------------------------------
|
|
@@ -77,14 +82,21 @@ function customId(prefix, projectName, channelId) {
|
|
|
77
82
|
// ---------------------------------------------------------------------------
|
|
78
83
|
/** Build the approval notification message. */
|
|
79
84
|
function buildApprovalNotification(opts) {
|
|
80
|
-
const { title, description, projectName, channelId, toolNames, extraFields } = opts;
|
|
85
|
+
const { title, description, projectName, channelId, toolNames, extraFields, hasAlwaysAllow, alwaysAllowText } = opts;
|
|
81
86
|
const richContent = (0, richContentBuilder_1.pipe)((0, richContentBuilder_1.createRichContent)(), (rc) => (0, richContentBuilder_1.withTitle)(rc, title), (rc) => (0, richContentBuilder_1.withDescription)(rc, description), (rc) => (0, richContentBuilder_1.withColor)(rc, COLOR_APPROVAL), (rc) => (0, richContentBuilder_1.addField)(rc, 'Project', projectName, true), (rc) => toolNames && toolNames.length > 0
|
|
82
87
|
? (0, richContentBuilder_1.addField)(rc, 'Tools', toolNames.join(', '), true)
|
|
83
88
|
: rc, (rc) => extraFields
|
|
84
89
|
? extraFields.reduce((acc, f) => (0, richContentBuilder_1.addField)(acc, f.name, f.value, f.inline), rc)
|
|
85
90
|
: rc, (rc) => (0, richContentBuilder_1.withFooter)(rc, 'Approval required'), (rc) => (0, richContentBuilder_1.withTimestamp)(rc));
|
|
91
|
+
const buttons = [
|
|
92
|
+
button(customId(APPROVE_ACTION_PREFIX, projectName, channelId), 'Allow', 'success'),
|
|
93
|
+
];
|
|
94
|
+
if (hasAlwaysAllow) {
|
|
95
|
+
buttons.push(button(customId(ALWAYS_ALLOW_ACTION_PREFIX, projectName, channelId), alwaysAllowText || 'Allow Chat', 'primary'));
|
|
96
|
+
}
|
|
97
|
+
buttons.push(button(customId(DENY_ACTION_PREFIX, projectName, channelId), 'Deny', 'danger'));
|
|
86
98
|
const components = [
|
|
87
|
-
buttonRow(
|
|
99
|
+
buttonRow(...buttons),
|
|
88
100
|
];
|
|
89
101
|
return { richContent, components };
|
|
90
102
|
}
|
|
@@ -94,8 +106,15 @@ function buildPlanningNotification(opts) {
|
|
|
94
106
|
const richContent = (0, richContentBuilder_1.pipe)((0, richContentBuilder_1.createRichContent)(), (rc) => (0, richContentBuilder_1.withTitle)(rc, title), (rc) => (0, richContentBuilder_1.withDescription)(rc, description), (rc) => (0, richContentBuilder_1.withColor)(rc, COLOR_PLANNING), (rc) => extraFields
|
|
95
107
|
? extraFields.reduce((acc, f) => (0, richContentBuilder_1.addField)(acc, f.name, f.value, f.inline), rc)
|
|
96
108
|
: rc, (rc) => (0, richContentBuilder_1.withFooter)(rc, 'Planning mode detected'), (rc) => (0, richContentBuilder_1.withTimestamp)(rc));
|
|
109
|
+
const buttons = [];
|
|
110
|
+
const openLabel = opts.openText || 'Open';
|
|
111
|
+
if (opts.hasOpenButton !== false) {
|
|
112
|
+
buttons.push(button(customId(PLANNING_OPEN_ACTION_PREFIX, projectName, channelId), openLabel, 'primary'));
|
|
113
|
+
}
|
|
114
|
+
const buttonLabel = opts.proceedText || 'Proceed';
|
|
115
|
+
buttons.push(button(customId(PLANNING_PROCEED_ACTION_PREFIX, projectName, channelId), buttonLabel, 'success'));
|
|
97
116
|
const components = [
|
|
98
|
-
buttonRow(
|
|
117
|
+
buttonRow(...buttons),
|
|
99
118
|
];
|
|
100
119
|
return { richContent, components };
|
|
101
120
|
}
|
|
@@ -158,6 +177,54 @@ function buildStatusNotification(opts) {
|
|
|
158
177
|
: rc);
|
|
159
178
|
return { richContent };
|
|
160
179
|
}
|
|
180
|
+
function buildQuestionNotification(params) {
|
|
181
|
+
const { title, description, projectName, channelId, options } = params;
|
|
182
|
+
const baseContent = (0, richContentBuilder_1.pipe)((0, richContentBuilder_1.createRichContent)(), (rc) => (0, richContentBuilder_1.withTitle)(rc, title), (rc) => (0, richContentBuilder_1.withColor)(rc, COLOR_APPROVAL));
|
|
183
|
+
const embed = description ? (0, richContentBuilder_1.withDescription)(baseContent, description) : baseContent;
|
|
184
|
+
const selectMenuOptions = options.map((opt, i) => ({
|
|
185
|
+
label: opt.text.substring(0, 100), // Discord max length for label is 100
|
|
186
|
+
value: i.toString(),
|
|
187
|
+
})).slice(0, 25); // Discord max options in a select menu is 25
|
|
188
|
+
// Ensure customIds fit within 100 chars without losing channelId
|
|
189
|
+
const encodeSafe = (str, maxLen) => {
|
|
190
|
+
let encoded = encodeURIComponent(str);
|
|
191
|
+
if (encoded.length <= maxLen)
|
|
192
|
+
return encoded;
|
|
193
|
+
return encoded.substring(0, maxLen).replace(/(%[0-9A-F]?)$/i, '');
|
|
194
|
+
};
|
|
195
|
+
const encodedChannel = encodeURIComponent(channelId);
|
|
196
|
+
const selectOverhead = exports.QUESTION_SELECT_ACTION_PREFIX.length + 2 + encodedChannel.length;
|
|
197
|
+
const safeSelectProjectName = encodeSafe(projectName, 100 - selectOverhead);
|
|
198
|
+
const encodedCustomId = `${exports.QUESTION_SELECT_ACTION_PREFIX}:${safeSelectProjectName}:${encodedChannel}`;
|
|
199
|
+
const skipOverhead = exports.QUESTION_SKIP_ACTION_PREFIX.length + 2 + encodedChannel.length;
|
|
200
|
+
const safeSkipProjectName = encodeSafe(projectName, 100 - skipOverhead);
|
|
201
|
+
const encodedSkipCustomId = `${exports.QUESTION_SKIP_ACTION_PREFIX}:${safeSkipProjectName}:${encodedChannel}`;
|
|
202
|
+
return {
|
|
203
|
+
richContent: embed,
|
|
204
|
+
components: [
|
|
205
|
+
{
|
|
206
|
+
components: [
|
|
207
|
+
{
|
|
208
|
+
type: 'selectMenu',
|
|
209
|
+
customId: encodedCustomId,
|
|
210
|
+
placeholder: 'Select an option...',
|
|
211
|
+
options: selectMenuOptions,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
components: [
|
|
217
|
+
{
|
|
218
|
+
type: 'button',
|
|
219
|
+
customId: encodedSkipCustomId,
|
|
220
|
+
label: 'Skip',
|
|
221
|
+
style: 'secondary',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
161
228
|
/** Build a progress / phase notification (e.g. "Thinking...", "Generating..."). */
|
|
162
229
|
function buildProgressNotification(opts) {
|
|
163
230
|
const { phase, projectName, detail } = opts;
|
|
@@ -10,33 +10,59 @@ const approvalDetector_1 = require("./approvalDetector");
|
|
|
10
10
|
* and extracts plan metadata from the surrounding DOM elements.
|
|
11
11
|
*/
|
|
12
12
|
const DETECT_PLANNING_SCRIPT = `(() => {
|
|
13
|
-
const OPEN_PATTERNS = ['open'];
|
|
13
|
+
const OPEN_PATTERNS = ['open', 'review'];
|
|
14
14
|
const PROCEED_PATTERNS = ['proceed'];
|
|
15
|
+
const REJECT_PATTERNS = ['reject'];
|
|
16
|
+
const ACCEPT_PATTERNS = ['accept'];
|
|
15
17
|
|
|
16
18
|
const normalize = (text) => (text || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const allButtons = Array.from(document.querySelectorAll('button, a'))
|
|
21
|
+
.filter(btn => btn.offsetParent !== null)
|
|
22
|
+
.reverse();
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
let proceedBtn = null;
|
|
25
|
+
let openBtn = null;
|
|
26
|
+
let rejectBtn = null;
|
|
24
27
|
|
|
25
|
-
const
|
|
28
|
+
for (const btn of allButtons) {
|
|
26
29
|
const t = normalize(btn.textContent || '');
|
|
27
|
-
|
|
28
|
-
}) || null;
|
|
30
|
+
if (t.includes('review changes')) continue; // Ignore file change review button
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// If we hit an accept or reject button before finding a proceed button,
|
|
33
|
+
// it means the most recent state is an Approval, not Planning.
|
|
34
|
+
if (!proceedBtn && (ACCEPT_PATTERNS.some(p => t === p || t.includes(p)) || REJECT_PATTERNS.some(p => t === p || t.includes(p)))) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!proceedBtn && PROCEED_PATTERNS.some(p => t === p || t.includes(p))) {
|
|
39
|
+
proceedBtn = btn;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!openBtn && OPEN_PATTERNS.some(p => t === p || t.includes(p))) {
|
|
44
|
+
openBtn = btn;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!rejectBtn && REJECT_PATTERNS.some(p => t === p || t.includes(p))) {
|
|
48
|
+
rejectBtn = btn;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (proceedBtn && openBtn) break;
|
|
52
|
+
}
|
|
34
53
|
|
|
35
|
-
//
|
|
36
|
-
if (!
|
|
54
|
+
// Proceed button must exist for this to be a planning UI
|
|
55
|
+
if (!proceedBtn) return null;
|
|
56
|
+
|
|
57
|
+
const container = proceedBtn.closest('.notify-user-container') || proceedBtn.closest('div[class*="rounded-lg"]') || proceedBtn.parentElement?.parentElement;
|
|
58
|
+
if (!container) return null;
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
// openBtn was already extracted in the loop above
|
|
61
|
+
|
|
62
|
+
const hasOpenButton = openBtn !== null;
|
|
63
|
+
const openText = openBtn ? (openBtn.textContent || '').trim() : 'Open';
|
|
39
64
|
const proceedText = (proceedBtn.textContent || '').trim();
|
|
65
|
+
const rejectText = rejectBtn ? (rejectBtn.textContent || '').trim() : '';
|
|
40
66
|
|
|
41
67
|
// Extract plan title from .inline-flex.break-all
|
|
42
68
|
const titleEl = container.querySelector('span.inline-flex.break-all, .inline-flex.break-all');
|
|
@@ -67,7 +93,7 @@ const DETECT_PLANNING_SCRIPT = `(() => {
|
|
|
67
93
|
description = parts.join(' ').slice(0, 500);
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
return { openText, proceedText, planTitle, planSummary, description };
|
|
96
|
+
return { openText, proceedText, planTitle, planSummary, description, rejectText, hasOpenButton };
|
|
71
97
|
})()`;
|
|
72
98
|
/**
|
|
73
99
|
* Extract plan content displayed after clicking Open.
|
|
@@ -158,8 +184,12 @@ class PlanningDetector {
|
|
|
158
184
|
lastDetectedInfo = null;
|
|
159
185
|
/** Timestamp of last notification (for cooldown-based dedup) */
|
|
160
186
|
lastNotifiedAt = 0;
|
|
187
|
+
/** Number of consecutive polls without detecting buttons */
|
|
188
|
+
emptyPollCount = 0;
|
|
161
189
|
/** Cooldown period in ms to suppress duplicate notifications */
|
|
162
190
|
static COOLDOWN_MS = 5000;
|
|
191
|
+
/** Number of consecutive empty polls required before resetting state */
|
|
192
|
+
static REQUIRED_EMPTY_POLLS = 3;
|
|
163
193
|
constructor(options) {
|
|
164
194
|
this.cdpService = options.cdpService;
|
|
165
195
|
this.pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
@@ -174,6 +204,7 @@ class PlanningDetector {
|
|
|
174
204
|
this.lastDetectedKey = null;
|
|
175
205
|
this.lastDetectedInfo = null;
|
|
176
206
|
this.lastNotifiedAt = 0;
|
|
207
|
+
this.emptyPollCount = 0;
|
|
177
208
|
this.schedulePoll();
|
|
178
209
|
}
|
|
179
210
|
/** Stop monitoring. */
|
|
@@ -210,6 +241,15 @@ class PlanningDetector {
|
|
|
210
241
|
const text = buttonText ?? this.lastDetectedInfo?.proceedText ?? 'Proceed';
|
|
211
242
|
return this.clickButton(text);
|
|
212
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Click the Reject button via CDP.
|
|
246
|
+
* @param buttonText Text of the button to click (default: detected rejectText or "Reject All")
|
|
247
|
+
* @returns true if click succeeded
|
|
248
|
+
*/
|
|
249
|
+
async clickRejectButton(buttonText) {
|
|
250
|
+
const text = buttonText ?? this.lastDetectedInfo?.rejectText ?? 'Reject All';
|
|
251
|
+
return this.clickButton(text);
|
|
252
|
+
}
|
|
213
253
|
/**
|
|
214
254
|
* Extract plan content from the DOM after Open has been clicked.
|
|
215
255
|
* @returns Plan content text or null if not found
|
|
@@ -255,8 +295,9 @@ class PlanningDetector {
|
|
|
255
295
|
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
256
296
|
const info = result?.result?.value ?? null;
|
|
257
297
|
if (info) {
|
|
258
|
-
|
|
259
|
-
|
|
298
|
+
this.emptyPollCount = 0;
|
|
299
|
+
// Duplicate prevention: use plan title and content as key (stable across DOM redraws and button text updates)
|
|
300
|
+
const key = `${info.planTitle}::${info.planSummary}::${(info.description || '').substring(0, 50)}`;
|
|
260
301
|
const now = Date.now();
|
|
261
302
|
const withinCooldown = (now - this.lastNotifiedAt) < PlanningDetector.COOLDOWN_MS;
|
|
262
303
|
if (key !== this.lastDetectedKey && !withinCooldown) {
|
|
@@ -271,12 +312,15 @@ class PlanningDetector {
|
|
|
271
312
|
}
|
|
272
313
|
}
|
|
273
314
|
else {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.
|
|
315
|
+
this.emptyPollCount++;
|
|
316
|
+
if (this.emptyPollCount >= PlanningDetector.REQUIRED_EMPTY_POLLS) {
|
|
317
|
+
// Reset when buttons disappear for consecutive polls
|
|
318
|
+
const wasDetected = this.lastDetectedKey !== null;
|
|
319
|
+
this.lastDetectedKey = null;
|
|
320
|
+
this.lastDetectedInfo = null;
|
|
321
|
+
if (wasDetected && this.onResolved) {
|
|
322
|
+
this.onResolved();
|
|
323
|
+
}
|
|
280
324
|
}
|
|
281
325
|
}
|
|
282
326
|
}
|
|
@@ -11,7 +11,13 @@ class PromptDispatcher {
|
|
|
11
11
|
this.deps = deps;
|
|
12
12
|
}
|
|
13
13
|
async send(req) {
|
|
14
|
-
await this.
|
|
14
|
+
await this._dispatch(req, req.options);
|
|
15
|
+
}
|
|
16
|
+
async resume(req) {
|
|
17
|
+
await this._dispatch(req, { ...req.options, resumeOnly: true });
|
|
18
|
+
}
|
|
19
|
+
async _dispatch(req, options) {
|
|
20
|
+
await this.deps.sendPromptImpl(this.deps.bridge, req.message, req.prompt, req.cdp, this.deps.modeService, this.deps.modelService, req.inboundImages ?? [], options);
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
exports.PromptDispatcher = PromptDispatcher;
|