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.
@@ -1,22 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Telegram command parser and handlers.
4
- *
5
- * Handles built-in bot commands that can be answered immediately
6
- * without routing through CDP/Antigravity:
7
- * /start — Welcome message
8
- * /help — List available commands
9
- * /status — Show bot connection status
10
- * /stop — Interrupt active LLM generation
11
- * /ping — Latency check
12
- * /mode — Switch execution mode
13
- * /model — Switch LLM model
14
- * /screenshot — Capture Antigravity screenshot
15
- * /autoaccept — Toggle auto-accept for approval dialogs
16
- * /template — List and execute prompt templates
17
- * /logs — Show recent log entries
18
- * /new — Start a new chat session
19
- */
20
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
3
  if (k2 === undefined) k2 = k;
22
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -56,6 +38,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
56
38
  Object.defineProperty(exports, "__esModule", { value: true });
57
39
  exports.parseTelegramCommand = parseTelegramCommand;
58
40
  exports.handleTelegramCommand = handleTelegramCommand;
41
+ const telegramJoinCommand_1 = require("./telegramJoinCommand");
42
+ /**
43
+ * Telegram command parser and handlers.
44
+ *
45
+ * Handles built-in bot commands that can be answered immediately
46
+ * without routing through CDP/Antigravity:
47
+ * /start — Welcome message
48
+ * /help — List available commands
49
+ * /status — Show bot connection status
50
+ * /stop — Interrupt active LLM generation
51
+ * /ping — Latency check
52
+ * /mode — Switch execution mode
53
+ * /model — Switch LLM model
54
+ * /screenshot — Capture Antigravity screenshot
55
+ * /autoaccept — Toggle auto-accept for approval dialogs
56
+ * /template — List and execute prompt templates
57
+ * /logs — Show recent log entries
58
+ * /new — Start a new chat session
59
+ */
59
60
  const fs_1 = __importDefault(require("fs"));
60
61
  const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
61
62
  const modeUi_1 = require("../ui/modeUi");
@@ -63,13 +64,16 @@ const modelsUi_1 = require("../ui/modelsUi");
63
64
  const autoAcceptUi_1 = require("../ui/autoAcceptUi");
64
65
  const templateUi_1 = require("../ui/templateUi");
65
66
  const screenshotUi_1 = require("../ui/screenshotUi");
67
+ const accountUi_1 = require("../ui/accountUi");
66
68
  const logBuffer_1 = require("../utils/logBuffer");
67
69
  const telegramFormatter_1 = require("../platform/telegram/telegramFormatter");
68
70
  const logger_1 = require("../utils/logger");
71
+ const telegramProjectCommand_1 = require("./telegramProjectCommand");
72
+ const accountUtils_1 = require("../utils/accountUtils");
69
73
  // ---------------------------------------------------------------------------
70
74
  // Known commands (used by both parser and /help output)
71
75
  // ---------------------------------------------------------------------------
