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 +6 -1
- package/bin/setup.js +14 -0
- package/npm-scripts/postinstall.js +5 -19
- package/npm-scripts/register-hook.js +32 -23
- package/package.json +1 -1
- package/src/api/server.py +1 -1
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
|
-
//
|
|
13
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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:
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
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, "
|
|
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")
|