lazy-gravity 0.2.0 → 0.3.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 +76 -15
- package/dist/bin/commands/doctor.js +19 -2
- package/dist/bin/commands/setup.js +286 -70
- package/dist/bot/eventRouter.js +70 -0
- package/dist/bot/index.js +353 -147
- package/dist/bot/telegramCommands.js +428 -0
- package/dist/bot/telegramMessageHandler.js +304 -0
- package/dist/bot/telegramProjectCommand.js +137 -0
- package/dist/bot/workspaceQueue.js +61 -0
- package/dist/commands/joinCommandHandler.js +4 -1
- package/dist/database/telegramBindingRepository.js +97 -0
- package/dist/database/userPreferenceRepository.js +46 -1
- package/dist/events/interactionCreateHandler.js +36 -0
- package/dist/events/messageCreateHandler.js +11 -7
- package/dist/handlers/approvalButtonAction.js +99 -0
- package/dist/handlers/autoAcceptButtonAction.js +43 -0
- package/dist/handlers/buttonHandler.js +55 -0
- package/dist/handlers/commandHandler.js +44 -0
- package/dist/handlers/errorPopupButtonAction.js +137 -0
- package/dist/handlers/messageHandler.js +70 -0
- package/dist/handlers/modeSelectAction.js +63 -0
- package/dist/handlers/modelButtonAction.js +102 -0
- package/dist/handlers/planningButtonAction.js +118 -0
- package/dist/handlers/selectHandler.js +41 -0
- package/dist/handlers/templateButtonAction.js +54 -0
- package/dist/platform/adapter.js +8 -0
- package/dist/platform/discord/discordAdapter.js +99 -0
- package/dist/platform/discord/index.js +15 -0
- package/dist/platform/discord/wrappers.js +331 -0
- package/dist/platform/index.js +18 -0
- package/dist/platform/richContentBuilder.js +76 -0
- package/dist/platform/telegram/index.js +16 -0
- package/dist/platform/telegram/telegramAdapter.js +195 -0
- package/dist/platform/telegram/telegramFormatter.js +134 -0
- package/dist/platform/telegram/wrappers.js +329 -0
- package/dist/platform/types.js +28 -0
- package/dist/services/approvalDetector.js +15 -2
- package/dist/services/cdpBridgeManager.js +91 -146
- package/dist/services/defaultModelApplicator.js +54 -0
- package/dist/services/modeService.js +16 -1
- package/dist/services/modelService.js +57 -16
- package/dist/services/notificationSender.js +149 -0
- package/dist/services/responseMonitor.js +1 -2
- package/dist/ui/autoAcceptUi.js +37 -0
- package/dist/ui/modeUi.js +38 -1
- package/dist/ui/modelsUi.js +96 -0
- package/dist/ui/outputUi.js +32 -0
- package/dist/ui/projectListUi.js +55 -0
- package/dist/ui/screenshotUi.js +26 -0
- package/dist/ui/sessionPickerUi.js +35 -1
- package/dist/ui/templateUi.js +41 -0
- package/dist/utils/configLoader.js +63 -12
- package/dist/utils/lockfile.js +5 -5
- package/dist/utils/logger.js +7 -0
- package/dist/utils/telegramImageHandler.js +127 -0
- package/package.json +4 -2
|
@@ -16,10 +16,9 @@ exports.ensureApprovalDetector = ensureApprovalDetector;
|
|
|
16
16
|
exports.ensurePlanningDetector = ensurePlanningDetector;
|
|
17
17
|
exports.ensureErrorPopupDetector = ensureErrorPopupDetector;
|
|
18
18
|
exports.ensureUserMessageDetector = ensureUserMessageDetector;
|
|
19
|
-
const discord_js_1 = require("discord.js");
|
|
20
19
|
const i18n_1 = require("../utils/i18n");
|
|
21
20
|
const logger_1 = require("../utils/logger");
|
|
22
|
-
const
|
|
21
|
+
const notificationSender_1 = require("./notificationSender");
|
|
23
22
|
const approvalDetector_1 = require("./approvalDetector");
|
|
24
23
|
const autoAcceptService_1 = require("./autoAcceptService");
|
|
25
24
|
const cdpConnectionPool_1 = require("./cdpConnectionPool");
|
|
@@ -230,89 +229,66 @@ function getCurrentCdp(bridge) {
|
|
|
230
229
|
* Helper to start an approval detector for each workspace.
|
|
231
230
|
* Does nothing if a detector for the same workspace is already running.
|
|
232
231
|
*/
|
|
233
|
-
function ensureApprovalDetector(bridge, cdp, projectName
|
|
232
|
+
function ensureApprovalDetector(bridge, cdp, projectName) {
|
|
234
233
|
const existing = bridge.pool.getApprovalDetector(projectName);
|
|
235
234
|
if (existing && existing.isActive())
|
|
236
235
|
return;
|
|
237
|
-
// Track the most recent
|
|
238
|
-
// Only the latest
|
|
239
|
-
// is resolved, the older
|
|
236
|
+
// Track the most recent notification for auto-disable on resolve.
|
|
237
|
+
// Only the latest is tracked; if a new detection fires before the previous
|
|
238
|
+
// is resolved, the older reference is overwritten. This is acceptable because
|
|
240
239
|
// the detector's lastDetectedKey deduplication prevents rapid successive notifications.
|
|
241
|
-
let
|
|
240
|
+
let lastNotification = null;
|
|
242
241
|
const detector = new approvalDetector_1.ApprovalDetector({
|
|
243
242
|
cdpService: cdp,
|
|
244
243
|
pollIntervalMs: 2000,
|
|
245
244
|
onResolved: () => {
|
|
246
|
-
if (!
|
|
245
|
+
if (!lastNotification)
|
|
247
246
|
return;
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
253
|
-
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Approval Required'));
|
|
254
|
-
updatedEmbed
|
|
255
|
-
.setColor(0x95A5A6)
|
|
256
|
-
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
257
|
-
msg.edit({
|
|
258
|
-
embeds: [updatedEmbed],
|
|
259
|
-
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
260
|
-
}).catch(logger_1.logger.error);
|
|
247
|
+
const { sent, payload } = lastNotification;
|
|
248
|
+
lastNotification = null;
|
|
249
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Resolved in Antigravity'));
|
|
250
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
261
251
|
},
|
|
262
252
|
onApprovalRequired: async (info) => {
|
|
263
253
|
logger_1.logger.debug(`[ApprovalDetector:${projectName}] Approval button detected (allow="${info.approveText}", deny="${info.denyText}")`);
|
|
264
254
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
265
255
|
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
266
|
-
const targetChannelId = targetChannel
|
|
267
|
-
if (!targetChannel || !targetChannelId
|
|
268
|
-
logger_1.logger.warn(`[ApprovalDetector:${projectName}] Skipped approval notification because chat is not linked to a
|
|
256
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
257
|
+
if (!targetChannel || !targetChannelId) {
|
|
258
|
+
logger_1.logger.warn(`[ApprovalDetector:${projectName}] Skipped approval notification because chat is not linked to a session` +
|
|
269
259
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
270
260
|
return;
|
|
271
261
|
}
|
|
272
262
|
if (bridge.autoAccept.isEnabled()) {
|
|
273
263
|
const accepted = await detector.alwaysAllowButton() || await detector.approveButton();
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
if (info.approveText) {
|
|
283
|
-
autoEmbed.addFields({ name: (0, i18n_1.t)('Approved via'), value: info.approveText, inline: true });
|
|
284
|
-
}
|
|
285
|
-
autoEmbed.setTimestamp();
|
|
286
|
-
await targetChannel.send({ embeds: [autoEmbed] }).catch(logger_1.logger.error);
|
|
264
|
+
const autoPayload = (0, notificationSender_1.buildAutoApprovedNotification)({
|
|
265
|
+
accepted,
|
|
266
|
+
projectName,
|
|
267
|
+
description: info.description ?? undefined,
|
|
268
|
+
approveText: info.approveText ?? undefined,
|
|
269
|
+
});
|
|
270
|
+
await targetChannel.send(autoPayload).catch(logger_1.logger.error);
|
|
287
271
|
if (accepted) {
|
|
288
272
|
return;
|
|
289
273
|
}
|
|
290
274
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
.setCustomId(buildApprovalCustomId('deny', projectName, targetChannelId))
|
|
307
|
-
.setLabel((0, i18n_1.t)('Deny'))
|
|
308
|
-
.setStyle(discord_js_1.ButtonStyle.Danger);
|
|
309
|
-
const row = new discord_js_1.ActionRowBuilder().addComponents(approveBtn, alwaysAllowBtn, denyBtn);
|
|
310
|
-
const sent = await targetChannel.send({
|
|
311
|
-
embeds: [embed],
|
|
312
|
-
components: [row],
|
|
313
|
-
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
275
|
+
const payload = (0, notificationSender_1.buildApprovalNotification)({
|
|
276
|
+
title: (0, i18n_1.t)('Approval Required'),
|
|
277
|
+
description: info.description || (0, i18n_1.t)('Antigravity is requesting approval for an action'),
|
|
278
|
+
projectName,
|
|
279
|
+
channelId: targetChannelId,
|
|
280
|
+
extraFields: [
|
|
281
|
+
{ name: (0, i18n_1.t)('Allow button'), value: info.approveText, inline: true },
|
|
282
|
+
{ name: (0, i18n_1.t)('Allow Chat button'), value: info.alwaysAllowText || (0, i18n_1.t)('In Dropdown'), inline: true },
|
|
283
|
+
{ name: (0, i18n_1.t)('Deny button'), value: info.denyText || (0, i18n_1.t)('(None)'), inline: true },
|
|
284
|
+
],
|
|
285
|
+
});
|
|
286
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
287
|
+
logger_1.logger.error(err);
|
|
288
|
+
return null;
|
|
289
|
+
});
|
|
314
290
|
if (sent) {
|
|
315
|
-
|
|
291
|
+
lastNotification = { sent, payload };
|
|
316
292
|
}
|
|
317
293
|
},
|
|
318
294
|
});
|
|
@@ -324,68 +300,55 @@ function ensureApprovalDetector(bridge, cdp, projectName, client) {
|
|
|
324
300
|
* Helper to start a planning detector for each workspace.
|
|
325
301
|
* Does nothing if a detector for the same workspace is already running.
|
|
326
302
|
*/
|
|
327
|
-
function ensurePlanningDetector(bridge, cdp, projectName
|
|
303
|
+
function ensurePlanningDetector(bridge, cdp, projectName) {
|
|
328
304
|
const existing = bridge.pool.getPlanningDetector(projectName);
|
|
329
305
|
if (existing && existing.isActive())
|
|
330
306
|
return;
|
|
331
|
-
// Track the most recent planning
|
|
307
|
+
// Track the most recent planning notification for auto-disable on resolve.
|
|
332
308
|
// See ensureApprovalDetector comment for tracking limitation rationale.
|
|
333
|
-
let
|
|
309
|
+
let lastNotification = null;
|
|
334
310
|
const detector = new planningDetector_1.PlanningDetector({
|
|
335
311
|
cdpService: cdp,
|
|
336
312
|
pollIntervalMs: 2000,
|
|
337
313
|
onResolved: () => {
|
|
338
|
-
if (!
|
|
314
|
+
if (!lastNotification)
|
|
339
315
|
return;
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
345
|
-
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Planning Mode'));
|
|
346
|
-
updatedEmbed
|
|
347
|
-
.setColor(0x95A5A6)
|
|
348
|
-
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
349
|
-
msg.edit({
|
|
350
|
-
embeds: [updatedEmbed],
|
|
351
|
-
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
352
|
-
}).catch(logger_1.logger.error);
|
|
316
|
+
const { sent, payload } = lastNotification;
|
|
317
|
+
lastNotification = null;
|
|
318
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Resolved in Antigravity'));
|
|
319
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
353
320
|
},
|
|
354
321
|
onPlanningRequired: async (info) => {
|
|
355
322
|
logger_1.logger.debug(`[PlanningDetector:${projectName}] Planning buttons detected (title="${info.planTitle}")`);
|
|
356
323
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
357
324
|
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
358
|
-
const targetChannelId = targetChannel
|
|
359
|
-
if (!targetChannel || !targetChannelId
|
|
360
|
-
logger_1.logger.warn(`[PlanningDetector:${projectName}] Skipped planning notification because chat is not linked to a
|
|
325
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
326
|
+
if (!targetChannel || !targetChannelId) {
|
|
327
|
+
logger_1.logger.warn(`[PlanningDetector:${projectName}] Skipped planning notification because chat is not linked to a session` +
|
|
361
328
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
362
329
|
return;
|
|
363
330
|
}
|
|
364
331
|
const descriptionText = info.description || info.planSummary || (0, i18n_1.t)('A plan has been generated and is awaiting your review.');
|
|
365
|
-
const
|
|
366
|
-
.
|
|
367
|
-
.
|
|
368
|
-
|
|
369
|
-
.addFields({ name: (0, i18n_1.t)('Plan'), value: info.planTitle || (0, i18n_1.t)('Implementation Plan'), inline: true }, { name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true })
|
|
370
|
-
.setTimestamp();
|
|
332
|
+
const extraFields = [
|
|
333
|
+
{ name: (0, i18n_1.t)('Plan'), value: info.planTitle || (0, i18n_1.t)('Implementation Plan'), inline: true },
|
|
334
|
+
{ name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true },
|
|
335
|
+
];
|
|
371
336
|
if (info.planSummary && info.description) {
|
|
372
|
-
|
|
337
|
+
extraFields.push({ name: (0, i18n_1.t)('Summary'), value: info.planSummary.substring(0, 1024), inline: false });
|
|
373
338
|
}
|
|
374
|
-
const
|
|
375
|
-
.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
components: [row],
|
|
386
|
-
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
339
|
+
const payload = (0, notificationSender_1.buildPlanningNotification)({
|
|
340
|
+
title: (0, i18n_1.t)('Planning Mode'),
|
|
341
|
+
description: descriptionText,
|
|
342
|
+
projectName,
|
|
343
|
+
channelId: targetChannelId,
|
|
344
|
+
extraFields,
|
|
345
|
+
});
|
|
346
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
347
|
+
logger_1.logger.error(err);
|
|
348
|
+
return null;
|
|
349
|
+
});
|
|
387
350
|
if (sent) {
|
|
388
|
-
|
|
351
|
+
lastNotification = { sent, payload };
|
|
389
352
|
}
|
|
390
353
|
},
|
|
391
354
|
});
|
|
@@ -397,69 +360,51 @@ function ensurePlanningDetector(bridge, cdp, projectName, _client) {
|
|
|
397
360
|
* Helper to start an error popup detector for each workspace.
|
|
398
361
|
* Does nothing if a detector for the same workspace is already running.
|
|
399
362
|
*/
|
|
400
|
-
function ensureErrorPopupDetector(bridge, cdp, projectName
|
|
363
|
+
function ensureErrorPopupDetector(bridge, cdp, projectName) {
|
|
401
364
|
const existing = bridge.pool.getErrorPopupDetector(projectName);
|
|
402
365
|
if (existing && existing.isActive())
|
|
403
366
|
return;
|
|
404
|
-
// Track the most recent error
|
|
367
|
+
// Track the most recent error notification for auto-disable on resolve.
|
|
405
368
|
// See ensureApprovalDetector comment for tracking limitation rationale.
|
|
406
|
-
let
|
|
369
|
+
let lastNotification = null;
|
|
407
370
|
const detector = new errorPopupDetector_1.ErrorPopupDetector({
|
|
408
371
|
cdpService: cdp,
|
|
409
372
|
pollIntervalMs: 3000,
|
|
410
373
|
onResolved: () => {
|
|
411
|
-
if (!
|
|
374
|
+
if (!lastNotification)
|
|
412
375
|
return;
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
418
|
-
: new discord_js_1.EmbedBuilder().setTitle((0, i18n_1.t)('Agent Error'));
|
|
419
|
-
updatedEmbed
|
|
420
|
-
.setColor(0x95A5A6)
|
|
421
|
-
.addFields({ name: (0, i18n_1.t)('Status'), value: (0, i18n_1.t)('Resolved in Antigravity'), inline: false });
|
|
422
|
-
msg.edit({
|
|
423
|
-
embeds: [updatedEmbed],
|
|
424
|
-
components: (0, discordButtonUtils_1.disableAllButtons)(msg.components),
|
|
425
|
-
}).catch(logger_1.logger.error);
|
|
376
|
+
const { sent, payload } = lastNotification;
|
|
377
|
+
lastNotification = null;
|
|
378
|
+
const resolved = (0, notificationSender_1.buildResolvedOverlay)(payload, (0, i18n_1.t)('Resolved in Antigravity'));
|
|
379
|
+
sent.edit(resolved).catch(logger_1.logger.error);
|
|
426
380
|
},
|
|
427
381
|
onErrorPopup: async (info) => {
|
|
428
382
|
logger_1.logger.debug(`[ErrorPopupDetector:${projectName}] Error popup detected (title="${info.title}")`);
|
|
429
383
|
const currentChatTitle = await getCurrentChatTitle(cdp);
|
|
430
384
|
const targetChannel = resolveApprovalChannelForCurrentChat(bridge, projectName, currentChatTitle);
|
|
431
|
-
const targetChannelId = targetChannel
|
|
432
|
-
if (!targetChannel || !targetChannelId
|
|
433
|
-
logger_1.logger.warn(`[ErrorPopupDetector:${projectName}] Skipped error popup notification because chat is not linked to a
|
|
385
|
+
const targetChannelId = targetChannel ? targetChannel.id : '';
|
|
386
|
+
if (!targetChannel || !targetChannelId) {
|
|
387
|
+
logger_1.logger.warn(`[ErrorPopupDetector:${projectName}] Skipped error popup notification because chat is not linked to a session` +
|
|
434
388
|
`${currentChatTitle ? ` (title="${currentChatTitle}")` : ''}`);
|
|
435
389
|
return;
|
|
436
390
|
}
|
|
437
391
|
const bodyText = info.body || (0, i18n_1.t)('An error occurred in the Antigravity agent.');
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const retryBtn = new discord_js_1.ButtonBuilder()
|
|
453
|
-
.setCustomId(buildErrorPopupCustomId('retry', projectName, targetChannelId))
|
|
454
|
-
.setLabel((0, i18n_1.t)('Retry'))
|
|
455
|
-
.setStyle(discord_js_1.ButtonStyle.Success);
|
|
456
|
-
const row = new discord_js_1.ActionRowBuilder().addComponents(dismissBtn, copyDebugBtn, retryBtn);
|
|
457
|
-
const sent = await targetChannel.send({
|
|
458
|
-
embeds: [embed],
|
|
459
|
-
components: [row],
|
|
460
|
-
}).catch((err) => { logger_1.logger.error(err); return null; });
|
|
392
|
+
const payload = (0, notificationSender_1.buildErrorPopupNotification)({
|
|
393
|
+
title: info.title || (0, i18n_1.t)('Agent Error'),
|
|
394
|
+
errorMessage: bodyText.substring(0, 4096),
|
|
395
|
+
projectName,
|
|
396
|
+
channelId: targetChannelId,
|
|
397
|
+
extraFields: [
|
|
398
|
+
{ name: (0, i18n_1.t)('Buttons'), value: info.buttons.join(', ') || (0, i18n_1.t)('(None)'), inline: true },
|
|
399
|
+
{ name: (0, i18n_1.t)('Workspace'), value: projectName, inline: true },
|
|
400
|
+
],
|
|
401
|
+
});
|
|
402
|
+
const sent = await targetChannel.send(payload).catch((err) => {
|
|
403
|
+
logger_1.logger.error(err);
|
|
404
|
+
return null;
|
|
405
|
+
});
|
|
461
406
|
if (sent) {
|
|
462
|
-
|
|
407
|
+
lastNotification = { sent, payload };
|
|
463
408
|
}
|
|
464
409
|
},
|
|
465
410
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Single-responsibility module for applying the user's default model
|
|
4
|
+
* preference when a CDP session connects.
|
|
5
|
+
*
|
|
6
|
+
* Strategy: exact match only — no fuzzy matching to avoid selecting
|
|
7
|
+
* the wrong model after Antigravity renames a model.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.applyDefaultModel = applyDefaultModel;
|
|
11
|
+
const logger_1 = require("../utils/logger");
|
|
12
|
+
/**
|
|
13
|
+
* Apply the user's default model preference to a CDP session.
|
|
14
|
+
*
|
|
15
|
+
* 1. Read modelService.getDefaultModel() — if null, skip
|
|
16
|
+
* 2. Read cdp.getCurrentModel() — if already matches, skip (mark synced)
|
|
17
|
+
* 3. Get available models via cdp.getUiModels()
|
|
18
|
+
* 4. Exact match → cdp.setUiModel() → mark synced
|
|
19
|
+
* 5. No match → return stale result with message + available model list
|
|
20
|
+
*/
|
|
21
|
+
async function applyDefaultModel(cdp, modelService) {
|
|
22
|
+
const defaultModel = modelService.getDefaultModel();
|
|
23
|
+
if (!defaultModel) {
|
|
24
|
+
return { applied: false, modelName: null, stale: false, staleMessage: null };
|
|
25
|
+
}
|
|
26
|
+
const currentModel = await cdp.getCurrentModel();
|
|
27
|
+
if (currentModel && currentModel.toLowerCase() === defaultModel.toLowerCase()) {
|
|
28
|
+
modelService.markSynced();
|
|
29
|
+
logger_1.logger.debug(`[DefaultModelApplicator] Already on default model: ${defaultModel}`);
|
|
30
|
+
return { applied: true, modelName: defaultModel, stale: false, staleMessage: null };
|
|
31
|
+
}
|
|
32
|
+
const availableModels = await cdp.getUiModels();
|
|
33
|
+
const exactMatch = availableModels.find(m => m.toLowerCase() === defaultModel.toLowerCase());
|
|
34
|
+
if (exactMatch) {
|
|
35
|
+
const result = await cdp.setUiModel(exactMatch);
|
|
36
|
+
if (result.ok) {
|
|
37
|
+
modelService.markSynced();
|
|
38
|
+
logger_1.logger.debug(`[DefaultModelApplicator] Applied default model: ${exactMatch}`);
|
|
39
|
+
return { applied: true, modelName: exactMatch, stale: false, staleMessage: null };
|
|
40
|
+
}
|
|
41
|
+
logger_1.logger.warn(`[DefaultModelApplicator] setUiModel failed: ${result.error}`);
|
|
42
|
+
return { applied: false, modelName: defaultModel, stale: false, staleMessage: null };
|
|
43
|
+
}
|
|
44
|
+
// No exact match — model is stale
|
|
45
|
+
const availableList = availableModels.join(', ');
|
|
46
|
+
const staleMessage = `Saved default model "${defaultModel}" is no longer available. Available models: ${availableList}`;
|
|
47
|
+
logger_1.logger.warn(`[DefaultModelApplicator] ${staleMessage}`);
|
|
48
|
+
return {
|
|
49
|
+
applied: false,
|
|
50
|
+
modelName: defaultModel,
|
|
51
|
+
stale: true,
|
|
52
|
+
staleMessage,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -33,17 +33,31 @@ exports.DEFAULT_MODE = 'fast';
|
|
|
33
33
|
*/
|
|
34
34
|
class ModeService {
|
|
35
35
|
currentMode = exports.DEFAULT_MODE;
|
|
36
|
+
pendingSync = false;
|
|
36
37
|
/**
|
|
37
38
|
* Get the current execution mode
|
|
38
39
|
*/
|
|
39
40
|
getCurrentMode() {
|
|
40
41
|
return this.currentMode;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if the current mode is pending sync to Antigravity
|
|
45
|
+
*/
|
|
46
|
+
isPendingSync() {
|
|
47
|
+
return this.pendingSync;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mark the pending mode as synced (clears pendingSync flag)
|
|
51
|
+
*/
|
|
52
|
+
markSynced() {
|
|
53
|
+
this.pendingSync = false;
|
|
54
|
+
}
|
|
42
55
|
/**
|
|
43
56
|
* Switch execution mode
|
|
44
57
|
* @param modeName Mode name to set (case-insensitive)
|
|
58
|
+
* @param synced Whether the mode has been synced to Antigravity (default: false)
|
|
45
59
|
*/
|
|
46
|
-
setMode(modeName) {
|
|
60
|
+
setMode(modeName, synced = false) {
|
|
47
61
|
if (!modeName || modeName.trim() === '') {
|
|
48
62
|
return {
|
|
49
63
|
success: false,
|
|
@@ -58,6 +72,7 @@ class ModeService {
|
|
|
58
72
|
};
|
|
59
73
|
}
|
|
60
74
|
this.currentMode = normalized;
|
|
75
|
+
this.pendingSync = !synced;
|
|
61
76
|
return {
|
|
62
77
|
success: true,
|
|
63
78
|
mode: this.currentMode,
|
|
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ModelService = exports.DEFAULT_MODEL = exports.AVAILABLE_MODELS = void 0;
|
|
4
4
|
const i18n_1 = require("../utils/i18n");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Fallback model list used when CDP is not connected.
|
|
7
|
+
* NOT used for validation — CDP is the sole source of truth
|
|
8
|
+
* for available models. This list may become stale after
|
|
9
|
+
* Antigravity updates.
|
|
9
10
|
*/
|
|
10
11
|
exports.AVAILABLE_MODELS = [
|
|
11
12
|
'gemini-3.1-pro-high',
|
|
@@ -15,14 +16,20 @@ exports.AVAILABLE_MODELS = [
|
|
|
15
16
|
'claude-opus-4.6-thinking',
|
|
16
17
|
'gpt-oss-120b-medium'
|
|
17
18
|
];
|
|
18
|
-
/** Default LLM model */
|
|
19
|
+
/** Default LLM model (initial value before CDP connects) */
|
|
19
20
|
exports.DEFAULT_MODEL = 'gemini-3-flash';
|
|
20
21
|
/**
|
|
21
22
|
* Service class for managing LLM models.
|
|
22
23
|
* Handles model switching via the /model command.
|
|
24
|
+
*
|
|
25
|
+
* Model validation is intentionally NOT performed here.
|
|
26
|
+
* The actual model list is dynamic (fetched from CDP via
|
|
27
|
+
* cdp.getUiModels()), so setModel() accepts any string.
|
|
23
28
|
*/
|
|
24
29
|
class ModelService {
|
|
25
30
|
currentModel = exports.DEFAULT_MODEL;
|
|
31
|
+
defaultModel = null;
|
|
32
|
+
pendingSync = false;
|
|
26
33
|
/**
|
|
27
34
|
* Get the current LLM model
|
|
28
35
|
*/
|
|
@@ -30,34 +37,68 @@ class ModelService {
|
|
|
30
37
|
return this.currentModel;
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
|
-
*
|
|
40
|
+
* Check if the current model is pending sync to Antigravity
|
|
41
|
+
*/
|
|
42
|
+
isPendingSync() {
|
|
43
|
+
return this.pendingSync;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Mark the pending model as synced (clears pendingSync flag)
|
|
47
|
+
*/
|
|
48
|
+
markSynced() {
|
|
49
|
+
this.pendingSync = false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Switch LLM model.
|
|
53
|
+
* Accepts any model name — validation happens at the CDP layer
|
|
54
|
+
* (cdp.setUiModel) against the live model list.
|
|
55
|
+
*
|
|
34
56
|
* @param modelName Model name to set (case-insensitive)
|
|
57
|
+
* @param synced Whether the model has been synced to Antigravity (default: false)
|
|
35
58
|
*/
|
|
36
|
-
setModel(modelName) {
|
|
59
|
+
setModel(modelName, synced = false) {
|
|
37
60
|
if (!modelName || modelName.trim() === '') {
|
|
38
61
|
return {
|
|
39
62
|
success: false,
|
|
40
|
-
error: (0, i18n_1.t)('⚠️ Model name not specified.
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
const normalized = modelName.trim().toLowerCase();
|
|
44
|
-
if (!exports.AVAILABLE_MODELS.includes(normalized)) {
|
|
45
|
-
return {
|
|
46
|
-
success: false,
|
|
47
|
-
error: (0, i18n_1.t)(`⚠️ Invalid model "${modelName}". Available models: ${exports.AVAILABLE_MODELS.join(', ')}`),
|
|
63
|
+
error: (0, i18n_1.t)('⚠️ Model name not specified.'),
|
|
48
64
|
};
|
|
49
65
|
}
|
|
50
|
-
this.currentModel =
|
|
66
|
+
this.currentModel = modelName.trim().toLowerCase();
|
|
67
|
+
this.pendingSync = !synced;
|
|
51
68
|
return {
|
|
52
69
|
success: true,
|
|
53
70
|
model: this.currentModel,
|
|
54
71
|
};
|
|
55
72
|
}
|
|
56
73
|
/**
|
|
57
|
-
* Get the list of available models
|
|
74
|
+
* Get the fallback list of available models.
|
|
75
|
+
* Prefer cdp.getUiModels() when CDP is connected.
|
|
58
76
|
*/
|
|
59
77
|
getAvailableModels() {
|
|
60
78
|
return exports.AVAILABLE_MODELS;
|
|
61
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the default model name (free-text, may not match current CDP models)
|
|
82
|
+
*/
|
|
83
|
+
getDefaultModel() {
|
|
84
|
+
return this.defaultModel;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Set the default model name (free-text, persisted via DB separately)
|
|
88
|
+
* @param name Model name or null to clear
|
|
89
|
+
*/
|
|
90
|
+
setDefaultModel(name) {
|
|
91
|
+
this.defaultModel = name ? name.trim() : null;
|
|
92
|
+
return { success: true, defaultModel: this.defaultModel };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Load the default model from an external source (e.g. DB).
|
|
96
|
+
* Only sets the in-memory value if not already set.
|
|
97
|
+
*/
|
|
98
|
+
loadDefaultModel(name) {
|
|
99
|
+
if (this.defaultModel === null && name) {
|
|
100
|
+
this.defaultModel = name.trim();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
62
103
|
}
|
|
63
104
|
exports.ModelService = ModelService;
|