72
- const KNOWN_COMMANDS = ['start', 'help', 'status', 'stop', 'ping', 'mode', 'model', 'screenshot', 'autoaccept', 'template', 'template_add', 'template_delete', 'project_create', 'logs', 'new'];
76
+ const KNOWN_COMMANDS = ['start', 'help', 'status', 'stop', 'ping', 'mode', 'model', 'screenshot', 'autoaccept', 'account', 'project_reopen', 'template', 'template_add', 'template_delete', 'project_create', 'logs', 'new', 'join', 'mirror'];
73
77
  /**
74
78
  * Parse a Telegram command from message text.
75
79
  *
@@ -133,6 +137,12 @@ async function handleTelegramCommand(deps, message, parsed) {
133
137
  case 'autoaccept':
134
138
  await handleAutoAccept(deps, message, parsed.args);
135
139
  break;
140
+ case 'account':
141
+ await handleAccount(deps, message, parsed.args);
142
+ break;
143
+ case 'project_reopen':
144
+ await handleProjectReopen(deps, message);
145
+ break;
136
146
  case 'template':
137
147
  await handleTemplate(deps, message);
138
148
  break;
@@ -151,6 +161,12 @@ async function handleTelegramCommand(deps, message, parsed) {
151
161
  case 'new':
152
162
  await handleNew(deps, message);
153
163
  break;
164
+ case 'join':
165
+ await (0, telegramJoinCommand_1.handleJoin)(deps, message);
166
+ break;
167
+ case 'mirror':
168
+ await (0, telegramJoinCommand_1.handleMirror)(deps, message);
169
+ break;
154
170
  default:
155
171
  // Should not happen — parser filters unknowns
156
172
  break;
@@ -183,11 +199,15 @@ async function handleHelp(message) {
183
199
  '/model — Switch LLM model',
184
200
  '/screenshot — Capture Antigravity screenshot',
185
201
  '/autoaccept — Toggle auto-accept mode',
202
+ '/account — Show and switch Antigravity account',
203
+ '/project_reopen — Reopen the bound project in the selected Antigravity account',
186
204
  '/template — List prompt templates',
187
205
  '/template_add — Add a prompt template',
188
206
  '/template_delete — Delete a prompt template',
189
207
  '/project_create — Create a new workspace',
190
208
  '/new — Start a new chat session',
209
+ '/join — Take over an existing Antigravity session',
210
+ '/mirror — Toggle PC-to-Telegram message mirroring',
191
211
  '/logs — Show recent log entries',
192
212
  '/stop — Interrupt active LLM generation',
193
213
  '/ping — Check bot latency',
@@ -316,6 +336,52 @@ async function handleAutoAccept(deps, message, args) {
316
336
  const payload = (0, autoAcceptUi_1.buildAutoAcceptPayload)(deps.bridge.autoAccept.isEnabled());
317
337
  await message.reply(payload).catch(logger_1.logger.error);
318
338
  }
339
+ async function handleAccount(deps, message, args) {
340
+ if (!deps.accountPrefRepo) {
341
+ await message.reply({ text: 'Account preference service not available.' }).catch(logger_1.logger.error);
342
+ return;
343
+ }
344
+ const names = (0, accountUtils_1.listAccountNames)(deps.antigravityAccounts);
345
+ const chatId = message.channel.id;
346
+ const userId = message.author.id;
347
+ const applySelection = (selectedAccount) => {
348
+ deps.accountPrefRepo?.setAccountName(userId, selectedAccount);
349
+ deps.channelPrefRepo?.setAccountName(chatId, selectedAccount);
350
+ deps.bridge.selectedAccountByChannel?.set(chatId, selectedAccount);
351
+ const channelBinding = deps.telegramBindingRepo?.findByChatId(chatId);
352
+ const workspacePath = channelBinding
353
+ ? (deps.workspaceService
354
+ ? deps.workspaceService.getWorkspacePath(channelBinding.workspacePath)
355
+ : channelBinding.workspacePath)
356
+ : null;
357
+ const selectedPort = deps.antigravityAccounts?.find((a) => a.name === selectedAccount)?.cdpPort;
358
+ logger_1.logger.info(`[AccountSwitch] source=telegram_command channel=${chatId} user=${userId} ` +
359
+ `account=${selectedAccount} port=${selectedPort ?? 'unknown'} ` +
360
+ `workspace=${workspacePath ?? 'unbound'}`);
361
+ };
362
+ const requested = args.trim();
363
+ if (requested) {
364
+ if (!names.includes(requested)) {
365
+ await message.reply({
366
+ text: `⚠️ Unknown account: <b>${(0, telegramFormatter_1.escapeHtml)(requested)}</b>\nAvailable: ${names.map(telegramFormatter_1.escapeHtml).join(', ')}`,
367
+ }).catch(logger_1.logger.error);
368
+ return;
369
+ }
370
+ applySelection(requested);
371
+ await message.reply({ text: `✅ Switched account to <b>${(0, telegramFormatter_1.escapeHtml)(requested)}</b>.` }).catch(logger_1.logger.error);
372
+ return;
373
+ }
374
+ const current = (0, accountUtils_1.resolveScopedAccountName)({
375
+ channelId: chatId,
376
+ userId,
377
+ selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
378
+ channelPrefRepo: deps.channelPrefRepo,
379
+ accountPrefRepo: deps.accountPrefRepo,
380
+ accounts: deps.antigravityAccounts,
381
+ });
382
+ const payload = (0, accountUi_1.buildAccountPayload)(current, names);
383
+ await message.reply(payload).catch(logger_1.logger.error);
384
+ }
319
385
  async function handleTemplate(deps, message) {
320
386
  if (!deps.templateRepo) {
321
387
  await message.reply({ text: 'Template service not available.' }).catch(logger_1.logger.error);
@@ -415,48 +481,50 @@ async function handleLogs(message, args) {
415
481
  await message.reply({ text: truncated }).catch(logger_1.logger.error);
416
482
  }
417
483
  async function handleNew(deps, message) {
418
- if (!deps.chatSessionService) {
419
- await message.reply({ text: 'Chat session service not available.' }).catch(logger_1.logger.error);
420
- return;
421
- }
422
- // Resolve workspace binding for this chat
423
- const chatId = message.channel.id;
424
- const binding = deps.telegramBindingRepo?.findByChatId(chatId);
484
+ const originalChannelId = message.channel.id;
485
+ const binding = deps.telegramBindingRepo?.findByChatIdWithParentFallback(originalChannelId);
425
486
  if (!binding) {
426
- await message.reply({
427
- text: 'No project is linked to this chat. Use /project to bind a workspace first.',
428
- }).catch(logger_1.logger.error);
429
- return;
430
- }
431
- // Resolve workspace path and connect to CDP
432
- let cdp;
433
- try {
434
- const workspacePath = deps.workspaceService
435
- ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
436
- : binding.workspacePath;
437
- cdp = await deps.bridge.pool.getOrConnect(workspacePath);
438
- }
439
- catch (err) {
440
- logger_1.logger.error('[TelegramCommand:new] CDP connection failed:', err?.message || err);
441
- await message.reply({ text: 'Failed to connect to Antigravity.' }).catch(logger_1.logger.error);
487
+ await message.reply({ text: '⚠️ No project is linked to this chat. Use /project first, or /project_reopen if this is a previously used session.' }).catch(logger_1.logger.error);
442
488
  return;
443
489
  }
444
- // Start a new chat session
490
+ const resolvedWorkspacePath = deps.workspaceService
491
+ ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
492
+ : binding.workspacePath;
493
+ let targetChannelId = originalChannelId;
494
+ if (deps.botApi && deps.bridge && deps.telegramBindingRepo) {
495
+ targetChannelId = await (0, telegramProjectCommand_1.tryCreateTopicAndBind)(deps.botApi, originalChannelId, binding.workspacePath, deps.telegramBindingRepo, deps.bridge.pool);
496
+ }
497
+ if (targetChannelId !== originalChannelId) {
498
+ await message.reply({ text: `✅ Created a new topic for the session.` }).catch(() => { });
499
+ }
500
+ const selectedAccount = (0, accountUtils_1.resolveScopedAccountName)({
501
+ channelId: originalChannelId,
502
+ userId: message.author.id,
503
+ selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
504
+ channelPrefRepo: deps.channelPrefRepo,
505
+ accountPrefRepo: deps.accountPrefRepo,
506
+ accounts: deps.antigravityAccounts,
507
+ });
445
508
  try {
509
+ const cdp = await deps.bridge.pool.getOrConnect(resolvedWorkspacePath, { name: selectedAccount });
510
+ if (!deps.chatSessionService) {
511
+ await message.reply({ text: 'Chat session service not available.' }).catch(logger_1.logger.error);
512
+ return;
513
+ }
446
514
  const result = await deps.chatSessionService.startNewChat(cdp);
447
515
  if (result.ok) {
448
- await message.reply({ text: 'New chat session started.' }).catch(logger_1.logger.error);
516
+ if (targetChannelId === originalChannelId) {
517
+ await message.reply({ text: '✅ New chat session started.' }).catch(logger_1.logger.error);
518
+ }
449
519
  }
450
520
  else {
451
521
  logger_1.logger.warn('[TelegramCommand:new] startNewChat failed:', result.error);
452
- await message.reply({
453
- text: `Failed to start new chat: ${(0, telegramFormatter_1.escapeHtml)(result.error || 'unknown error')}`,
454
- }).catch(logger_1.logger.error);
522
+ await message.reply({ text: `❌ Failed to start new chat: ${result.error}` }).catch(logger_1.logger.error);
455
523
  }
456
524
  }
457
525
  catch (err) {
458
526
  logger_1.logger.error('[TelegramCommand:new] startNewChat threw:', err?.message || err);
459
- await message.reply({ text: 'Failed to start new chat.' }).catch(logger_1.logger.error);
527
+ await message.reply({ text: 'Failed to connect to Antigravity. Is it running?' }).catch(logger_1.logger.error);
460
528
  }
461
529
  }
462
530
  // ---------------------------------------------------------------------------
@@ -476,3 +544,62 @@ async function sendFilePayload(message, payload) {
476
544
  await message.reply({ text: 'Screenshot captured but file sending failed.' }).catch(logger_1.logger.error);
477
545
  }
478
546
  }
547
+ async function handleProjectReopen(deps, message) {
548
+ const chatId = message.channel.id;
549
+ const channelBinding = deps.telegramBindingRepo?.findByChatIdWithParentFallback(chatId);
550
+ if (!channelBinding) {
551
+ await message.reply({ text: '⚠️ No project is bound to this chat. Use /project first, or /project_reopen if this is a previously used session.' }).catch(logger_1.logger.error);
552
+ return;
553
+ }
554
+ const workspacePath = deps.workspaceService
555
+ ? deps.workspaceService.getWorkspacePath(channelBinding.workspacePath)
556
+ : channelBinding.workspacePath;
557
+ if (!fs_1.default.existsSync(workspacePath) || !fs_1.default.statSync(workspacePath).isDirectory()) {
558
+ await message.reply({ text: `❌ Project folder does not exist: <code>${(0, telegramFormatter_1.escapeHtml)(workspacePath)}</code>` }).catch(logger_1.logger.error);
559
+ return;
560
+ }
561
+ const selectedAccount = (0, accountUtils_1.resolveScopedAccountName)({
562
+ channelId: chatId,
563
+ userId: message.author.id,
564
+ selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
565
+ channelPrefRepo: deps.channelPrefRepo,
566
+ accountPrefRepo: deps.accountPrefRepo,
567
+ accounts: deps.antigravityAccounts,
568
+ });
569
+ const accountPorts = Object.fromEntries((deps.antigravityAccounts ?? []).map((account) => [account.name, account.cdpPort]));
570
+ const accountUserDataDirs = Object.fromEntries((deps.antigravityAccounts ?? [])
571
+ .filter((account) => typeof account.userDataDir === 'string' && account.userDataDir.trim().length > 0)
572
+ .map((account) => [account.name, account.userDataDir.trim()]));
573
+ const port = accountPorts[selectedAccount] ?? null;
574
+ const projectName = deps.bridge.pool.extractProjectName(workspacePath);
575
+ logger_1.logger.info(`[ProjectReopenCommand] channel=${chatId} user=${message.author.id} ` +
576
+ `project=${projectName} account=${selectedAccount} ` +
577
+ `port=${port ?? 'unknown'} workspacePath=${workspacePath}`);
578
+ try {
579
+ const { CdpService } = await Promise.resolve().then(() => __importStar(require('../services/cdpService')));
580
+ const cdp = new CdpService({
581
+ accountName: selectedAccount,
582
+ accountPorts,
583
+ accountUserDataDirs,
584
+ cdpCallTimeout: 15000,
585
+ maxReconnectAttempts: 0,
586
+ });
587
+ try {
588
+ await cdp.openWorkspace(workspacePath);
589
+ }
590
+ finally {
591
+ await cdp.disconnect().catch(() => { });
592
+ }
593
+ deps.bridge.selectedAccountByChannel?.set(chatId, selectedAccount);
594
+ deps.bridge.pool.setPreferredAccountForWorkspace(workspacePath, selectedAccount);
595
+ await message.reply({
596
+ text: `✅ Reopened <b>${(0, telegramFormatter_1.escapeHtml)(projectName)}</b> in account <b>${(0, telegramFormatter_1.escapeHtml)(selectedAccount)}</b>${port ? ` (CDP ${port})` : ''}.`,
597
+ }).catch(logger_1.logger.error);
598
+ }
599
+ catch (error) {
600
+ logger_1.logger.error('[ProjectReopenCommand] Failed to reopen workspace:', error);
601
+ await message.reply({
602
+ text: `❌ Failed to reopen project in account <b>${(0, telegramFormatter_1.escapeHtml)(selectedAccount)}</b>: ${(0, telegramFormatter_1.escapeHtml)(error?.message || String(error))}`,
603
+ }).catch(logger_1.logger.error);
604
+ }
605
+ }
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleJoin = handleJoin;
4
+ exports.handleTelegramJoinSelect = handleTelegramJoinSelect;
5
+ exports.handleMirror = handleMirror;
6
+ const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
7
+ const responseMonitor_1 = require("../services/responseMonitor");
8
+ const sessionPickerUi_1 = require("../ui/sessionPickerUi");
9
+ const logger_1 = require("../utils/logger");
10
+ const telegramFormatter_1 = require("../platform/telegram/telegramFormatter");
11
+ const telegramProjectCommand_1 = require("./telegramProjectCommand");
12
+ const accountUtils_1 = require("../utils/accountUtils");
13
+ const activeResponseMonitors = new Map();
14
+ function resolveAccount(deps, chatId, userId) {
15
+ return (0, accountUtils_1.resolveScopedAccountName)({
16
+ channelId: chatId,
17
+ userId,
18
+ selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
19
+ channelPrefRepo: deps.channelPrefRepo,
20
+ accountPrefRepo: deps.accountPrefRepo,
21
+ accounts: deps.antigravityAccounts,
22
+ });
23
+ }
24
+ async function handleJoin(deps, message) {
25
+ const binding = deps.telegramBindingRepo?.findByChatIdWithParentFallback(message.channel.id);
26
+ if (!binding) {
27
+ await message.reply({ text: '⚠️ No project is linked to this chat. Use /project first, or /project_reopen if this is a previously used session.' }).catch(logger_1.logger.error);
28
+ return;
29
+ }
30
+ const resolvedWorkspacePath = deps.workspaceService
31
+ ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
32
+ : binding.workspacePath;
33
+ const account = resolveAccount(deps, message.channel.id, message.author.id);
34
+ try {
35
+ const cdp = await deps.bridge.pool.getOrConnect(resolvedWorkspacePath, { name: account });
36
+ if (!deps.chatSessionService) {
37
+ await message.reply({ text: 'Chat session service not available.' }).catch(logger_1.logger.error);
38
+ return;
39
+ }
40
+ const sessions = await deps.chatSessionService.listAllSessions(cdp);
41
+ if (sessions.length === 0) {
42
+ await message.reply({ text: 'No active sessions found in this project.' }).catch(logger_1.logger.error);
43
+ return;
44
+ }
45
+ const ui = (0, sessionPickerUi_1.buildSessionPickerPayload)(sessions);
46
+ await message.reply(ui).catch(logger_1.logger.error);
47
+ }
48
+ catch (e) {
49
+ await message.reply({ text: `⚠️ Failed to connect to project: ${e.message}` }).catch(logger_1.logger.error);
50
+ }
51
+ }
52
+ async function handleTelegramJoinSelect(deps, interaction) {
53
+ const selectedTitle = interaction.values[0];
54
+ const originalChannelId = interaction.channel.id;
55
+ const binding = deps.telegramBindingRepo?.findByChatId(originalChannelId);
56
+ if (!binding) {
57
+ await interaction.update({ text: '⚠️ No project is bound to this chat.' }).catch(logger_1.logger.error);
58
+ return;
59
+ }
60
+ const resolvedWorkspacePath = deps.workspaceService
61
+ ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
62
+ : binding.workspacePath;
63
+ const account = resolveAccount(deps, interaction.channel.id, interaction.user.id);
64
+ let cdp;
65
+ try {
66
+ cdp = await deps.bridge.pool.getOrConnect(resolvedWorkspacePath, { name: account });
67
+ }
68
+ catch (e) {
69
+ await interaction.update({ text: `⚠️ Failed to connect to project: ${e.message}` }).catch(logger_1.logger.error);
70
+ return;
71
+ }
72
+ if (!deps.chatSessionService) {
73
+ await interaction.update({ text: 'Chat session service not available.' }).catch(logger_1.logger.error);
74
+ return;
75
+ }
76
+ const activateResult = await deps.chatSessionService.activateSessionByTitle(cdp, selectedTitle);
77
+ if (!activateResult.ok) {
78
+ await interaction.update({ text: `⚠️ Failed to join session: ${activateResult.error}` }).catch(logger_1.logger.error);
79
+ return;
80
+ }
81
+ let targetChannelId = originalChannelId;
82
+ if (deps.botApi && deps.bridge && deps.telegramBindingRepo) {
83
+ targetChannelId = await (0, telegramProjectCommand_1.tryCreateTopicAndBind)(deps.botApi, originalChannelId, binding.workspacePath, deps.telegramBindingRepo, deps.bridge.pool);
84
+ }
85
+ const replyMsg = targetChannelId !== originalChannelId
86
+ ? `✅ Joined session in new topic: <b>${(0, telegramFormatter_1.escapeHtml)(selectedTitle)}</b>\nUse /mirror if you want to forward PC messages here.`
87
+ : `✅ Joined session: <b>${(0, telegramFormatter_1.escapeHtml)(selectedTitle)}</b>\nUse /mirror if you want to forward PC messages here.`;
88
+ await interaction.update({ text: replyMsg }).catch(logger_1.logger.error);
89
+ }
90
+ async function handleMirror(deps, message) {
91
+ const binding = deps.telegramBindingRepo?.findByChatIdWithParentFallback(message.channel.id);
92
+ if (!binding) {
93
+ await message.reply({ text: '⚠️ No project is linked to this chat. Use /project first, or /project_reopen if this is a previously used session.' }).catch(logger_1.logger.error);
94
+ return;
95
+ }
96
+ const resolvedWorkspacePath = deps.workspaceService
97
+ ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
98
+ : binding.workspacePath;
99
+ const projectName = deps.bridge.pool.extractProjectName(resolvedWorkspacePath);
100
+ const account = resolveAccount(deps, message.channel.id, message.author.id);
101
+ const detector = deps.bridge.pool.getUserMessageDetector(projectName, account);
102
+ if (detector?.isActive()) {
103
+ detector.stop();
104
+ const responseMonitor = activeResponseMonitors.get(resolvedWorkspacePath);
105
+ if (responseMonitor?.isActive()) {
106
+ await responseMonitor.stop();
107
+ activeResponseMonitors.delete(resolvedWorkspacePath);
108
+ }
109
+ await message.reply({ text: '📡 Mirroring OFF\nPC-to-Telegram message mirroring has been stopped.' }).catch(logger_1.logger.error);
110
+ }
111
+ else {
112
+ let cdp;
113
+ try {
114
+ cdp = await deps.bridge.pool.getOrConnect(resolvedWorkspacePath, { name: account });
115
+ }
116
+ catch (e) {
117
+ await message.reply({ text: `⚠️ Failed to connect to project: ${e.message}` }).catch(logger_1.logger.error);
118
+ return;
119
+ }
120
+ const existing = deps.bridge.pool.getUserMessageDetector(projectName, account);
121
+ if (existing?.isActive()) {
122
+ existing.stop();
123
+ }
124
+ (0, cdpBridgeManager_1.ensureUserMessageDetector)(deps.bridge, cdp, projectName, (info) => {
125
+ routeMirroredMessage(deps, cdp, resolvedWorkspacePath, info, message.channel).catch((err) => {
126
+ logger_1.logger.error('[TelegramMirror] Error routing mirrored message:', err);
127
+ });
128
+ }, account);
129
+ await message.reply({ text: '📡 Mirroring ON\nMessages typed in Antigravity on your PC will now appear here.' }).catch(logger_1.logger.error);
130
+ }
131
+ }
132
+ async function routeMirroredMessage(deps, cdp, workspacePath, info, channel) {
133
+ const chatTitle = await (0, cdpBridgeManager_1.getCurrentChatTitle)(cdp);
134
+ await channel.send({
135
+ text: `🖥️ <b>User typed in Antigravity:</b>\n<pre>${(0, telegramFormatter_1.escapeHtml)(info.text)}</pre>\n<i>Session: ${(0, telegramFormatter_1.escapeHtml)(chatTitle || 'Unknown')}</i>`
136
+ }).catch((err) => logger_1.logger.error('[TelegramMirror] Failed to send user message:', err));
137
+ startResponseMirror(deps, cdp, workspacePath, channel, chatTitle || 'Unknown');
138
+ }
139
+ function startResponseMirror(deps, cdp, workspacePath, channel, chatTitle) {
140
+ const prev = activeResponseMonitors.get(workspacePath);
141
+ if (prev?.isActive()) {
142
+ prev.stop().catch(() => { });
143
+ }
144
+ const monitor = new responseMonitor_1.ResponseMonitor({
145
+ cdpService: cdp,
146
+ pollIntervalMs: 2000,
147
+ maxDurationMs: 300000,
148
+ extractionMode: deps.extractionMode,
149
+ onComplete: (finalText) => {
150
+ activeResponseMonitors.delete(workspacePath);
151
+ if (!finalText || finalText.trim().length === 0)
152
+ return;
153
+ const maxLen = 3000;
154
+ const text = finalText.length > maxLen
155
+ ? finalText.slice(0, maxLen) + '\n...(truncated)'
156
+ : finalText;
157
+ channel.send({
158
+ text: `🤖 <b>Antigravity Response:</b>\n${(0, telegramFormatter_1.escapeHtml)(text)}\n\n<i>Session: ${(0, telegramFormatter_1.escapeHtml)(chatTitle)}</i>`
159
+ }).catch((err) => logger_1.logger.error('[TelegramMirror] Failed to send AI response:', err));
160
+ },
161
+ onTimeout: () => {
162
+ activeResponseMonitors.delete(workspacePath);
163
+ },
164
+ });
165
+ activeResponseMonitors.set(workspacePath, monitor);
166
+ monitor.startPassive().catch((err) => {
167
+ logger_1.logger.error('[TelegramMirror] Failed to start response monitor:', err);
168
+ activeResponseMonitors.delete(workspacePath);
169
+ });
170
+ }
@@ -22,6 +22,7 @@ const defaultModelApplicator_1 = require("../services/defaultModelApplicator");
22
22
  const logger_1 = require("../utils/logger");
23
23
  const telegramImageHandler_1 = require("../utils/telegramImageHandler");
24
24
  const imageHandler_1 = require("../utils/imageHandler");
25
+ const accountUtils_1 = require("../utils/accountUtils");
25
26
  /**
26
27
  * Create a handler for Telegram messages.
27
28
  * Returns an async function that processes a single PlatformMessage.
@@ -29,6 +30,16 @@ const imageHandler_1 = require("../utils/imageHandler");
29
30
  function createTelegramMessageHandler(deps) {
30
31
  // Per-workspace prompt queue to serialize messages
31
32
  const workspaceQueues = new Map();
33
+ function resolveAccount(chatId, userId) {
34
+ return (0, accountUtils_1.resolveScopedAccountName)({
35
+ channelId: chatId,
36
+ userId,
37
+ selectedAccountByChannel: deps.bridge.selectedAccountByChannel,
38
+ channelPrefRepo: deps.channelPrefRepo,
39
+ accountPrefRepo: deps.accountPrefRepo,
40
+ accounts: deps.antigravityAccounts,
41
+ });
42
+ }
32
43
  function enqueueForWorkspace(workspacePath, task) {
33
44
  const current = (workspaceQueues.get(workspacePath) ?? Promise.resolve()).catch(() => { });
34
45
  const next = current.then(async () => {
@@ -65,6 +76,10 @@ function createTelegramMessageHandler(deps) {
65
76
  fetchQuota: deps.fetchQuota,
66
77
  activeMonitors: deps.activeMonitors,
67
78
  chatSessionService: deps.chatSessionService,
79
+ accountPrefRepo: deps.accountPrefRepo,
80
+ channelPrefRepo: deps.channelPrefRepo,
81
+ antigravityAccounts: deps.antigravityAccounts,
82
+ botApi: deps.botApi,
68
83
  }, message, cmd);
69
84
  return;
70
85
  }
@@ -77,10 +92,10 @@ function createTelegramMessageHandler(deps) {
77
92
  }
78
93
  }
79
94
  // Resolve workspace binding for this Telegram chat
80
- const binding = deps.telegramBindingRepo.findByChatId(chatId);
95
+ const binding = deps.telegramBindingRepo.findByChatIdWithParentFallback(chatId);
81
96
  if (!binding) {
82
97
  await message.reply({
83
- text: 'No project is linked to this chat. Use /project to bind a workspace.',
98
+ text: 'No project is linked to this chat. Use /project to bind a workspace, or /project_reopen if this is a previously used session.',
84
99
  }).catch(logger_1.logger.error);
85
100
  return;
86
101
  }
@@ -91,11 +106,13 @@ function createTelegramMessageHandler(deps) {
91
106
  ? deps.workspaceService.getWorkspacePath(binding.workspacePath)
92
107
  : binding.workspacePath;
93
108
  await enqueueForWorkspace(workspacePath, async () => {
109
+ const selectedAccount = resolveAccount(chatId, message.author.id);
110
+ deps.bridge.selectedAccountByChannel?.set(chatId, selectedAccount);
94
111
  const cdpStartTime = Date.now();
95
112
  logger_1.logger.debug(`[TelegramHandler] getOrConnect start (elapsed=${cdpStartTime - handlerEntryTime}ms)`);
96
113
  let cdp;
97
114
  try {
98
- cdp = await deps.bridge.pool.getOrConnect(workspacePath);
115
+ cdp = await deps.bridge.pool.getOrConnect(workspacePath, { name: selectedAccount });
99
116
  }
100
117
  catch (e) {
101
118
  await message.reply({
@@ -131,10 +148,10 @@ function createTelegramMessageHandler(deps) {
131
148
  }
132
149
  }
133
150
  // Start detectors (platform-agnostic now)
134
- (0, cdpBridgeManager_1.ensureApprovalDetector)(deps.bridge, cdp, projectName);
135
- (0, cdpBridgeManager_1.ensureErrorPopupDetector)(deps.bridge, cdp, projectName);
136
- (0, cdpBridgeManager_1.ensurePlanningDetector)(deps.bridge, cdp, projectName);
137
- (0, cdpBridgeManager_1.ensureRunCommandDetector)(deps.bridge, cdp, projectName);
151
+ (0, cdpBridgeManager_1.ensureApprovalDetector)(deps.bridge, cdp, projectName, selectedAccount);
152
+ (0, cdpBridgeManager_1.ensureErrorPopupDetector)(deps.bridge, cdp, projectName, selectedAccount);
153
+ (0, cdpBridgeManager_1.ensurePlanningDetector)(deps.bridge, cdp, projectName, selectedAccount);
154
+ (0, cdpBridgeManager_1.ensureRunCommandDetector)(deps.bridge, cdp, projectName, selectedAccount);
138
155
  // Acknowledge receipt
139
156
  await message.react('\u{1F440}').catch(() => { });
140
157
  // Download image attachments if present
@@ -1,19 +1,9 @@
1
1
  "use strict";
2
- /**
3
- * Telegram /project command handler.
4
- *
5
- * Allows users to bind a Telegram chat to an Antigravity workspace
6
- * via inline keyboard buttons, similar to Discord's /project slash command.
7
- *
8
- * User flow:
9
- * /project → show workspace list as buttons → user taps → chat bound
10
- * /project list → show workspace list (same as bare /project)
11
- * /project unbind → remove current binding
12
- */
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
3
  exports.TG_PROJECT_SELECT_ID = void 0;
