lazy-gravity 0.6.2 → 0.7.1
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 +354 -28
- package/dist/bot/telegramCommands.js +175 -48
- package/dist/bot/telegramJoinCommand.js +170 -0
- package/dist/bot/telegramMessageHandler.js +24 -7
- package/dist/bot/telegramProjectCommand.js +71 -18
- package/dist/bot/telegramStartupTarget.js +54 -0
- package/dist/commands/chatCommandHandler.js +8 -12
- package/dist/commands/joinCommandHandler.js +16 -10
- package/dist/commands/registerSlashCommands.js +13 -1
- package/dist/commands/workspaceCommandHandler.js +22 -7
- package/dist/database/accountPreferenceRepository.js +29 -0
- package/dist/database/channelPreferenceRepository.js +29 -0
- package/dist/database/chatSessionRepository.js +66 -3
- package/dist/database/telegramBindingRepository.js +13 -0
- package/dist/events/interactionCreateHandler.js +194 -13
- package/dist/events/messageCreateHandler.js +103 -7
- package/dist/handlers/accountSelectAction.js +45 -0
- package/dist/handlers/modelButtonAction.js +13 -0
- package/dist/services/cdpBridgeManager.js +23 -18
- package/dist/services/cdpConnectionPool.js +133 -206
- package/dist/services/cdpService.js +14 -5
- package/dist/services/chatSessionService.js +199 -16
- package/dist/services/userMessageDetector.js +4 -4
- package/dist/ui/accountUi.js +60 -0
- package/dist/utils/accountUtils.js +36 -0
- package/dist/utils/cdpPorts.js +97 -2
- package/dist/utils/configLoader.js +14 -0
- package/dist/utils/lockfile.js +33 -41
- package/package.json +2 -1
|
@@ -12,8 +12,92 @@ const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
|
12
12
|
const projectListUi_1 = require("../ui/projectListUi");
|
|
13
13
|
const modeService_1 = require("../services/modeService");
|
|
14
14
|
const sessionPickerUi_1 = require("../ui/sessionPickerUi");
|
|
15
|
+
const accountUtils_1 = require("../utils/accountUtils");
|
|
16
|
+
const accountUi_1 = require("../ui/accountUi");
|
|
15
17
|
function createInteractionCreateHandler(deps) {
|
|
18
|
+
const getParentChannelId = (interaction) => (0, accountUtils_1.inferParentScopeChannelId)(interaction.channelId, interaction.channel?.parentId ?? null);
|
|
19
|
+
const getSessionAccountName = (channelId) => deps.chatSessionRepo?.findByChannelId(channelId)?.activeAccountName ?? null;
|
|
20
|
+
const resolveSelectedAccount = (channelId, userId, parentChannelId) => (0, accountUtils_1.resolveScopedAccountName)({
|
|
21
|
+
channelId,
|
|
22
|
+
userId,
|
|
23
|
+
sessionAccountName: getSessionAccountName(channelId),
|
|
24
|
+
parentChannelId,
|
|
25
|
+
selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
|
|
26
|
+
channelPrefRepo: deps.channelPrefRepo,
|
|
27
|
+
accountPrefRepo: deps.accountPrefRepo,
|
|
28
|
+
accounts: deps.antigravityAccounts,
|
|
29
|
+
});
|
|
30
|
+
const getChannelCdp = (channelId, userId) => (() => {
|
|
31
|
+
const workspacePath = deps.wsHandler.getWorkspaceForChannel(channelId);
|
|
32
|
+
if (workspacePath) {
|
|
33
|
+
const projectName = deps.bridge.pool.extractProjectName(workspacePath);
|
|
34
|
+
return deps.bridge.pool.getConnected(projectName, resolveSelectedAccount(channelId, userId));
|
|
35
|
+
}
|
|
36
|
+
return deps.bridge.lastActiveWorkspace
|
|
37
|
+
? deps.bridge.pool.getConnected(deps.bridge.lastActiveWorkspace, resolveSelectedAccount(channelId, userId))
|
|
38
|
+
: null;
|
|
39
|
+
})();
|
|
40
|
+
const ensureBoundSessionActive = async (channelId, userId, cdp) => {
|
|
41
|
+
const savedTitle = deps.chatSessionRepo?.findByChannelId(channelId)?.displayName?.trim() || '';
|
|
42
|
+
if (!savedTitle || savedTitle === (0, i18n_1.t)('(Untitled)') || !deps.chatSessionService) {
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
const current = await deps.chatSessionService.getCurrentSessionInfo(cdp);
|
|
46
|
+
if (current.title.trim() === savedTitle) {
|
|
47
|
+
return { ok: true };
|
|
48
|
+
}
|
|
49
|
+
logger_1.logger.info(`[ModelCommand] source=button channel=${channelId} user=${userId} ` +
|
|
50
|
+
`restoringSession target="${savedTitle}" current="${current.title.trim() || '(unknown)'}"`);
|
|
51
|
+
const activation = await deps.chatSessionService.activateSessionByTitle(cdp, savedTitle, {
|
|
52
|
+
maxWaitMs: 8000,
|
|
53
|
+
retryIntervalMs: 300,
|
|
54
|
+
allowVisibilityWarmupMs: 1000,
|
|
55
|
+
});
|
|
56
|
+
if (!activation.ok) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: `Failed to activate saved session "${savedTitle}" before model action: ${activation.error || 'unknown'}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const refresh = await deps.chatSessionService.refreshSessionViewIfStuck(cdp, savedTitle);
|
|
63
|
+
if (!refresh.ok) {
|
|
64
|
+
logger_1.logger.warn(`[ModelCommand] source=button channel=${channelId} user=${userId} ` +
|
|
65
|
+
`sessionRefreshWarning target="${savedTitle}" error="${refresh.error || 'unknown'}"`);
|
|
66
|
+
}
|
|
67
|
+
return { ok: true };
|
|
68
|
+
};
|
|
16
69
|
return async (interaction) => {
|
|
70
|
+
if (interaction.isAutocomplete()) {
|
|
71
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
72
|
+
await interaction.respond([]).catch(logger_1.logger.error);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (interaction.commandName === 'project') {
|
|
77
|
+
const subcommand = interaction.options.getSubcommand(false);
|
|
78
|
+
const focused = interaction.options.getFocused(true);
|
|
79
|
+
if (subcommand === 'account' && focused.name === 'name') {
|
|
80
|
+
const names = (0, accountUtils_1.listAccountNames)(deps.antigravityAccounts);
|
|
81
|
+
const currentAccount = resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction));
|
|
82
|
+
const needle = String(focused.value || '').trim().toLowerCase();
|
|
83
|
+
const choices = names
|
|
84
|
+
.filter((name) => !needle || name.toLowerCase().includes(needle))
|
|
85
|
+
.slice(0, 25)
|
|
86
|
+
.map((name) => ({
|
|
87
|
+
name: name === currentAccount ? `${name} (current)` : name,
|
|
88
|
+
value: name,
|
|
89
|
+
}));
|
|
90
|
+
await interaction.respond(choices);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
logger_1.logger.error('Autocomplete handling error:', error);
|
|
97
|
+
}
|
|
98
|
+
await interaction.respond([]).catch(logger_1.logger.error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
17
101
|
if (interaction.isButton()) {
|
|
18
102
|
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
19
103
|
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
@@ -31,7 +115,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
31
115
|
}
|
|
32
116
|
const projectName = approvalAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
33
117
|
const detector = projectName
|
|
34
|
-
? deps.bridge.pool.getApprovalDetector(projectName)
|
|
118
|
+
? deps.bridge.pool.getApprovalDetector(projectName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
35
119
|
: undefined;
|
|
36
120
|
if (!detector) {
|
|
37
121
|
try {
|
|
@@ -101,7 +185,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
101
185
|
}
|
|
102
186
|
const planWorkspaceDirName = planningAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
103
187
|
const planDetector = planWorkspaceDirName
|
|
104
|
-
? deps.bridge.pool.getPlanningDetector(planWorkspaceDirName)
|
|
188
|
+
? deps.bridge.pool.getPlanningDetector(planWorkspaceDirName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
105
189
|
: undefined;
|
|
106
190
|
if (!planDetector) {
|
|
107
191
|
try {
|
|
@@ -227,7 +311,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
227
311
|
}
|
|
228
312
|
const errorWorkspaceDirName = errorPopupAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
229
313
|
const errorDetector = errorWorkspaceDirName
|
|
230
|
-
? deps.bridge.pool.getErrorPopupDetector(errorWorkspaceDirName)
|
|
314
|
+
? deps.bridge.pool.getErrorPopupDetector(errorWorkspaceDirName, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
231
315
|
: undefined;
|
|
232
316
|
if (!errorDetector) {
|
|
233
317
|
try {
|
|
@@ -377,7 +461,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
377
461
|
}
|
|
378
462
|
const runCmdWorkspace = runCommandAction.projectName ?? deps.bridge.lastActiveWorkspace;
|
|
379
463
|
const runCmdDetector = runCmdWorkspace
|
|
380
|
-
? deps.bridge.pool.getRunCommandDetector(runCmdWorkspace)
|
|
464
|
+
? deps.bridge.pool.getRunCommandDetector(runCmdWorkspace, resolveSelectedAccount(interaction.channelId, interaction.user.id, getParentChannelId(interaction)))
|
|
381
465
|
: undefined;
|
|
382
466
|
if (!runCmdDetector) {
|
|
383
467
|
try {
|
|
@@ -446,11 +530,16 @@ function createInteractionCreateHandler(deps) {
|
|
|
446
530
|
}
|
|
447
531
|
if (interaction.customId === 'model_set_default_btn') {
|
|
448
532
|
await interaction.deferUpdate();
|
|
449
|
-
const cdp =
|
|
533
|
+
const cdp = getChannelCdp(interaction.channelId, interaction.user.id);
|
|
450
534
|
if (!cdp) {
|
|
451
535
|
await interaction.followUp({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
452
536
|
return;
|
|
453
537
|
}
|
|
538
|
+
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
539
|
+
if (!sessionReady.ok) {
|
|
540
|
+
await interaction.followUp({ content: sessionReady.error, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
454
543
|
const currentModel = await cdp.getCurrentModel();
|
|
455
544
|
if (!currentModel) {
|
|
456
545
|
await interaction.followUp({ content: 'No current model detected.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -461,7 +550,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
461
550
|
deps.userPrefRepo.setDefaultModel(interaction.user.id, currentModel);
|
|
462
551
|
}
|
|
463
552
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
464
|
-
getCurrentCdp: () =>
|
|
553
|
+
getCurrentCdp: () => getChannelCdp(interaction.channelId, interaction.user.id),
|
|
465
554
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
466
555
|
});
|
|
467
556
|
await interaction.followUp({ content: `Default model set to **${currentModel}**.`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -469,12 +558,20 @@ function createInteractionCreateHandler(deps) {
|
|
|
469
558
|
}
|
|
470
559
|
if (interaction.customId === 'model_clear_default_btn') {
|
|
471
560
|
await interaction.deferUpdate();
|
|
561
|
+
const cdp = getChannelCdp(interaction.channelId, interaction.user.id);
|
|
562
|
+
if (cdp) {
|
|
563
|
+
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
564
|
+
if (!sessionReady.ok) {
|
|
565
|
+
await interaction.followUp({ content: sessionReady.error, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
472
569
|
deps.modelService.setDefaultModel(null);
|
|
473
570
|
if (deps.userPrefRepo) {
|
|
474
571
|
deps.userPrefRepo.setDefaultModel(interaction.user.id, null);
|
|
475
572
|
}
|
|
476
573
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
477
|
-
getCurrentCdp: () =>
|
|
574
|
+
getCurrentCdp: () => getChannelCdp(interaction.channelId, interaction.user.id),
|
|
478
575
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
479
576
|
});
|
|
480
577
|
await interaction.followUp({ content: 'Default model cleared.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -482,8 +579,16 @@ function createInteractionCreateHandler(deps) {
|
|
|
482
579
|
}
|
|
483
580
|
if (interaction.customId === 'model_refresh_btn') {
|
|
484
581
|
await interaction.deferUpdate();
|
|
582
|
+
const cdp = getChannelCdp(interaction.channelId, interaction.user.id);
|
|
583
|
+
if (cdp) {
|
|
584
|
+
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
585
|
+
if (!sessionReady.ok) {
|
|
586
|
+
await interaction.followUp({ content: sessionReady.error, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
485
590
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
486
|
-
getCurrentCdp: () =>
|
|
591
|
+
getCurrentCdp: () => getChannelCdp(interaction.channelId, interaction.user.id),
|
|
487
592
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
488
593
|
});
|
|
489
594
|
return;
|
|
@@ -491,18 +596,23 @@ function createInteractionCreateHandler(deps) {
|
|
|
491
596
|
if (interaction.customId.startsWith('model_btn_')) {
|
|
492
597
|
await interaction.deferUpdate();
|
|
493
598
|
const modelName = interaction.customId.replace('model_btn_', '');
|
|
494
|
-
const cdp =
|
|
599
|
+
const cdp = getChannelCdp(interaction.channelId, interaction.user.id);
|
|
495
600
|
if (!cdp) {
|
|
496
601
|
await interaction.followUp({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
497
602
|
return;
|
|
498
603
|
}
|
|
604
|
+
const sessionReady = await ensureBoundSessionActive(interaction.channelId, interaction.user.id, cdp);
|
|
605
|
+
if (!sessionReady.ok) {
|
|
606
|
+
await interaction.followUp({ content: sessionReady.error, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
499
609
|
const res = await cdp.setUiModel(modelName);
|
|
500
610
|
if (!res.ok) {
|
|
501
611
|
await interaction.followUp({ content: res.error || 'Failed to change model.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
502
612
|
}
|
|
503
613
|
else {
|
|
504
614
|
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
505
|
-
getCurrentCdp: () =>
|
|
615
|
+
getCurrentCdp: () => getChannelCdp(interaction.channelId, interaction.user.id),
|
|
506
616
|
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
507
617
|
});
|
|
508
618
|
await interaction.followUp({ content: `Model changed to **${res.model}**!`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
@@ -589,7 +699,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
589
699
|
try {
|
|
590
700
|
const selectedMode = interaction.values[0];
|
|
591
701
|
deps.modeService.setMode(selectedMode);
|
|
592
|
-
const cdp =
|
|
702
|
+
const cdp = getChannelCdp(interaction.channelId, interaction.user.id);
|
|
593
703
|
if (cdp) {
|
|
594
704
|
const res = await cdp.setUiMode(selectedMode);
|
|
595
705
|
if (!res.ok) {
|
|
@@ -612,6 +722,75 @@ function createInteractionCreateHandler(deps) {
|
|
|
612
722
|
}
|
|
613
723
|
return;
|
|
614
724
|
}
|
|
725
|
+
if (interaction.isStringSelectMenu() && interaction.customId === accountUi_1.ACCOUNT_SELECT_ID) {
|
|
726
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
727
|
+
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
await interaction.deferUpdate();
|
|
732
|
+
}
|
|
733
|
+
catch (deferError) {
|
|
734
|
+
if (deferError?.code === 10062 || deferError?.code === 40060) {
|
|
735
|
+
logger_1.logger.warn('[Account] deferUpdate expired. Skipping.');
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
logger_1.logger.error('[Account] deferUpdate failed:', deferError);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
if (!deps.accountPrefRepo) {
|
|
743
|
+
await interaction.followUp({
|
|
744
|
+
content: 'Account preference service not available.',
|
|
745
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
746
|
+
}).catch(logger_1.logger.error);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const selectedAccount = interaction.values[0];
|
|
750
|
+
const names = (0, accountUtils_1.listAccountNames)(deps.antigravityAccounts);
|
|
751
|
+
if (!selectedAccount || !names.includes(selectedAccount)) {
|
|
752
|
+
await interaction.followUp({
|
|
753
|
+
content: `⚠️ Unknown account: **${selectedAccount || 'N/A'}**`,
|
|
754
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
755
|
+
}).catch(logger_1.logger.error);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
deps.bridge.selectedAccountByChannel?.set(interaction.channelId, selectedAccount);
|
|
759
|
+
const currentSession = deps.chatSessionRepo?.findByChannelId(interaction.channelId);
|
|
760
|
+
if (currentSession) {
|
|
761
|
+
deps.chatSessionRepo?.setActiveAccountName(interaction.channelId, selectedAccount);
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
deps.accountPrefRepo.setAccountName(interaction.user.id, selectedAccount);
|
|
765
|
+
deps.channelPrefRepo?.setAccountName(interaction.channelId, selectedAccount);
|
|
766
|
+
}
|
|
767
|
+
const channelWorkspace = deps.wsHandler.getWorkspaceForChannel(interaction.channelId);
|
|
768
|
+
const selectedPort = deps.antigravityAccounts?.find((a) => a.name === selectedAccount)?.cdpPort;
|
|
769
|
+
logger_1.logger.info(`[AccountSwitch] source=select channel=${interaction.channelId} user=${interaction.user.id} ` +
|
|
770
|
+
`account=${selectedAccount} port=${selectedPort ?? 'unknown'} ` +
|
|
771
|
+
`workspace=${channelWorkspace ?? 'unbound'}`);
|
|
772
|
+
await (0, accountUi_1.sendAccountUI)({ editReply: async (data) => await interaction.editReply(data) }, selectedAccount, names);
|
|
773
|
+
await interaction.followUp({
|
|
774
|
+
content: `✅ Switched session account to **${selectedAccount}**.`,
|
|
775
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
776
|
+
}).catch(logger_1.logger.error);
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
logger_1.logger.error('Error during account dropdown handling:', error);
|
|
780
|
+
try {
|
|
781
|
+
if (interaction.deferred || interaction.replied) {
|
|
782
|
+
await interaction.followUp({
|
|
783
|
+
content: 'An error occurred while switching account.',
|
|
784
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
785
|
+
}).catch(logger_1.logger.error);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
catch (e) {
|
|
789
|
+
logger_1.logger.error('Failed to send error message:', e);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
615
794
|
if (interaction.isStringSelectMenu() && (0, sessionPickerUi_1.isSessionSelectId)(interaction.customId)) {
|
|
616
795
|
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
617
796
|
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
@@ -648,6 +827,7 @@ function createInteractionCreateHandler(deps) {
|
|
|
648
827
|
return;
|
|
649
828
|
}
|
|
650
829
|
try {
|
|
830
|
+
await interaction.deferUpdate();
|
|
651
831
|
await deps.wsHandler.handleSelectMenu(interaction, interaction.guild);
|
|
652
832
|
}
|
|
653
833
|
catch (error) {
|
|
@@ -681,10 +861,11 @@ function createInteractionCreateHandler(deps) {
|
|
|
681
861
|
throw deferError;
|
|
682
862
|
}
|
|
683
863
|
try {
|
|
684
|
-
await deps.handleSlashInteraction(commandInteraction, deps.slashCommandHandler, deps.bridge, deps.wsHandler, deps.chatHandler, deps.cleanupHandler, deps.modeService, deps.modelService, deps.bridge.autoAccept, deps.client);
|
|
864
|
+
await deps.handleSlashInteraction(commandInteraction, deps.slashCommandHandler, deps.bridge, deps.wsHandler, deps.chatHandler, deps.cleanupHandler, deps.modeService, deps.modelService, deps.bridge.autoAccept, deps.client, deps.accountPrefRepo, deps.channelPrefRepo, deps.antigravityAccounts, deps.chatSessionRepo);
|
|
685
865
|
}
|
|
686
866
|
catch (error) {
|
|
687
|
-
logger_1.logger.error(
|
|
867
|
+
logger_1.logger.error(`[SlashCommand] command=${commandInteraction.commandName} channel=${commandInteraction.channelId} ` +
|
|
868
|
+
`user=${commandInteraction.user.id} failed:`, error);
|
|
688
869
|
try {
|
|
689
870
|
await commandInteraction.editReply({ content: 'An error occurred while processing the command.' });
|
|
690
871
|
}
|
|
@@ -8,6 +8,7 @@ const wrappers_1 = require("../platform/discord/wrappers");
|
|
|
8
8
|
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
9
9
|
const modeService_1 = require("../services/modeService");
|
|
10
10
|
const imageHandler_1 = require("../utils/imageHandler");
|
|
11
|
+
const accountUtils_1 = require("../utils/accountUtils");
|
|
11
12
|
const logger_1 = require("../utils/logger");
|
|
12
13
|
function createMessageCreateHandler(deps) {
|
|
13
14
|
const getCurrentCdp = deps.getCurrentCdp ?? cdpBridgeManager_1.getCurrentCdp;
|
|
@@ -20,6 +21,15 @@ function createMessageCreateHandler(deps) {
|
|
|
20
21
|
const downloadInboundImageAttachments = deps.downloadInboundImageAttachments ?? imageHandler_1.downloadInboundImageAttachments;
|
|
21
22
|
const cleanupInboundImageAttachments = deps.cleanupInboundImageAttachments ?? imageHandler_1.cleanupInboundImageAttachments;
|
|
22
23
|
const isImageAttachment = deps.isImageAttachment ?? imageHandler_1.isImageAttachment;
|
|
24
|
+
const getParentChannelId = (message) => {
|
|
25
|
+
const parentId = message.channel?.parentId;
|
|
26
|
+
return typeof parentId === 'string' && parentId.length > 0 ? parentId : null;
|
|
27
|
+
};
|
|
28
|
+
const getAccountPort = (accountName) => {
|
|
29
|
+
const match = (deps.antigravityAccounts ?? []).find((account) => account.name === accountName);
|
|
30
|
+
return match ? match.cdpPort : null;
|
|
31
|
+
};
|
|
32
|
+
const getSessionAccountName = (channelId) => deps.chatSessionRepo.findByChannelId(channelId)?.activeAccountName ?? null;
|
|
23
33
|
// Per-workspace prompt queue: serializes send→response cycles
|
|
24
34
|
const workspaceQueues = new Map();
|
|
25
35
|
const workspaceQueueDepths = new Map();
|
|
@@ -58,10 +68,25 @@ function createMessageCreateHandler(deps) {
|
|
|
58
68
|
if (parsed.commandName === 'status') {
|
|
59
69
|
const activeNames = deps.bridge.pool.getActiveWorkspaceNames();
|
|
60
70
|
const currentMode = deps.modeService.getCurrentMode();
|
|
71
|
+
const session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
72
|
+
const currentAccount = (0, accountUtils_1.resolveScopedAccountName)({
|
|
73
|
+
channelId: message.channelId,
|
|
74
|
+
userId: message.author.id,
|
|
75
|
+
sessionAccountName: getSessionAccountName(message.channelId),
|
|
76
|
+
parentChannelId: getParentChannelId(message),
|
|
77
|
+
selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
|
|
78
|
+
channelPrefRepo: deps.channelPrefRepo,
|
|
79
|
+
accountPrefRepo: deps.accountPrefRepo,
|
|
80
|
+
accounts: deps.antigravityAccounts,
|
|
81
|
+
});
|
|
82
|
+
const conversationTitle = session?.displayName ?? '(New chat / no saved title)';
|
|
61
83
|
const statusFields = [
|
|
62
84
|
{ name: 'CDP Connection', value: activeNames.length > 0 ? `🟢 ${activeNames.length} project(s) connected` : '⚪ Disconnected', inline: true },
|
|
63
85
|
{ name: 'Mode', value: modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode, inline: true },
|
|
64
86
|
{ name: 'Auto Approve', value: deps.bridge.autoAccept.isEnabled() ? '🟢 ON' : '⚪ OFF', inline: true },
|
|
87
|
+
{ name: 'Active Account', value: currentAccount, inline: true },
|
|
88
|
+
{ name: 'Original Account', value: session?.originAccountName ?? '(unset)', inline: true },
|
|
89
|
+
{ name: 'Conversation Title', value: conversationTitle, inline: false },
|
|
65
90
|
];
|
|
66
91
|
let statusDescription = '';
|
|
67
92
|
if (activeNames.length > 0) {
|
|
@@ -97,6 +122,43 @@ function createMessageCreateHandler(deps) {
|
|
|
97
122
|
await message.reply({ embeds: [embed] });
|
|
98
123
|
return;
|
|
99
124
|
}
|
|
125
|
+
if (parsed.commandName === 'account') {
|
|
126
|
+
const accountNames = (0, accountUtils_1.listAccountNames)(deps.antigravityAccounts);
|
|
127
|
+
const requested = parsed.args?.[0];
|
|
128
|
+
if (!requested) {
|
|
129
|
+
const current = (0, accountUtils_1.resolveScopedAccountName)({
|
|
130
|
+
channelId: message.channelId,
|
|
131
|
+
userId: message.author.id,
|
|
132
|
+
sessionAccountName: getSessionAccountName(message.channelId),
|
|
133
|
+
parentChannelId: getParentChannelId(message),
|
|
134
|
+
selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
|
|
135
|
+
channelPrefRepo: deps.channelPrefRepo,
|
|
136
|
+
accountPrefRepo: deps.accountPrefRepo,
|
|
137
|
+
accounts: deps.antigravityAccounts,
|
|
138
|
+
});
|
|
139
|
+
await message.reply(`Current account: **${current}**\nAvailable: ${accountNames.join(', ')}`).catch(() => { });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!accountNames.includes(requested)) {
|
|
143
|
+
await message.reply(`⚠️ Unknown account: **${requested}**`).catch(() => { });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
deps.bridge.selectedAccountByChannel?.set(message.channelId, requested);
|
|
147
|
+
const currentSession = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
148
|
+
if (currentSession) {
|
|
149
|
+
deps.chatSessionRepo.setActiveAccountName(message.channelId, requested);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
deps.accountPrefRepo?.setAccountName(message.author.id, requested);
|
|
153
|
+
deps.channelPrefRepo?.setAccountName(message.channelId, requested);
|
|
154
|
+
}
|
|
155
|
+
const channelWorkspace = deps.wsHandler.getWorkspaceForChannel(message.channelId);
|
|
156
|
+
logger_1.logger.info(`[AccountSwitch] source=text channel=${message.channelId} user=${message.author.id} ` +
|
|
157
|
+
`account=${requested} port=${getAccountPort(requested) ?? 'unknown'} ` +
|
|
158
|
+
`workspace=${channelWorkspace ?? 'unbound'}`);
|
|
159
|
+
await message.reply(`✅ Switched session account to **${requested}**.`).catch(() => { });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
100
162
|
const slashOnlyCommands = ['help', 'stop', 'model', 'mode', 'project', 'chat', 'new', 'cleanup', 'join', 'mirror', 'output'];
|
|
101
163
|
if (slashOnlyCommands.includes(parsed.commandName)) {
|
|
102
164
|
await message.reply({
|
|
@@ -163,17 +225,48 @@ function createMessageCreateHandler(deps) {
|
|
|
163
225
|
await message.reactions.resolve('⏳')?.users.remove(botId).catch(() => { });
|
|
164
226
|
}
|
|
165
227
|
try {
|
|
166
|
-
const
|
|
228
|
+
const selectedAccount = (0, accountUtils_1.resolveScopedAccountName)({
|
|
229
|
+
channelId: message.channelId,
|
|
230
|
+
userId: message.author.id,
|
|
231
|
+
sessionAccountName: getSessionAccountName(message.channelId),
|
|
232
|
+
parentChannelId: getParentChannelId(message),
|
|
233
|
+
selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
|
|
234
|
+
channelPrefRepo: deps.channelPrefRepo,
|
|
235
|
+
accountPrefRepo: deps.accountPrefRepo,
|
|
236
|
+
accounts: deps.antigravityAccounts,
|
|
237
|
+
});
|
|
238
|
+
const selectedPort = getAccountPort(selectedAccount);
|
|
239
|
+
deps.bridge.selectedAccountByChannel?.set(message.channelId, selectedAccount);
|
|
240
|
+
logger_1.logger.info(`[Route] channel=${message.channelId} user=${message.author.id} ` +
|
|
241
|
+
`project=${projectLabel} account=${selectedAccount} ` +
|
|
242
|
+
`port=${selectedPort ?? 'unknown'} workspacePath=${workspacePath}`);
|
|
243
|
+
const previousPreferredAccount = deps.bridge.pool.getPreferredAccountForWorkspace?.(workspacePath) ?? null;
|
|
244
|
+
const cdp = await deps.bridge.pool.getOrConnect(workspacePath, { name: selectedAccount });
|
|
167
245
|
const projectName = deps.bridge.pool.extractProjectName(workspacePath);
|
|
246
|
+
deps.bridge.pool.setPreferredAccountForWorkspace?.(workspacePath, selectedAccount);
|
|
168
247
|
deps.bridge.lastActiveWorkspace = projectName;
|
|
169
248
|
const platformChannel = (0, wrappers_1.wrapDiscordChannel)(message.channel);
|
|
170
249
|
deps.bridge.lastActiveChannel = platformChannel;
|
|
171
250
|
registerApprovalWorkspaceChannel(deps.bridge, projectName, platformChannel);
|
|
172
|
-
ensureApprovalDetector(deps.bridge, cdp, projectName);
|
|
173
|
-
ensureErrorPopupDetector(deps.bridge, cdp, projectName);
|
|
174
|
-
ensurePlanningDetector(deps.bridge, cdp, projectName);
|
|
175
|
-
ensureRunCommandDetector(deps.bridge, cdp, projectName);
|
|
176
|
-
|
|
251
|
+
ensureApprovalDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
252
|
+
ensureErrorPopupDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
253
|
+
ensurePlanningDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
254
|
+
ensureRunCommandDetector(deps.bridge, cdp, projectName, selectedAccount);
|
|
255
|
+
let session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
256
|
+
const staleSessionAccount = session?.isRenamed
|
|
257
|
+
&& ((session.activeAccountName && session.activeAccountName !== selectedAccount)
|
|
258
|
+
|| (!session.activeAccountName && previousPreferredAccount && previousPreferredAccount !== selectedAccount));
|
|
259
|
+
if (session && staleSessionAccount) {
|
|
260
|
+
logger_1.logger.info(`[SessionAccountReset] channel=${message.channelId} ` +
|
|
261
|
+
`project=${projectName} oldAccount=${session.activeAccountName ?? previousPreferredAccount ?? 'unknown'} ` +
|
|
262
|
+
`newAccount=${selectedAccount}`);
|
|
263
|
+
deps.chatSessionRepo.setActiveAccountName?.(message.channelId, selectedAccount);
|
|
264
|
+
session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
265
|
+
}
|
|
266
|
+
if (session) {
|
|
267
|
+
deps.chatSessionRepo.setActiveAccountName?.(message.channelId, selectedAccount);
|
|
268
|
+
deps.chatSessionRepo.initializeOriginAccountName?.(message.channelId, selectedAccount);
|
|
269
|
+
}
|
|
177
270
|
if (session?.displayName) {
|
|
178
271
|
registerApprovalSessionChannel(deps.bridge, projectName, session.displayName, platformChannel);
|
|
179
272
|
}
|
|
@@ -198,6 +291,8 @@ function createMessageCreateHandler(deps) {
|
|
|
198
291
|
`"${session.displayName}" -> "${recoveredTitle}" ` +
|
|
199
292
|
`(channel: ${message.channelId})`);
|
|
200
293
|
deps.chatSessionRepo.updateDisplayName(message.channelId, recoveredTitle);
|
|
294
|
+
deps.chatSessionRepo.setActiveAccountName?.(message.channelId, selectedAccount);
|
|
295
|
+
deps.chatSessionRepo.initializeOriginAccountName?.(message.channelId, selectedAccount);
|
|
201
296
|
registerApprovalSessionChannel(deps.bridge, projectName, recoveredTitle, platformChannel);
|
|
202
297
|
}
|
|
203
298
|
activationResult = retryResult;
|
|
@@ -290,7 +385,8 @@ function createMessageCreateHandler(deps) {
|
|
|
290
385
|
});
|
|
291
386
|
}
|
|
292
387
|
else {
|
|
293
|
-
await message.reply('No project is configured for this channel.
|
|
388
|
+
await message.reply('No project is configured for this channel. Use `/project` to bind one, ' +
|
|
389
|
+
'or `/project reopen` if this is a previously used session.');
|
|
294
390
|
}
|
|
295
391
|
}
|
|
296
392
|
finally {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAccountSelectAction = createAccountSelectAction;
|
|
4
|
+
const accountUtils_1 = require("../utils/accountUtils");
|
|
5
|
+
const accountUi_1 = require("../ui/accountUi");
|
|
6
|
+
const logger_1 = require("../utils/logger");
|
|
7
|
+
function createAccountSelectAction(deps) {
|
|
8
|
+
return {
|
|
9
|
+
match(customId) {
|
|
10
|
+
return customId === accountUi_1.ACCOUNT_SELECT_ID;
|
|
11
|
+
},
|
|
12
|
+
async execute(interaction, values) {
|
|
13
|
+
const selectedAccount = values[0];
|
|
14
|
+
if (!selectedAccount)
|
|
15
|
+
return;
|
|
16
|
+
const names = (0, accountUtils_1.listAccountNames)(deps.antigravityAccounts);
|
|
17
|
+
if (!names.includes(selectedAccount)) {
|
|
18
|
+
await interaction.followUp({
|
|
19
|
+
text: `⚠️ Unknown account: **${selectedAccount}**`,
|
|
20
|
+
}).catch(() => { });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await interaction.deferUpdate();
|
|
24
|
+
deps.bridge.selectedAccountByChannel?.set(interaction.channel.id, selectedAccount);
|
|
25
|
+
const currentSession = deps.chatSessionRepo?.findByChannelId(interaction.channel.id);
|
|
26
|
+
if (currentSession) {
|
|
27
|
+
deps.chatSessionRepo?.setActiveAccountName(interaction.channel.id, selectedAccount);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
deps.accountPrefRepo.setAccountName(interaction.user.id, selectedAccount);
|
|
31
|
+
deps.channelPrefRepo?.setAccountName(interaction.channel.id, selectedAccount);
|
|
32
|
+
}
|
|
33
|
+
const channelWorkspace = deps.getWorkspacePathForChannel?.(interaction.channel.id) ?? null;
|
|
34
|
+
const selectedPort = deps.antigravityAccounts.find((a) => a.name === selectedAccount)?.cdpPort;
|
|
35
|
+
logger_1.logger.info(`[AccountSwitch] source=select channel=${interaction.channel.id} user=${interaction.user.id} ` +
|
|
36
|
+
`account=${selectedAccount} port=${selectedPort ?? 'unknown'} ` +
|
|
37
|
+
`workspace=${channelWorkspace ?? 'unbound'}`);
|
|
38
|
+
const payload = (0, accountUi_1.buildAccountPayload)(selectedAccount, names);
|
|
39
|
+
await interaction.update(payload);
|
|
40
|
+
await interaction.followUp({
|
|
41
|
+
text: `✅ Switched session account to **${selectedAccount}**.`,
|
|
42
|
+
}).catch(() => { });
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -32,9 +32,17 @@ function createModelButtonAction(deps) {
|
|
|
32
32
|
await interaction.deferUpdate();
|
|
33
33
|
const cdp = (0, cdpBridgeManager_1.getCurrentCdp)(deps.bridge);
|
|
34
34
|
if (!cdp) {
|
|
35
|
+
logger_1.logger.warn(`[ModelCommand] source=button user=${interaction.user.id} action=${params.action} cdp=unavailable`);
|
|
35
36
|
await interaction.followUp({ text: 'Not connected to CDP.' }).catch(() => { });
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
39
|
+
if (deps.ensureSessionActivated) {
|
|
40
|
+
const sessionReady = await deps.ensureSessionActivated(interaction.channel.id, interaction.user.id, cdp);
|
|
41
|
+
if (!sessionReady.ok) {
|
|
42
|
+
await interaction.followUp({ text: sessionReady.error }).catch(() => { });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
38
46
|
if (params.action === 'set_default') {
|
|
39
47
|
const currentModel = await cdp.getCurrentModel();
|
|
40
48
|
if (!currentModel) {
|
|
@@ -65,7 +73,12 @@ function createModelButtonAction(deps) {
|
|
|
65
73
|
}).catch(() => { });
|
|
66
74
|
}
|
|
67
75
|
else if (params.action === 'select') {
|
|
76
|
+
logger_1.logger.info(`[ModelCommand] source=button user=${interaction.user.id} target="${params.modelName}"`);
|
|
68
77
|
const res = await cdp.setUiModel(params.modelName);
|
|
78
|
+
logger_1.logger.info(`[ModelCommand] source=button user=${interaction.user.id} target="${params.modelName}" ` +
|
|
79
|
+
`ok=${res.ok} applied=${res.model ? `"${res.model}"` : 'null'} ` +
|
|
80
|
+
`verified=${res.verified === true} alreadySelected=${res.alreadySelected === true} ` +
|
|
81
|
+
`error=${res.error ? `"${res.error}"` : 'null'}`);
|
|
69
82
|
if (!res.ok) {
|
|
70
83
|
await interaction.followUp({
|
|
71
84
|
text: res.error || 'Failed to change model.',
|