linkgravity 1.5.5 → 1.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/bin/cli.js CHANGED
@@ -200,6 +200,10 @@ function verifyStartup() {
200
200
  finish(false);
201
201
  }, 30000);
202
202
 
203
+ const isBenignShutdownNoise = (str) =>
204
+ str.includes('asyncio.exceptions.CancelledError') &&
205
+ str.includes('Application.stop() complete');
206
+
203
207
  const checkLog = (data) => {
204
208
  if (settled) return;
205
209
  const str = data.toString();
@@ -210,7 +214,8 @@ function verifyStartup() {
210
214
  errorDetectionArmed &&
211
215
  (str.includes('Traceback (most recent call last):') ||
212
216
  str.includes('Error:') ||
213
- str.includes('Exception:'))
217
+ str.includes('Exception:')) &&
218
+ !isBenignShutdownNoise(str)
214
219
  ) {
215
220
  console.log(`\n\n${color.yellow}❌ Error detected during startup:${color.reset}`);
216
221
  const errorLines = str
package/bin/setup.js CHANGED
@@ -446,6 +446,20 @@ async function runSetup() {
446
446
  console.log();
447
447
  p.intro(`${color.cyan}▶ LinkGravity Setup Wizard${color.reset}`);
448
448
 
449
+ const registerHook = require('../npm-scripts/register-hook');
450
+ if (!registerHook.isHookRegistered()) {
451
+ const consent = await p.confirm({
452
+ message:
453
+ "Register LinkGravity's approval hook with agy? (required for tool-call approval - lets LinkGravity gate agy's actions through Discord/Telegram/Slack)",
454
+ initialValue: true,
455
+ });
456
+ if (p.isCancel(consent)) {
457
+ p.cancel('Setup cancelled.');
458
+ process.exit(0);
459
+ }
460
+ if (consent) registerHook({ allowFirstTimeCreate: true });
461
+ }
462
+
449
463
  while (true) {
450
464
  const settings = getSettings();
451
465
  const options = Object.entries(PLATFORMS).map(([key, def]) => {
@@ -9,43 +9,29 @@ console.log(` (in ${path.join(workspaceDir, 'venv')} - not inside this install
9
9
  console.log(' package updates/reinstalls and works the same whether this is a global');
10
10
  console.log(' `npm install -g linkgravity` or a local dev clone.)');
11
11
 
12
- // Use python on Windows, python3 on Mac/Linux - this is the *system*
13
- // python used only to create the venv below; once it exists, every
14
- // other script (this one included) goes through venv-paths.js instead.
12
+ // System python (not the venv's) - only used to create the venv below; every other
13
+ // script goes through venv-paths.js instead.
15
14
  const isWin = os.platform() === 'win32';
16
15
  const pyCmd = isWin ? 'python' : 'python3';
17
16
 
18
17
  async function main() {
19
18
  try {
20
- // 1. Create Python virtual environment (venv) in the fixed
21
- // workspace dir, not in cwd - see venv-paths.js for why.
19
+ // Fixed workspace dir, not cwd - see venv-paths.js for why.
22
20
  fs.mkdirSync(workspaceDir, { recursive: true });
23
21
  execSync(`${pyCmd} -m venv "${path.join(workspaceDir, 'venv')}"`, { stdio: 'inherit' });
24
22
 
25
- // 2. Install Python packages
26
23
  console.log('📦 Installing Python dependencies...');
27
24
  execSync(`"${venvPip}" install -r requirements.txt`, { stdio: 'inherit' });
28
25
 
29
- // 3. Install Node.js voice service packages (includes
30
- // rustpotter-web, which handles both wake-word detection AND
31
- // building .rpw reference files in-process - no separate
32
- // binary download needed for either).
33
26
  console.log('🎙️ Installing Voice Service dependencies...');
34
27
  execSync('npm install', {
35
28
  stdio: 'inherit',
36
29
  cwd: path.join(__dirname, '..', 'voice-service'),
37
30
  });
38
31
 
39
- // 4. Register this install's location with agy as its
40
- // tool-approval hook (~/.gemini/config/hooks.json) - always
41
- // re-run so the registered path self-heals if this checkout
42
- // gets moved/renamed later, instead of silently going stale.
43
- // Guarded on its own: agy may not be installed/configured yet
44
- // on a brand new machine, and that shouldn't fail the rest of
45
- // the install - just means the hook needs registering once agy
46
- // itself is set up (re-running `npm install` after does it).
32
+ // Own try/catch: agy may not be installed yet on a brand new machine, and that shouldn't fail the rest of the install.
47
33
  try {
48
- require('./register-hook')();
34
+ require('./register-hook')({ allowFirstTimeCreate: false });
49
35
  } catch (err) {
50
36
  console.warn(
51
37
  `⚠️ Couldn't register the agy tool-approval hook: ${err.message.split('\n')[0]}`,
@@ -1,11 +1,5 @@
1
1
  'use strict';
2
- // Registers/fixes this project's agy hooks in ~/.gemini/config/hooks.json
3
- // (schema: https://antigravity.google/docs/hooks). Runs on every `npm
4
- // install` so paths stay correct if this checkout moves, identifying its
5
- // own entries by `name` (not command string) so a stale path gets fixed
6
- // in place rather than duplicated, leaving any other configured hooks
7
- // untouched. Only ever overwrites `command` - never `type`/`timeout`,
8
- // which the user may have customized - logging old/new values on change.
2
+ // Only ever overwrites `command` on an existing entry - never `type`/`timeout`, which the user may have customized.
9
3
  const fs = require('fs');
10
4
  const path = require('path');
11
5
  const os = require('os');
@@ -13,11 +7,9 @@ const { repoRoot, python: venvPython } = require('./venv-paths');
13
7
 
14
8
  const hooksJsonPath = path.join(os.homedir(), '.gemini', 'config', 'hooks.json');
15
9
 
16
- // PreToolUse: tool-approval gate, matcher-wrapped per schema.
17
- // Stop: fires when agy is about to end a turn; if fullyIdle is false
18
- // (an async run_command is still in flight), stop_hook.py tells agy to
19
- // keep going instead of ending the turn with that result lost. Flat
20
- // array per schema (no matcher - nothing to match tool names against).
10
+ // PreToolUse/Stop meanings are agy's own hook contract: Stop fires when agy is about to
11
+ // end a turn, and if fullyIdle is false (an async run_command still in flight), stop_hook.py
12
+ // tells agy to keep going instead of losing that result.
21
13
  const HOOK_REGISTRATIONS = [
22
14
  {
23
15
  eventType: 'PreToolUse',
@@ -35,9 +27,6 @@ const HOOK_REGISTRATIONS = [
35
27
  },
36
28
  ];
37
29
 
38
- // Hooks retired from HOOK_REGISTRATIONS but listed here so an existing
39
- // install actually gets the stale entry removed from hooks.json, instead
40
- // of a zombie entry pointing at a script that no longer exists.
41
30
  const RETIRED_HOOKS = [{ eventType: 'PreInvocation', name: 'wait-ms-before-async-reminder' }];
42
31
 
43
32
  function loadHooksConfig() {
@@ -47,9 +36,6 @@ function loadHooksConfig() {
47
36
  try {
48
37
  return JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8'));
49
38
  } catch (err) {
50
- // Back up rather than silently clobbering whatever was there -
51
- // it may have other hooks configured that have nothing to do
52
- // with this project.
53
39
  const backupPath = `${hooksJsonPath}.corrupted-${Date.now()}`;
54
40
  fs.copyFileSync(hooksJsonPath, backupPath);
55
41
  console.warn(
@@ -76,7 +62,6 @@ function findHookEntry(config, eventType, name, wrapInMatcher) {
76
62
  }
77
63
  return hookEntry;
78
64
  }
79
- // Flat array (Stop/PreInvocation/PostInvocation) - no matcher wrapper.
80
65
  let hookEntry = config.hooks[eventType].find((h) => h.name === name);
81
66
  if (!hookEntry) {
82
67
  hookEntry = { name };
@@ -94,8 +79,6 @@ function removeRetiredHooks(config) {
94
79
  const nextArr = [];
95
80
  for (const entry of arr) {
96
81
  if (Array.isArray(entry.hooks)) {
97
- // Matcher-wrapped shape - drop the matcher block too if
98
- // nothing's left in it.
99
82
  const beforeLen = entry.hooks.length;
100
83
  entry.hooks = entry.hooks.filter((h) => h.name !== retired.name);
101
84
  if (entry.hooks.length !== beforeLen) {
@@ -106,7 +89,6 @@ function removeRetiredHooks(config) {
106
89
  }
107
90
  if (entry.hooks.length > 0) nextArr.push(entry);
108
91
  } else {
109
- // Flat shape.
110
92
  if (entry.name === retired.name) {
111
93
  removedAny = true;
112
94
  console.log(
@@ -122,9 +104,24 @@ function removeRetiredHooks(config) {
122
104
  return removedAny;
123
105
  }
124
106
 
125
- function registerHook() {
107
+ function registerHook({ allowFirstTimeCreate = true } = {}) {
126
108
  const config = loadHooksConfig();
127
109
  config.hooks = config.hooks || {};
110
+
111
+ const isFirstTime = HOOK_REGISTRATIONS.some((reg) => {
112
+ const arr = config.hooks[reg.eventType] || [];
113
+ return reg.wrapInMatcher
114
+ ? !arr.some((m) => (m.hooks || []).some((h) => h.name === reg.name))
115
+ : !arr.some((h) => h.name === reg.name);
116
+ });
117
+
118
+ if (isFirstTime && !allowFirstTimeCreate) {
119
+ console.log(
120
+ "ℹ️ LinkGravity's Discord/Telegram/Slack approval hook isn't registered with agy yet - run `lgy setup` to enable it.",
121
+ );
122
+ return;
123
+ }
124
+
128
125
  let wroteChange = false;
129
126
  let backedUp = false;
130
127
 
@@ -175,7 +172,19 @@ function registerHook() {
175
172
  }
176
173
  }
177
174
 
175
+ function isHookRegistered() {
176
+ const config = loadHooksConfig();
177
+ config.hooks = config.hooks || {};
178
+ return HOOK_REGISTRATIONS.every((reg) => {
179
+ const arr = config.hooks[reg.eventType] || [];
180
+ return reg.wrapInMatcher
181
+ ? arr.some((m) => (m.hooks || []).some((h) => h.name === reg.name))
182
+ : arr.some((h) => h.name === reg.name);
183
+ });
184
+ }
185
+
178
186
  module.exports = registerHook;
187
+ module.exports.isHookRegistered = isHookRegistered;
179
188
 
180
189
  if (require.main === module) {
181
190
  registerHook();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkgravity",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "Discord/Telegram bot bridge for the Antigravity (agy) CLI, with voice interaction support",
5
5
  "scripts": {
6
6
  "postinstall": "node npm-scripts/postinstall.js",
package/src/api/server.py CHANGED
@@ -41,6 +41,6 @@ async def setup_webhook_server(bot):
41
41
 
42
42
  runner = web.AppRunner(app)
43
43
  await runner.setup()
44
- site = web.TCPSite(runner, "0.0.0.0", 18080)
44
+ site = web.TCPSite(runner, "127.0.0.1", 18080)
45
45
  await site.start()
46
46
  logger.info("Webhook / STT Server started on port 18080")