lazy-gravity 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bot/index.js CHANGED
@@ -97,6 +97,20 @@ const modelButtonAction_1 = require("../handlers/modelButtonAction");
97
97
  const autoAcceptButtonAction_1 = require("../handlers/autoAcceptButtonAction");
98
98
  const templateButtonAction_1 = require("../handlers/templateButtonAction");
99
99
  const modeSelectAction_1 = require("../handlers/modeSelectAction");
100
+ const channelManager_2 = require("../services/channelManager");
101
+ /**
102
+ * Normalize a candidate startup channel name for preference checks.
103
+ */
104
+ function normalizeStartupChannelName(name) {
105
+ return name.trim().replace(/^#/, '').toLowerCase();
106
+ }
107
+ /**
108
+ * Prefer the shared default channel name plus the localized 常规 variant.
109
+ */
110
+ function isPreferredDiscordStartupChannel(name) {
111
+ const normalized = normalizeStartupChannelName(name);
112
+ return normalized === channelManager_2.DEFAULT_CHANNEL_NAME || normalized === '常规';
113
+ }
100
114
  // =============================================================================
101
115
  // Embed color palette (color-coded by phase)
102
116
  // =============================================================================
@@ -499,7 +513,7 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
499
513
  const monitor = new responseMonitor_1.ResponseMonitor({
500
514
  cdpService: cdp,
501
515
  pollIntervalMs: 2000,
502
- maxDurationMs: 300000,
516
+ maxDurationMs: options?.responseTimeoutMs,
503
517
  stopGoneConfirmCount: 3,
504
518
  extractionMode: options?.extractionMode,
505
519
  onPhaseChange: (_phase, _text) => {
@@ -663,9 +677,10 @@ async function sendPromptToAntigravity(bridge, message, prompt, cdp, modeService
663
677
  : lastProgressText;
664
678
  const separated = (0, discordFormatter_1.splitOutputAndLogs)(timeoutText || '');
665
679
  const sanitizedTimeoutLogs = lastActivityLogText || processLogBuffer.snapshot();
680
+ const timeoutMinutes = Math.round((options?.responseTimeoutMs ?? 900000) / 60000);
666
681
  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.';
682
+ ? (0, i18n_1.t)(`${separated.output}\n\n[Monitor Ended] Timeout after ${timeoutMinutes} minutes of inactivity.`)
683
+ : `Monitor ended after ${timeoutMinutes} minutes of inactivity. No text was retrieved.`;
669
684
  liveResponseUpdateVersion += 1;
670
685
  const responseVersion = liveResponseUpdateVersion;
671
686
  await upsertLiveResponseEmbeds(`${PHASE_ICONS.timeout} Timeout`, payload, PHASE_COLORS.timeout, `⏱️ Elapsed: ${elapsed}s | Timeout`, {
@@ -774,7 +789,7 @@ const startBot = async (cliLogLevel) => {
774
789
  discord_js_1.GatewayIntentBits.MessageContent,
775
790
  ]
776
791
  });
777
- const joinHandler = new joinCommandHandler_1.JoinCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, bridge.pool, workspaceService, client, config.extractionMode);
792
+ const joinHandler = new joinCommandHandler_1.JoinCommandHandler(chatSessionService, chatSessionRepo, workspaceBindingRepo, channelManager, bridge.pool, workspaceService, client, config.extractionMode, config.responseTimeoutMs);
778
793
  client.once(discord_js_1.Events.ClientReady, async (readyClient) => {
779
794
  logger_1.logger.info(`Ready! Logged in as ${readyClient.user.tag} | extractionMode=${config.extractionMode}`);
780
795
  try {
@@ -800,10 +815,14 @@ const startBot = async (cliLogLevel) => {
800
815
  .addFields({ name: 'Version', value: version, inline: true }, { name: 'Node.js', value: process.versions.node, inline: true }, { name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true }, { name: 'CDP', value: cdpStatus, inline: true }, { name: 'Model', value: modelService.getCurrentModel(), inline: true }, { name: 'Mode', value: modeService.getCurrentMode(), inline: true }, { name: 'Projects', value: `${projects.length} registered`, inline: true }, { name: 'Extraction', value: config.extractionMode, inline: true })
801
816
  .setFooter({ text: `Started at ${new Date().toLocaleString()}` })
802
817
  .setTimestamp();
803
- // Send to the first available text channel in the guild
818
+ // Prefer the guild's general text channel, then fall back to the first sendable text channel.
804
819
  const guild = readyClient.guilds.cache.first();
805
820
  if (guild) {
806
- const channel = guild.channels.cache.find((ch) => ch.isTextBased() && !ch.isVoiceBased() && ch.permissionsFor(readyClient.user)?.has('SendMessages'));
821
+ const sendableTextChannels = guild.channels.cache.filter((ch) => ch.isTextBased()
822
+ && !ch.isVoiceBased()
823
+ && ch.permissionsFor(readyClient.user)?.has('SendMessages'));
824
+ const channel = sendableTextChannels.find((ch) => isPreferredDiscordStartupChannel(ch.name))
825
+ ?? sendableTextChannels.first();
807
826
  if (channel && channel.isTextBased()) {
808
827
  await channel.send({ embeds: [dashboardEmbed] });
809
828
  logger_1.logger.info('Startup dashboard embed sent.');
@@ -969,6 +988,7 @@ const startBot = async (cliLogLevel) => {
969
988
  botToken: config.telegramToken,
970
989
  botApi: telegramBot.api,
971
990
  chatSessionService,
991
+ responseTimeoutMs: config.responseTimeoutMs,
972
992
  });
973
993
  // Compose select handlers: project select + mode select
974
994
  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 {
@@ -263,6 +264,7 @@ function createMessageCreateHandler(deps) {
263
264
  titleGenerator: deps.titleGenerator,
264
265
  userPrefRepo: deps.userPrefRepo,
265
266
  extractionMode: deps.config.extractionMode,
267
+ responseTimeoutMs: deps.config.responseTimeoutMs,
266
268
  onFullCompletion: settle,
267
269
  }).catch((err) => {
268
270
  // sendPromptToAntigravity rejected before onFullCompletion fired
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChannelManager = void 0;
3
+ exports.ChannelManager = exports.DEFAULT_CHANNEL_NAME = void 0;
4
4
  const discord_js_1 = require("discord.js");
5
5
  /** Category name prefix emoji */
6
6
  const CATEGORY_PREFIX = '🗂️-';
7
7
  /** Default channel name under the category */
8
- const DEFAULT_CHANNEL_NAME = 'general';
8
+ exports.DEFAULT_CHANNEL_NAME = 'general';
9
9
  /**
10
10
  * Class that manages Discord categories and channels corresponding to workspace paths.
11
11
  * Creates the category/channel if they don't exist for the given workspace name,
@@ -77,7 +77,7 @@ class ChannelManager {
77
77
  const existingTextChannel = guild.channels.cache.find((ch) => ch.type === discord_js_1.ChannelType.GuildText &&
78
78
  'parentId' in ch &&
79
79
  ch.parentId === categoryId &&
80
- ch.name === DEFAULT_CHANNEL_NAME);
80
+ ch.name === exports.DEFAULT_CHANNEL_NAME);
81
81
  if (existingTextChannel) {
82
82
  return {
83
83
  categoryId,
@@ -85,7 +85,7 @@ class ChannelManager {
85
85
  created: false,
86
86
  };
87
87
  }
88
- const sessionResult = await this.createSessionChannel(guild, categoryId, DEFAULT_CHANNEL_NAME);
88
+ const sessionResult = await this.createSessionChannel(guild, categoryId, exports.DEFAULT_CHANNEL_NAME);
89
89
  return {
90
90
  categoryId,
91
91
  channelId: sessionResult.channelId,
@@ -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.5",
3
+ "version": "0.6.0",
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",