15
4
  exports.parseTelegramProjectCommand = parseTelegramProjectCommand;
16
5
  exports.handleTelegramProjectCommand = handleTelegramProjectCommand;
6
+ exports.tryCreateTopicAndBind = tryCreateTopicAndBind;
17
7
  exports.handleTelegramProjectSelect = handleTelegramProjectSelect;
18
8
  exports.createTelegramSelectHandler = createTelegramSelectHandler;
19
9
  const logger_1 = require("../utils/logger");
@@ -45,6 +35,7 @@ function parseTelegramProjectCommand(text) {
45
35
  // Default (no subcommand or "list") → show workspace list
46
36
  return { subcommand: 'list' };
47
37
  }
38
+ const telegramFormatter_1 = require("../platform/telegram/telegramFormatter");
48
39
  // ---------------------------------------------------------------------------
49
40
  // Command handler
50
41
  // ---------------------------------------------------------------------------
@@ -96,6 +87,55 @@ async function handleTelegramProjectCommand(deps, message, parsed) {
96
87
  /**
97
88
  * Handle a workspace selection callback from inline keyboard.
98
89
  */
90
+ async function tryCreateTopicAndBind(botApi, originalChannelId, workspacePath, telegramBindingRepo, pool) {
91
+ const baseChatId = originalChannelId.split('_')[0];
92
+ const isExistingTopic = originalChannelId.includes('_');
93
+ if (isExistingTopic) {
94
+ telegramBindingRepo.upsert({
95
+ chatId: originalChannelId,
96
+ workspacePath,
97
+ });
98
+ return originalChannelId;
99
+ }
100
+ try {
101
+ const chat = await botApi.getChat(baseChatId);
102
+ logger_1.logger.debug(`[Telegram] getChat(${baseChatId}) returned:`, chat);
103
+ if (chat?.is_forum) {
104
+ const projectName = pool.extractProjectName(workspacePath) || 'Project';
105
+ const topicName = `[Session] ${projectName}`.substring(0, 128);
106
+ const topic = await botApi.createForumTopic(baseChatId, topicName);
107
+ const threadId = topic.message_thread_id;
108
+ const newChannelId = `${baseChatId}_${threadId}`;
109
+ telegramBindingRepo.upsert({
110
+ chatId: newChannelId,
111
+ workspacePath,
112
+ });
113
+ await botApi.sendMessage(baseChatId, `✅ <b>${(0, telegramFormatter_1.escapeHtml)(projectName)}</b> session started in this topic.`, {
114
+ message_thread_id: threadId,
115
+ parse_mode: 'HTML'
116
+ }).catch((e) => logger_1.logger.warn('Failed to send welcome message to new topic', e));
117
+ return newChannelId;
118
+ }
119
+ }
120
+ catch (error) {
121
+ logger_1.logger.debug(`[Telegram] Could not create forum topic for chat ${baseChatId}`, error);
122
+ // If the error is specifically a permissions error for creating topics
123
+ const errStr = String(error);
124
+ if (errStr.includes('not enough rights') || errStr.includes('400')) {
125
+ await botApi.sendMessage(baseChatId, '⚠️ <b>Permission Error:</b> I do not have permission to create a Topic (Forum) in this group.\n\n' +
126
+ 'Please follow these steps to fix:\n' +
127
+ '1. Go to Group Info -> Edit -> Administrators\n' +
128
+ '2. Select this bot (<code>@' + (botApi.me?.username || 'bot') + '</code>)\n' +
129
+ '3. Enable the <b>"Manage Topics"</b> (or "Change Group Info") permission\n' +
130
+ '4. Try binding the project or using /new again.', { parse_mode: 'HTML' }).catch((e) => logger_1.logger.warn('Failed to send permission error message', e));
131
+ }
132
+ }
133
+ telegramBindingRepo.upsert({
134
+ chatId: originalChannelId,
135
+ workspacePath,
136
+ });
137
+ return originalChannelId;
138
+ }
99
139
  async function handleTelegramProjectSelect(deps, interaction) {
100
140
  const selectedWorkspace = interaction.values[0];
101
141
  if (!selectedWorkspace)
@@ -109,13 +149,26 @@ async function handleTelegramProjectSelect(deps, interaction) {
109
149
  }).catch(logger_1.logger.error);
110
150
  return;
111
151
  }
