knoxis-helper 1.1.0 → 1.2.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.
- package/bin/knoxis-helper.js +251 -8
- package/lib/knoxis-local-agent.js +37 -8
- package/package.json +7 -2
package/bin/knoxis-helper.js
CHANGED
|
@@ -3,20 +3,35 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Knoxis Helper - First-time setup + local agent launcher
|
|
5
5
|
*
|
|
6
|
+
* On first run (typically via npx), this copies the agent files to a stable
|
|
7
|
+
* local path (~/.knoxis/agent/) so that:
|
|
8
|
+
* 1. Subsequent runs don't need network (works behind VPN)
|
|
9
|
+
* 2. The macOS LaunchAgent always points to a path that exists
|
|
10
|
+
* 3. Coworkers get the same reliable experience
|
|
11
|
+
*
|
|
6
12
|
* Usage:
|
|
7
|
-
* npx knoxis-helper #
|
|
8
|
-
* npx knoxis-helper --backend wss://...
|
|
9
|
-
* npx knoxis-helper --status
|
|
13
|
+
* npx knoxis-helper # First-time: setup + install locally
|
|
14
|
+
* npx knoxis-helper --backend wss://... # Connect to specific backend
|
|
15
|
+
* npx knoxis-helper --status # Check connection status
|
|
16
|
+
* npx knoxis-helper --check # Diagnose connectivity issues
|
|
17
|
+
* npx knoxis-helper --update # Force re-copy of agent files
|
|
18
|
+
* npx knoxis-helper --uninstall # Remove LaunchAgent + local files
|
|
10
19
|
*/
|
|
11
20
|
|
|
12
21
|
const fs = require('fs');
|
|
13
22
|
const path = require('path');
|
|
14
23
|
const os = require('os');
|
|
24
|
+
const https = require('https');
|
|
25
|
+
const http = require('http');
|
|
15
26
|
const readline = require('readline');
|
|
27
|
+
const { spawnSync } = require('child_process');
|
|
16
28
|
|
|
17
29
|
const CONFIG_DIR = path.join(os.homedir(), '.knoxis');
|
|
18
30
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
19
|
-
const
|
|
31
|
+
const AGENT_DIR = path.join(CONFIG_DIR, 'agent');
|
|
32
|
+
const STABLE_AGENT_PATH = path.join(AGENT_DIR, 'knoxis-local-agent.js');
|
|
33
|
+
const PLIST_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.knoxis.helper.plist');
|
|
34
|
+
const DEFAULT_BACKEND = 'wss://voice-app-v2.graymushroom-5a5599fe.centralus.azurecontainerapps.io';
|
|
20
35
|
|
|
21
36
|
function loadConfig() {
|
|
22
37
|
try {
|
|
@@ -40,6 +55,9 @@ function parseArgs() {
|
|
|
40
55
|
const arg = process.argv[i];
|
|
41
56
|
if (arg === '--status') { args.status = true; continue; }
|
|
42
57
|
if (arg === '--reset') { args.reset = true; continue; }
|
|
58
|
+
if (arg === '--check') { args.check = true; continue; }
|
|
59
|
+
if (arg === '--update') { args.update = true; continue; }
|
|
60
|
+
if (arg === '--uninstall') { args.uninstall = true; continue; }
|
|
43
61
|
if (arg.startsWith('--')) {
|
|
44
62
|
const key = arg.slice(2);
|
|
45
63
|
const next = process.argv[i + 1];
|
|
@@ -58,6 +76,118 @@ function ask(rl, question) {
|
|
|
58
76
|
return new Promise(resolve => rl.question(question, resolve));
|
|
59
77
|
}
|
|
60
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Copy agent files to ~/.knoxis/agent/ so they persist across npx cache clears,
|
|
81
|
+
* VPN changes, and reboots. Returns the stable path.
|
|
82
|
+
*/
|
|
83
|
+
function installAgentLocally(force) {
|
|
84
|
+
const sourceAgent = path.join(__dirname, '..', 'lib', 'knoxis-local-agent.js');
|
|
85
|
+
const sourcePairProgram = path.join(__dirname, '..', 'lib', 'knoxis-pair-program.js');
|
|
86
|
+
const sourcePackage = path.join(__dirname, '..', 'package.json');
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(sourceAgent)) {
|
|
89
|
+
return null; // Source not available (shouldn't happen)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if already installed and up to date
|
|
93
|
+
if (!force && fs.existsSync(STABLE_AGENT_PATH)) {
|
|
94
|
+
try {
|
|
95
|
+
const installedPkg = path.join(AGENT_DIR, 'package.json');
|
|
96
|
+
if (fs.existsSync(installedPkg) && fs.existsSync(sourcePackage)) {
|
|
97
|
+
const installed = JSON.parse(fs.readFileSync(installedPkg, 'utf8'));
|
|
98
|
+
const source = JSON.parse(fs.readFileSync(sourcePackage, 'utf8'));
|
|
99
|
+
if (installed.version === source.version) {
|
|
100
|
+
return STABLE_AGENT_PATH; // Already up to date
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Fall through to install
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Copy files to stable location
|
|
109
|
+
if (!fs.existsSync(AGENT_DIR)) {
|
|
110
|
+
fs.mkdirSync(AGENT_DIR, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fs.copyFileSync(sourceAgent, STABLE_AGENT_PATH);
|
|
114
|
+
console.log(' Installed: knoxis-local-agent.js');
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(sourcePairProgram)) {
|
|
117
|
+
fs.copyFileSync(sourcePairProgram, path.join(AGENT_DIR, 'knoxis-pair-program.js'));
|
|
118
|
+
console.log(' Installed: knoxis-pair-program.js');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (fs.existsSync(sourcePackage)) {
|
|
122
|
+
fs.copyFileSync(sourcePackage, path.join(AGENT_DIR, 'package.json'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return STABLE_AGENT_PATH;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if the backend is reachable (helps diagnose VPN issues)
|
|
130
|
+
*/
|
|
131
|
+
function checkConnectivity(backendUrl) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
// Convert wss:// to https:// for health check
|
|
134
|
+
let healthUrl = backendUrl
|
|
135
|
+
.replace('wss://', 'https://')
|
|
136
|
+
.replace('ws://', 'http://');
|
|
137
|
+
// Strip path and add /health (using the local agent's health, not backend)
|
|
138
|
+
// Actually check the backend WebSocket endpoint reachability
|
|
139
|
+
if (!healthUrl.endsWith('/')) healthUrl += '/';
|
|
140
|
+
|
|
141
|
+
const client = healthUrl.startsWith('https') ? https : http;
|
|
142
|
+
const req = client.get(healthUrl, { rejectUnauthorized: false, timeout: 5000 }, (res) => {
|
|
143
|
+
resolve({ reachable: true, statusCode: res.statusCode });
|
|
144
|
+
});
|
|
145
|
+
req.on('error', (err) => {
|
|
146
|
+
resolve({ reachable: false, error: err.code || err.message });
|
|
147
|
+
});
|
|
148
|
+
req.on('timeout', () => {
|
|
149
|
+
req.destroy();
|
|
150
|
+
resolve({ reachable: false, error: 'TIMEOUT' });
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if the local agent is already running
|
|
157
|
+
*/
|
|
158
|
+
function checkLocalAgent(port) {
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
const req = https.get(`https://localhost:${port || 3456}/health`, { rejectUnauthorized: false, timeout: 3000 }, (res) => {
|
|
161
|
+
let data = '';
|
|
162
|
+
res.on('data', chunk => { data += chunk; });
|
|
163
|
+
res.on('end', () => {
|
|
164
|
+
try {
|
|
165
|
+
resolve({ running: true, ...JSON.parse(data) });
|
|
166
|
+
} catch (e) {
|
|
167
|
+
resolve({ running: true, raw: data });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
req.on('error', () => {
|
|
172
|
+
// Try HTTP fallback
|
|
173
|
+
const req2 = http.get(`http://localhost:${port || 3456}/health`, { timeout: 3000 }, (res) => {
|
|
174
|
+
let data = '';
|
|
175
|
+
res.on('data', chunk => { data += chunk; });
|
|
176
|
+
res.on('end', () => {
|
|
177
|
+
try {
|
|
178
|
+
resolve({ running: true, ...JSON.parse(data) });
|
|
179
|
+
} catch (e) {
|
|
180
|
+
resolve({ running: true, raw: data });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
req2.on('error', () => resolve({ running: false }));
|
|
185
|
+
req2.on('timeout', () => { req2.destroy(); resolve({ running: false }); });
|
|
186
|
+
});
|
|
187
|
+
req.on('timeout', () => { req.destroy(); resolve({ running: false }); });
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
61
191
|
async function firstTimeSetup(args) {
|
|
62
192
|
const config = loadConfig();
|
|
63
193
|
|
|
@@ -119,9 +249,37 @@ async function firstTimeSetup(args) {
|
|
|
119
249
|
return config;
|
|
120
250
|
}
|
|
121
251
|
|
|
252
|
+
function uninstall() {
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(' Knoxis Helper - Uninstall');
|
|
255
|
+
console.log(' =========================');
|
|
256
|
+
|
|
257
|
+
// Unload and remove LaunchAgent
|
|
258
|
+
if (process.platform === 'darwin' && fs.existsSync(PLIST_PATH)) {
|
|
259
|
+
spawnSync('launchctl', ['unload', PLIST_PATH]);
|
|
260
|
+
fs.unlinkSync(PLIST_PATH);
|
|
261
|
+
console.log(' Removed LaunchAgent');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Remove agent files
|
|
265
|
+
if (fs.existsSync(AGENT_DIR)) {
|
|
266
|
+
fs.rmSync(AGENT_DIR, { recursive: true, force: true });
|
|
267
|
+
console.log(' Removed ~/.knoxis/agent/');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log(' Config preserved at ~/.knoxis/config.json (use --reset to clear)');
|
|
271
|
+
console.log('');
|
|
272
|
+
}
|
|
273
|
+
|
|
122
274
|
async function main() {
|
|
123
275
|
const args = parseArgs();
|
|
124
276
|
|
|
277
|
+
// Uninstall
|
|
278
|
+
if (args.uninstall) {
|
|
279
|
+
uninstall();
|
|
280
|
+
process.exit(0);
|
|
281
|
+
}
|
|
282
|
+
|
|
125
283
|
// Reset
|
|
126
284
|
if (args.reset) {
|
|
127
285
|
try { fs.unlinkSync(CONFIG_FILE); } catch (e) {}
|
|
@@ -136,9 +294,83 @@ async function main() {
|
|
|
136
294
|
console.log('Not configured. Run: npx knoxis-helper');
|
|
137
295
|
} else {
|
|
138
296
|
console.log('Configured:');
|
|
139
|
-
console.log(` Backend:
|
|
140
|
-
console.log(` User:
|
|
297
|
+
console.log(` Backend: ${config.backendWsUrl}`);
|
|
298
|
+
console.log(` User: ${config.userId}`);
|
|
299
|
+
console.log(` Installed: ${fs.existsSync(STABLE_AGENT_PATH) ? STABLE_AGENT_PATH : 'NOT INSTALLED (run npx knoxis-helper --update)'}`);
|
|
300
|
+
|
|
301
|
+
const local = await checkLocalAgent();
|
|
302
|
+
console.log(` Running: ${local.running ? 'YES (v' + (local.version || '?') + ', ' + (local.secure ? 'HTTPS' : 'HTTP') + ')' : 'NO'}`);
|
|
303
|
+
}
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Connectivity check
|
|
308
|
+
if (args.check) {
|
|
309
|
+
const config = loadConfig();
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(' Knoxis Helper - Connectivity Check');
|
|
312
|
+
console.log(' ==================================');
|
|
313
|
+
console.log('');
|
|
314
|
+
|
|
315
|
+
// Check local agent
|
|
316
|
+
const local = await checkLocalAgent();
|
|
317
|
+
if (local.running) {
|
|
318
|
+
console.log(` Local agent: RUNNING (v${local.version || '?'}, ${local.secure ? 'HTTPS' : 'HTTP'})`);
|
|
319
|
+
} else {
|
|
320
|
+
console.log(' Local agent: NOT RUNNING');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check stable install
|
|
324
|
+
console.log(` Installed at: ${fs.existsSync(STABLE_AGENT_PATH) ? STABLE_AGENT_PATH : 'NOT INSTALLED'}`);
|
|
325
|
+
|
|
326
|
+
// Check LaunchAgent
|
|
327
|
+
if (process.platform === 'darwin') {
|
|
328
|
+
console.log(` LaunchAgent: ${fs.existsSync(PLIST_PATH) ? 'CONFIGURED' : 'NOT CONFIGURED'}`);
|
|
141
329
|
}
|
|
330
|
+
|
|
331
|
+
// Check backend reachability
|
|
332
|
+
if (config.backendWsUrl) {
|
|
333
|
+
console.log(` Backend URL: ${config.backendWsUrl}`);
|
|
334
|
+
const result = await checkConnectivity(config.backendWsUrl);
|
|
335
|
+
if (result.reachable) {
|
|
336
|
+
console.log(` Backend: REACHABLE (HTTP ${result.statusCode})`);
|
|
337
|
+
} else {
|
|
338
|
+
console.log(` Backend: UNREACHABLE (${result.error})`);
|
|
339
|
+
console.log('');
|
|
340
|
+
if (result.error === 'TIMEOUT' || result.error === 'ECONNREFUSED') {
|
|
341
|
+
console.log(' This is likely a VPN issue. The backend is not reachable from this network.');
|
|
342
|
+
console.log(' The local agent will still work for direct API calls (localhost:3456).');
|
|
343
|
+
console.log(' WebSocket relay to backend will reconnect automatically when VPN allows it.');
|
|
344
|
+
} else {
|
|
345
|
+
console.log(` Network error: ${result.error}`);
|
|
346
|
+
console.log(' Check your VPN settings or try: curl -k https://voice-app-v2.graymushroom-5a5599fe.centralus.azurecontainerapps.io/');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
console.log(' Backend: NOT CONFIGURED');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check npm registry (the actual cause of "reinstall" prompts)
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(' Checking npm registry access...');
|
|
356
|
+
const npmResult = await checkConnectivity('https://registry.npmjs.org/knoxis-helper');
|
|
357
|
+
if (npmResult.reachable) {
|
|
358
|
+
console.log(' npm registry: REACHABLE');
|
|
359
|
+
} else {
|
|
360
|
+
console.log(` npm registry: UNREACHABLE (${npmResult.error})`);
|
|
361
|
+
console.log('');
|
|
362
|
+
console.log(' This is why npx keeps prompting to reinstall!');
|
|
363
|
+
console.log(' When npm registry is unreachable (VPN), npx can\'t verify its cache.');
|
|
364
|
+
console.log(' Fix: The local install at ~/.knoxis/agent/ bypasses this entirely.');
|
|
365
|
+
if (!fs.existsSync(STABLE_AGENT_PATH)) {
|
|
366
|
+
console.log(' Run: npx knoxis-helper (while OFF VPN to do initial install)');
|
|
367
|
+
} else {
|
|
368
|
+
console.log(' Your local install is fine. Run directly with:');
|
|
369
|
+
console.log(` node ${STABLE_AGENT_PATH}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log('');
|
|
142
374
|
process.exit(0);
|
|
143
375
|
}
|
|
144
376
|
|
|
@@ -155,13 +387,24 @@ async function main() {
|
|
|
155
387
|
console.log('');
|
|
156
388
|
}
|
|
157
389
|
|
|
390
|
+
// Install agent files to stable path (~/.knoxis/agent/)
|
|
391
|
+
// This is the key fix: npx cache paths are ephemeral, this path is permanent
|
|
392
|
+
const stableAgent = installAgentLocally(!!args.update);
|
|
393
|
+
if (stableAgent) {
|
|
394
|
+
console.log(` Agent installed at: ${stableAgent}`);
|
|
395
|
+
console.log(' (This path survives VPN changes, npx cache clears, and reboots)');
|
|
396
|
+
console.log('');
|
|
397
|
+
}
|
|
398
|
+
|
|
158
399
|
// Set environment variables for the local agent
|
|
159
400
|
process.env.KNOXIS_BACKEND_WS_URL = config.backendWsUrl;
|
|
160
401
|
process.env.KNOXIS_USER_ID = config.userId;
|
|
402
|
+
// Tell the agent to use the stable path for its LaunchAgent plist
|
|
403
|
+
process.env.KNOXIS_STABLE_AGENT_PATH = STABLE_AGENT_PATH;
|
|
161
404
|
|
|
162
|
-
//
|
|
163
|
-
// Try to find it in common locations
|
|
405
|
+
// Determine which agent to load: prefer stable install, fall back to package location
|
|
164
406
|
const agentLocations = [
|
|
407
|
+
STABLE_AGENT_PATH,
|
|
165
408
|
path.join(__dirname, '..', 'lib', 'knoxis-local-agent.js'),
|
|
166
409
|
path.join(__dirname, '..', '..', 'knoxis-local-agent.js'),
|
|
167
410
|
path.join(process.cwd(), 'scripts', 'knoxis-local-agent.js'),
|
|
@@ -446,7 +446,7 @@ async function handleRequest(req, res) {
|
|
|
446
446
|
status: 'healthy',
|
|
447
447
|
platform: os.platform(),
|
|
448
448
|
agent: 'knoxis-local-agent',
|
|
449
|
-
version: '2.
|
|
449
|
+
version: '2.2.0-stable',
|
|
450
450
|
secure: serverMeta.secure,
|
|
451
451
|
port: serverMeta.port,
|
|
452
452
|
dependencies: 'none',
|
|
@@ -864,7 +864,13 @@ function ensureLaunchAgentIfNeeded() {
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
const plistPath = path.join(agentsDir, 'com.knoxis.helper.plist');
|
|
867
|
-
|
|
867
|
+
|
|
868
|
+
// Use the stable path (~/.knoxis/agent/) if available, NOT the ephemeral npx cache path.
|
|
869
|
+
// This is critical: npx cache paths change on every download, breaking the LaunchAgent.
|
|
870
|
+
const stableAgentPath = process.env.KNOXIS_STABLE_AGENT_PATH
|
|
871
|
+
|| path.join(os.homedir(), '.knoxis', 'agent', 'knoxis-local-agent.js');
|
|
872
|
+
const agentScript = fs.existsSync(stableAgentPath) ? stableAgentPath : __filename;
|
|
873
|
+
const programArgs = [process.execPath, agentScript];
|
|
868
874
|
|
|
869
875
|
const logDir = path.join(os.homedir(), 'Library', 'Logs');
|
|
870
876
|
if (!fs.existsSync(logDir)) {
|
|
@@ -904,7 +910,7 @@ function ensureLaunchAgentIfNeeded() {
|
|
|
904
910
|
spawnSync('launchctl', ['unload', plistPath]);
|
|
905
911
|
const load = spawnSync('launchctl', ['load', '-w', plistPath]);
|
|
906
912
|
if (load.status === 0) {
|
|
907
|
-
console.log(
|
|
913
|
+
console.log(`🛠️ Installed launch agent (pointing to ${agentScript})`);
|
|
908
914
|
} else {
|
|
909
915
|
console.warn('⚠️ Unable to automatically load launch agent. Run:');
|
|
910
916
|
console.warn(` launchctl load -w ${plistPath}`);
|
|
@@ -934,10 +940,12 @@ function loadConfigFallback() {
|
|
|
934
940
|
const _config = loadConfigFallback();
|
|
935
941
|
const BACKEND_WS_URL = process.env.KNOXIS_BACKEND_WS_URL || _config.backendWsUrl || null;
|
|
936
942
|
const RELAY_USER_ID = process.env.KNOXIS_USER_ID || _config.userId || null;
|
|
937
|
-
const
|
|
943
|
+
const RELAY_RECONNECT_BASE_MS = parseInt(process.env.KNOXIS_RECONNECT_MS || '2000', 10);
|
|
944
|
+
const RELAY_RECONNECT_MAX_MS = 60000; // Cap at 60s
|
|
938
945
|
|
|
939
946
|
let relaySocket = null;
|
|
940
947
|
let relayReconnectTimer = null;
|
|
948
|
+
let relayReconnectAttempts = 0;
|
|
941
949
|
|
|
942
950
|
function connectRelayWebSocket() {
|
|
943
951
|
if (!BACKEND_WS_URL || !RELAY_USER_ID) {
|
|
@@ -972,6 +980,7 @@ function connectRelayWebSocket() {
|
|
|
972
980
|
|
|
973
981
|
relaySocket.onopen = () => {
|
|
974
982
|
console.log('✅ Relay WebSocket connected to backend');
|
|
983
|
+
relayReconnectAttempts = 0; // Reset backoff on successful connection
|
|
975
984
|
if (relayReconnectTimer) {
|
|
976
985
|
clearTimeout(relayReconnectTimer);
|
|
977
986
|
relayReconnectTimer = null;
|
|
@@ -1036,9 +1045,16 @@ function connectRelayWebSocket() {
|
|
|
1036
1045
|
};
|
|
1037
1046
|
|
|
1038
1047
|
relaySocket.onclose = (event) => {
|
|
1039
|
-
|
|
1048
|
+
const code = event.code || 'unknown';
|
|
1049
|
+
console.log(`🔌 Relay WebSocket closed (code: ${code})`);
|
|
1040
1050
|
relaySocket = null;
|
|
1041
|
-
|
|
1051
|
+
// Abnormal closure (1006) or going away (1001) — reconnect immediately
|
|
1052
|
+
if (code === 1006 || code === 1001) {
|
|
1053
|
+
console.log('⚡ Unexpected close — reconnecting immediately...');
|
|
1054
|
+
setTimeout(() => connectRelayWebSocket(), 500);
|
|
1055
|
+
} else {
|
|
1056
|
+
scheduleRelayReconnect();
|
|
1057
|
+
}
|
|
1042
1058
|
};
|
|
1043
1059
|
|
|
1044
1060
|
relaySocket.onerror = (err) => {
|
|
@@ -1051,11 +1067,24 @@ function scheduleRelayReconnect() {
|
|
|
1051
1067
|
if (relayReconnectTimer) return;
|
|
1052
1068
|
if (!BACKEND_WS_URL || !RELAY_USER_ID) return;
|
|
1053
1069
|
|
|
1054
|
-
|
|
1070
|
+
// Exponential backoff: 2s, 4s, 8s, 16s, 32s, 60s (capped)
|
|
1071
|
+
// This prevents log spam when VPN blocks the backend
|
|
1072
|
+
relayReconnectAttempts++;
|
|
1073
|
+
const delay = Math.min(RELAY_RECONNECT_BASE_MS * Math.pow(2, relayReconnectAttempts - 1), RELAY_RECONNECT_MAX_MS);
|
|
1074
|
+
const delaySec = (delay / 1000).toFixed(0);
|
|
1075
|
+
|
|
1076
|
+
if (relayReconnectAttempts <= 3) {
|
|
1077
|
+
console.log(`⏳ Relay reconnecting in ${delaySec}s... (attempt ${relayReconnectAttempts})`);
|
|
1078
|
+
} else if (relayReconnectAttempts === 4) {
|
|
1079
|
+
console.log(`⏳ Relay reconnecting in ${delaySec}s... (backend may be unreachable — VPN?)`);
|
|
1080
|
+
console.log(' Local agent (localhost:3456) still works. Relay will keep retrying quietly.');
|
|
1081
|
+
}
|
|
1082
|
+
// After attempt 4, reconnect silently to avoid log noise
|
|
1083
|
+
|
|
1055
1084
|
relayReconnectTimer = setTimeout(() => {
|
|
1056
1085
|
relayReconnectTimer = null;
|
|
1057
1086
|
connectRelayWebSocket();
|
|
1058
|
-
},
|
|
1087
|
+
}, delay);
|
|
1059
1088
|
}
|
|
1060
1089
|
|
|
1061
1090
|
// Send heartbeat to keep relay alive
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knoxis-helper",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Local helper for Knoxis pair programming - connects your machine to Knoxis on qig.ai",
|
|
5
5
|
"bin": {
|
|
6
6
|
"knoxis-helper": "./bin/knoxis-helper.js"
|
|
7
7
|
},
|
|
8
|
-
"keywords": [
|
|
8
|
+
"keywords": [
|
|
9
|
+
"knoxis",
|
|
10
|
+
"pair-programming",
|
|
11
|
+
"claude",
|
|
12
|
+
"qig"
|
|
13
|
+
],
|
|
9
14
|
"license": "MIT",
|
|
10
15
|
"engines": {
|
|
11
16
|
"node": ">=18"
|