lazy-gravity 0.5.4 → 0.5.6

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 CHANGED
@@ -499,7 +499,7 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
499
499
  const monitor = new responseMonitor_1.ResponseMonitor({
500
500
  cdpService: cdp,
501
501
  pollIntervalMs: 2000,
502
- maxDurationMs: 300000,
502
+ maxDurationMs: options?.responseTimeoutMs,
503
503
  stopGoneConfirmCount: 3,
504
504
  extractionMode: options?.extractionMode,
505
505
  onPhaseChange: (_phase, _text) => {
@@ -663,9 +663,10 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
663
663
  : lastProgressText;
664
664
  const separated = (0, discordFormatter_1.splitOutputAndLogs)(timeoutText || '');
665
665
  const sanitizedTimeoutLogs = lastActivityLogText || processLogBuffer.snapshot();
666
+ const timeoutMinutes = Math.round((options?.responseTimeoutMs ?? 900000) / 60000);
666
667
  const payload = separated.output && separated.output.trim().length > 0
667
- ? (0, i18n_1.t)(`${separated.output}\n\n[Monitor Ended] Timeout after 5 minutes.`)
668
- : 'Monitor ended after 5 minutes. No text was retrieved.';
668
+ ? (0, i18n_1.t)(`${separated.output}\n\n[Monitor Ended] Timeout after ${timeoutMinutes} minutes of inactivity.`)
669
+ : `Monitor ended after ${timeoutMinutes} minutes of inactivity. No text was retrieved.`;
669
670
  liveResponseUpdateVersion += 1;
670
671
  const responseVersion = liveResponseUpdateVersion;
671
672
  await upsertLiveResponseEmbeds(`${PHASE_ICONS.timeout} Timeout`, payload, PHASE_COLORS.timeout, `⏱️ Elapsed: ${elapsed}s | Timeout`, {
@@ -774,7 +775,7 @@ const startBot = async (cliLogLevel) => {
774
775
  discord_js_1.GatewayIntentBits.MessageContent,
775
776
  ]
776
777
  });
777
- const joinHandler = new joinCommandHandler_1.JoinCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, bridge.pool, workspaceService, client, config.extractionMode);
778
+ const joinHandler = new joinCommandHandler_1.JoinCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, bridge.pool, workspaceService, client, config.extractionMode, config.responseTimeoutMs);
778
779
  client.once(discord_js_1.Events.ClientReady, async (readyClient) => {
779
780
  logger_1.logger.info(`Ready! Logged in as ${readyClient.user.tag} | extractionMode=${config.extractionMode}`);
780
781
  try {
@@ -969,6 +970,7 @@ const startBot = async (cliLogLevel) => {
969
970
  botToken: config.telegramToken,
970
971
  botApi: telegramBot.api,
971
972
  chatSessionService,
973
+ responseTimeoutMs: config.responseTimeoutMs,
972
974
  });
973
975
  // Compose select handlers: project select + mode select
974
976
  const projectSelectHandler = (0, telegramProjectCommand_1.createTelegramSelectHandler)({
@@ -192,7 +192,7 @@ function createTelegramMessageHandler(deps) {
192
192
  // Send initial status message
193
193
  statusMsg = await channel.send({ text: 'Processing...' }).catch(() => null);
194
194
  await new Promise((resolve) => {
195
- const TIMEOUT_MS = 300_000;
195
+ const TIMEOUT_MS = deps.responseTimeoutMs ?? 900_000;
196
196
  let settled = false;
197
197
  const settle = () => {
198
198
  if (settled)
@@ -24,9 +24,10 @@ class JoinCommandHandler {
24
24
  workspaceService;
25
25
  client;
26
26
  extractionMode;
27
+ responseTimeoutMs;
27
28
  /** Active ResponseMonitors per workspace (for AI response mirroring) */
28
29
  activeResponseMonitors = new Map();
29
- constructor(chatSessionService, chatSessionRepo, bindingRepo, channelManager, pool, workspaceService, client, extractionMode) {
30
+ constructor(chatSessionService, chatSessionRepo, bindingRepo, channelManager, pool, workspaceService, client, extractionMode, responseTimeoutMs) {
30
31
  this.chatSessionService = chatSessionService;
31
32
  this.chatSessionRepo = chatSessionRepo;
32
33
  this.bindingRepo = bindingRepo;
@@ -35,6 +36,7 @@ class JoinCommandHandler {
35
36
  this.workspaceService = workspaceService;
36
37
  this.client = client;
37
38
  this.extractionMode = extractionMode;
39
+ this.responseTimeoutMs = responseTimeoutMs;
38
40
  }
39
41
  /**
40
42
  * Resolve a project name (from DB) to its full absolute path.
@@ -282,7 +284,7 @@ class JoinCommandHandler {
282
284
  const monitor = new responseMonitor_1.ResponseMonitor({
283
285
  cdpService: cdp,
284
286
  pollIntervalMs: 2000,
285
- maxDurationMs: 300000,
287
+ maxDurationMs: this.responseTimeoutMs,
286
288
  extractionMode: this.extractionMode,
287
289
  onComplete: (finalText) => {
288
290
  this.activeResponseMonitors.delete(projectName);
@@ -118,6 +118,7 @@ function createMessageCreateHandler(deps) {
118
118
  titleGenerator: deps.titleGenerator,
119
119
  userPrefRepo: deps.userPrefRepo,
120
120
  extractionMode: deps.config.extractionMode,
121
+ responseTimeoutMs: deps.config.responseTimeoutMs,
121
122
  });
122
123
  }
123
124
  else {
@@ -177,12 +178,37 @@ function createMessageCreateHandler(deps) {
177
178
  registerApprovalSessionChannel(deps.bridge, projectName, session.displayName, platformChannel);
178
179
  }
179
180
  if (session?.isRenamed && session.displayName) {
180
- const activationResult = await deps.chatSessionService.activateSessionByTitle(cdp, session.displayName);
181
+ let activationResult = await deps.chatSessionService.activateSessionByTitle(cdp, session.displayName);
181
182
  if (!activationResult.ok) {
182
- const reason = activationResult.error ? ` (${activationResult.error})` : '';
183
- await message.reply(`⚠️ Could not route this message to the bound session (${session.displayName}). ` +
184
- `Please open /chat and verify the session${reason}.`).catch(() => { });
185
- return;
183
+ // Recovery: Antigravity may have renamed the session.
184
+ // Check if the currently active chat is the renamed version.
185
+ const currentInfo = await deps.chatSessionService.getCurrentSessionInfo(cdp);
186
+ const isRecoverable = currentInfo.hasActiveChat
187
+ && currentInfo.title.trim() !== ''
188
+ && currentInfo.title !== session.displayName;
189
+ if (isRecoverable) {
190
+ const siblings = deps.chatSessionRepo.findByCategoryId(session.categoryId);
191
+ const ownedByOther = siblings.some((s) => s.channelId !== message.channelId
192
+ && s.displayName === currentInfo.title);
193
+ if (!ownedByOther) {
194
+ const recoveredTitle = currentInfo.title;
195
+ const retryResult = await deps.chatSessionService.activateSessionByTitle(cdp, recoveredTitle);
196
+ if (retryResult.ok) {
197
+ logger_1.logger.info(`[SessionRecovery] Adopting renamed title: ` +
198
+ `"${session.displayName}" -> "${recoveredTitle}" ` +
199
+ `(channel: ${message.channelId})`);
200
+ deps.chatSessionRepo.updateDisplayName(message.channelId, recoveredTitle);
201
+ registerApprovalSessionChannel(deps.bridge, projectName, recoveredTitle, platformChannel);
202
+ }
203
+ activationResult = retryResult;
204
+ }
205
+ }
206
+ if (!activationResult.ok) {
207
+ const reason = activationResult.error ? ` (${activationResult.error})` : '';
208
+ await message.reply(`⚠️ Could not route this message to the bound session (${session.displayName}). ` +
209
+ `Please open /chat and verify the session${reason}.`).catch(() => { });
210
+ return;
211
+ }
186
212
  }
187
213
  }
188
214
  else if (session && !session.isRenamed) {
@@ -238,6 +264,7 @@ function createMessageCreateHandler(deps) {
238
264
  titleGenerator: deps.titleGenerator,
239
265
  userPrefRepo: deps.userPrefRepo,
240
266
  extractionMode: deps.config.extractionMode,
267
+ responseTimeoutMs: deps.config.responseTimeoutMs,
241
268
  onFullCompletion: settle,
242
269
  }).catch((err) => {
243
270
  // sendPromptToAntigravity rejected before onFullCompletion fired
@@ -472,7 +472,7 @@ class ResponseMonitor {
472
472
  constructor(options) {
473
473
  this.cdpService = options.cdpService;
474
474
  this.pollIntervalMs = options.pollIntervalMs ?? 2000;
475
- this.maxDurationMs = options.maxDurationMs ?? 300000;
475
+ this.maxDurationMs = options.maxDurationMs ?? 900000;
476
476
  this.stopGoneConfirmCount = options.stopGoneConfirmCount ?? 3;
477
477
  this.extractionMode = options.extractionMode ?? 'structured';
478
478
  this.onProgress = options.onProgress;
@@ -861,7 +861,10 @@ class ResponseMonitor {
861
861
  }
862
862
  }
863
863
  // Activity-based inactivity timeout (#49)
864
- if (this.maxDurationMs > 0 && Date.now() - this.lastActivityTime >= this.maxDurationMs) {
864
+ // Guard: never timeout while the stop button is visible — it means
865
+ // Antigravity is still actively generating (extended thinking, long
866
+ // shell commands, large file operations, etc.).
867
+ if (this.maxDurationMs > 0 && !isGenerating && Date.now() - this.lastActivityTime >= this.maxDurationMs) {
865
868
  const lastText = this.lastText ?? '';
866
869
  this.setPhase('timeout', lastText);
867
870
  await this.stop();
@@ -102,6 +102,7 @@ function mergeConfig(persisted) {
102
102
  const autoApproveFileEdits = resolveBoolean(process.env.AUTO_APPROVE_FILE_EDITS, persisted.autoApproveFileEdits, false);
103
103
  const logLevel = resolveLogLevel(process.env.LOG_LEVEL, persisted.logLevel);
104
104
  const extractionMode = resolveExtractionMode(process.env.EXTRACTION_MODE, persisted.extractionMode);
105
+ const responseTimeoutMs = resolvePositiveInt(process.env.RESPONSE_TIMEOUT_MS, persisted.responseTimeoutMs, 900000);
105
106
  // Telegram credentials — only required when Telegram is an active platform
106
107
  const telegramToken = process.env.TELEGRAM_BOT_TOKEN ?? persisted.telegramToken ?? undefined;
107
108
  const telegramAllowedUserIds = resolveTelegramAllowedUserIds(persisted);
@@ -117,6 +118,7 @@ function mergeConfig(persisted) {
117
118
  autoApproveFileEdits,
118
119
  logLevel,
119
120
  extractionMode,
121
+ responseTimeoutMs,
120
122
  telegramToken,
121
123
  telegramAllowedUserIds,
122
124
  platforms,
@@ -186,6 +188,20 @@ function resolveBoolean(envValue, persistedValue, defaultValue) {
186
188
  return persistedValue;
187
189
  return defaultValue;
188
190
  }
191
+ /**
192
+ * Resolve a non-negative integer value from env var > persisted config > default.
193
+ * Returns the default if the env/persisted value is not a valid non-negative integer.
194
+ */
195
+ function resolvePositiveInt(envValue, persistedValue, defaultValue) {
196
+ if (envValue !== undefined) {
197
+ const parsed = parseInt(envValue, 10);
198
+ if (!isNaN(parsed) && parsed >= 0)
199
+ return parsed;
200
+ }
201
+ if (persistedValue !== undefined && persistedValue >= 0)
202
+ return persistedValue;
203
+ return defaultValue;
204
+ }
189
205
  // ---------------------------------------------------------------------------
190
206
  // Public API (ConfigLoader namespace)
191
207
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazy-gravity",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Control Antigravity from anywhere — a local, secure bot (Discord + Telegram) that lets you remotely operate Antigravity on your home PC from your smartphone.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -65,7 +65,7 @@
65
65
  "@types/ws": "^8.18.1",
66
66
  "jest": "^30.2.0",
67
67
  "jest-environment-jsdom": "^30.2.0",
68
- "jsdom": "^28.1.0",
68
+ "jsdom": "^29.0.0",
69
69
  "minimatch": "^10.2.1",
70
70
  "semantic-release": "^25.0.3",
71
71
  "ts-jest": "^29.4.6",