112
- deps.telegramBindingRepo.upsert({
113
- chatId,
114
- workspacePath: selectedWorkspace,
115
- });
116
- await interaction.update({
117
- text: `Workspace bound: <b>${selectedWorkspace}</b>\nSend a message to start chatting with Antigravity.`,
118
- }).catch(logger_1.logger.error);
152
+ let finalChannelId = chatId;
153
+ if (deps.botApi && deps.bridge) {
154
+ finalChannelId = await tryCreateTopicAndBind(deps.botApi, chatId, selectedWorkspace, deps.telegramBindingRepo, deps.bridge.pool);
155
+ }
156
+ else {
157
+ deps.telegramBindingRepo.upsert({
158
+ chatId,
159
+ workspacePath: selectedWorkspace,
160
+ });
161
+ }
162
+ if (finalChannelId !== chatId) {
163
+ await interaction.update({
164
+ text: `✅ Workspace bound to new topic: <b>${(0, telegramFormatter_1.escapeHtml)(selectedWorkspace)}</b>`,
165
+ }).catch(logger_1.logger.error);
166
+ }
167
+ else {
168
+ await interaction.update({
169
+ text: `Workspace bound: <b>${(0, telegramFormatter_1.escapeHtml)(selectedWorkspace)}</b>\nSend a message to start chatting with Antigravity.`,
170
+ }).catch(logger_1.logger.error);
171
+ }
119
172
  logger_1.logger.info(`[TelegramProject] Chat ${chatId} bound to workspace: ${selectedWorkspace}`);
120
173
  }
121
174
  // ---------------------------------------------------------------------------