lazy-gravity 0.9.2 → 0.11.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/bin/commands/doctor.js +45 -7
- package/dist/bot/index.js +401 -41
- package/dist/bot/telegramMessageHandler.js +1 -0
- package/dist/commands/chatCommandHandler.js +43 -3
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/events/interactionCreateHandler.js +456 -27
- package/dist/events/messageCreateHandler.js +66 -7
- 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 +57 -0
- package/dist/handlers/genericActionButtonAction.js +72 -0
- package/dist/handlers/planningButtonAction.js +126 -23
- package/dist/handlers/planningModalSubmitAction.js +38 -0
- package/dist/handlers/questionSelectAction.js +70 -0
- package/dist/handlers/questionSkipAction.js +68 -0
- package/dist/handlers/runCommandButtonAction.js +2 -1
- package/dist/platform/discord/discordResponseRenderer.js +164 -0
- package/dist/platform/discord/wrappers.js +76 -0
- package/dist/services/approvalDetector.js +110 -35
- package/dist/services/artifactService.js +103 -37
- package/dist/services/assistantDomExtractor.js +194 -3
- package/dist/services/cdpBridgeManager.js +149 -8
- package/dist/services/cdpConnectionPool.js +22 -2
- package/dist/services/cdpService.js +134 -15
- package/dist/services/chatSessionService.js +147 -40
- package/dist/services/errorPopupDetector.js +23 -11
- package/dist/services/notificationSender.js +80 -3
- package/dist/services/planningDetector.js +69 -25
- package/dist/services/promptDispatcher.js +27 -1
- package/dist/services/questionDetector.js +428 -0
- package/dist/services/responseMonitor.js +45 -3
- 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 +29 -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 +47 -0
- package/package.json +1 -1
|
@@ -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,15 +82,32 @@ 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, walkthroughCustomId, taskCustomId } = 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
|
];
|
|
101
|
+
const artifactButtons = [];
|
|
102
|
+
if (walkthroughCustomId) {
|
|
103
|
+
artifactButtons.push(button(walkthroughCustomId, 'Review walkthrough.md', 'success'));
|
|
104
|
+
}
|
|
105
|
+
if (taskCustomId) {
|
|
106
|
+
artifactButtons.push(button(taskCustomId, 'Review task.md', 'primary'));
|
|
107
|
+
}
|
|
108
|
+
if (artifactButtons.length > 0) {
|
|
109
|
+
components.push(buttonRow(...artifactButtons));
|
|
110
|
+
}
|
|
89
111
|
return { richContent, components };
|
|
90
112
|
}
|
|
91
113
|
/** Build the planning mode notification message. */
|
|
@@ -94,8 +116,15 @@ function buildPlanningNotification(opts) {
|
|
|
94
116
|
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
117
|
? extraFields.reduce((acc, f) => (0, richContentBuilder_1.addField)(acc, f.name, f.value, f.inline), rc)
|
|
96
118
|
: rc, (rc) => (0, richContentBuilder_1.withFooter)(rc, 'Planning mode detected'), (rc) => (0, richContentBuilder_1.withTimestamp)(rc));
|
|
119
|
+
const buttons = [];
|
|
120
|
+
if (opts.hasOpenButton !== false) {
|
|
121
|
+
const openLabel = opts.openText || 'Open Plan';
|
|
122
|
+
buttons.push(button(customId(PLANNING_OPEN_ACTION_PREFIX, projectName, channelId), openLabel, 'primary'));
|
|
123
|
+
}
|
|
124
|
+
const buttonLabel = opts.proceedText || 'Proceed';
|
|
125
|
+
buttons.push(button(customId(PLANNING_PROCEED_ACTION_PREFIX, projectName, channelId), buttonLabel, 'success'));
|
|
97
126
|
const components = [
|
|
98
|
-
buttonRow(
|
|
127
|
+
buttonRow(...buttons),
|
|
99
128
|
];
|
|
100
129
|
return { richContent, components };
|
|
101
130
|
}
|
|
@@ -158,6 +187,54 @@ function buildStatusNotification(opts) {
|
|
|
158
187
|
: rc);
|
|
159
188
|
return { richContent };
|
|
160
189
|
}
|
|
190
|
+
function buildQuestionNotification(params) {
|
|
191
|
+
const { title, description, projectName, channelId, options } = params;
|
|
192
|
+
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));
|
|
193
|
+
const embed = description ? (0, richContentBuilder_1.withDescription)(baseContent, description) : baseContent;
|
|
194
|
+
const selectMenuOptions = options.map((opt, i) => ({
|
|
195
|
+
label: opt.text.substring(0, 100), // Discord max length for label is 100
|
|
196
|
+
value: i.toString(),
|
|
197
|
+
})).slice(0, 25); // Discord max options in a select menu is 25
|
|
198
|
+
// Ensure customIds fit within 100 chars without losing channelId
|
|
199
|
+
const encodeSafe = (str, maxLen) => {
|
|
200
|
+
let encoded = encodeURIComponent(str);
|
|
201
|
+
if (encoded.length <= maxLen)
|
|
202
|
+
return encoded;
|
|
203
|
+
return encoded.substring(0, maxLen).replace(/(%[0-9A-F]?)$/i, '');
|
|
204
|
+
};
|
|
205
|
+
const encodedChannel = encodeURIComponent(channelId);
|
|
206
|
+
const selectOverhead = exports.QUESTION_SELECT_ACTION_PREFIX.length + 2 + encodedChannel.length;
|
|
207
|
+
const safeSelectProjectName = encodeSafe(projectName, 100 - selectOverhead);
|
|
208
|
+
const encodedCustomId = `${exports.QUESTION_SELECT_ACTION_PREFIX}:${safeSelectProjectName}:${encodedChannel}`;
|
|
209
|
+
const skipOverhead = exports.QUESTION_SKIP_ACTION_PREFIX.length + 2 + encodedChannel.length;
|
|
210
|
+
const safeSkipProjectName = encodeSafe(projectName, 100 - skipOverhead);
|
|
211
|
+
const encodedSkipCustomId = `${exports.QUESTION_SKIP_ACTION_PREFIX}:${safeSkipProjectName}:${encodedChannel}`;
|
|
212
|
+
return {
|
|
213
|
+
richContent: embed,
|
|
214
|
+
components: [
|
|
215
|
+
{
|
|
216
|
+
components: [
|
|
217
|
+
{
|
|
218
|
+
type: 'selectMenu',
|
|
219
|
+
customId: encodedCustomId,
|
|
220
|
+
placeholder: 'Select an option...',
|
|
221
|
+
options: selectMenuOptions,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
components: [
|
|
227
|
+
{
|
|
228
|
+
type: 'button',
|
|
229
|
+
customId: encodedSkipCustomId,
|
|
230
|
+
label: 'Skip',
|
|
231
|
+
style: 'secondary',
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
161
238
|
/** Build a progress / phase notification (e.g. "Thinking...", "Generating..."). */
|
|
162
239
|
function buildProgressNotification(opts) {
|
|
163
240
|
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
|
}
|
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PromptDispatcher = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
4
5
|
/**
|
|
5
6
|
* Dispatcher that calls the existing sendPromptToAntigravity.
|
|
6
7
|
* Unifies dependency injection on the caller side and simplifies event handlers.
|
|
7
8
|
*/
|
|
8
9
|
class PromptDispatcher {
|
|
9
10
|
deps;
|
|
11
|
+
activeMonitors = new Map();
|
|
10
12
|
constructor(deps) {
|
|
11
13
|
this.deps = deps;
|
|
12
14
|
}
|
|
13
15
|
async send(req) {
|
|
14
|
-
await this.
|
|
16
|
+
await this._dispatch(req, req.options);
|
|
17
|
+
}
|
|
18
|
+
async resume(req) {
|
|
19
|
+
await this._dispatch(req, { ...req.options, resumeOnly: true });
|
|
20
|
+
}
|
|
21
|
+
async _dispatch(req, options) {
|
|
22
|
+
const channelId = req.message.channelId;
|
|
23
|
+
const existing = this.activeMonitors.get(channelId);
|
|
24
|
+
if (existing) {
|
|
25
|
+
logger_1.logger.info(`[PromptDispatcher] Aborting previous active monitor for channel ${channelId}`);
|
|
26
|
+
await existing.stop().catch((err) => logger_1.logger.error('[PromptDispatcher] Error stopping monitor:', err));
|
|
27
|
+
this.activeMonitors.delete(channelId);
|
|
28
|
+
}
|
|
29
|
+
const wrappedOptions = {
|
|
30
|
+
...(options || {}),
|
|
31
|
+
onMonitorCreated: (monitor) => {
|
|
32
|
+
this.activeMonitors.set(channelId, monitor);
|
|
33
|
+
options?.onMonitorCreated?.(monitor);
|
|
34
|
+
},
|
|
35
|
+
onFullCompletion: () => {
|
|
36
|
+
this.activeMonitors.delete(channelId);
|
|
37
|
+
options?.onFullCompletion?.();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
await this.deps.sendPromptImpl(this.deps.bridge, req.message, req.prompt, req.cdp, this.deps.modeService, this.deps.modelService, req.inboundImages ?? [], wrappedOptions);
|
|
15
41
|
}
|
|
16
42
|
}
|
|
17
43
|
exports.PromptDispatcher = PromptDispatcher;
|