@agent-webui/ai-desk-daemon 1.0.61-beta7 → 1.0.62
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 +2 -79
- package/package.json +11 -11
- package/lib/daemon-registry.js +0 -392
package/bin/cli.js
CHANGED
|
@@ -11,13 +11,6 @@ const path = require('path');
|
|
|
11
11
|
const { start, stop, restart, status } = require('../lib/daemon-manager');
|
|
12
12
|
const { getLogPath } = require('../lib/platform');
|
|
13
13
|
const { VERSION } = require('../lib/platform');
|
|
14
|
-
const { getPort } = require('../lib/config');
|
|
15
|
-
const {
|
|
16
|
-
configureRegistryUrl,
|
|
17
|
-
getRegistryConfig,
|
|
18
|
-
reportLifecycle,
|
|
19
|
-
syncRegistryConfigToDaemonConfig,
|
|
20
|
-
} = require('../lib/daemon-registry');
|
|
21
14
|
const { upgradePackage } = require('../lib/self-upgrade');
|
|
22
15
|
|
|
23
16
|
function wait(ms) {
|
|
@@ -135,74 +128,11 @@ function configureRequestedMode(mode) {
|
|
|
135
128
|
});
|
|
136
129
|
}
|
|
137
130
|
|
|
138
|
-
async function reportRegistryLifecycle(event) {
|
|
139
|
-
try {
|
|
140
|
-
await reportLifecycle(event, {
|
|
141
|
-
port: Number(getPort()),
|
|
142
|
-
version: VERSION,
|
|
143
|
-
});
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.warn(chalk.yellow(`[registry] Failed to report daemon ${event}: ${error.message || error}`));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function syncRegistryConfigForDaemonStart() {
|
|
150
|
-
try {
|
|
151
|
-
syncRegistryConfigToDaemonConfig();
|
|
152
|
-
} catch (error) {
|
|
153
|
-
console.warn(chalk.yellow(`[registry] Failed to sync daemon registry config: ${error.message || error}`));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
131
|
program
|
|
158
132
|
.name('aidesk')
|
|
159
133
|
.description('AI Desk Daemon - CLI tool for managing the AI Desk daemon service')
|
|
160
134
|
.version(VERSION);
|
|
161
135
|
|
|
162
|
-
const registryCommand = program
|
|
163
|
-
.command('registry')
|
|
164
|
-
.description('Manage shared runtime registry backend settings');
|
|
165
|
-
|
|
166
|
-
registryCommand
|
|
167
|
-
.command('set-url <url>')
|
|
168
|
-
.description('Persist the AI Desk backend URL used for daemon registry reporting')
|
|
169
|
-
.option('--insecure-skip-tls-verify', 'Skip TLS certificate verification for this registry URL')
|
|
170
|
-
.option('--strict-tls', 'Require strict TLS certificate verification for this registry URL')
|
|
171
|
-
.action((url, options) => {
|
|
172
|
-
try {
|
|
173
|
-
if (options.insecureSkipTlsVerify && options.strictTls) {
|
|
174
|
-
throw new Error('Use either --insecure-skip-tls-verify or --strict-tls, not both');
|
|
175
|
-
}
|
|
176
|
-
const tlsInsecureSkipVerify = options.insecureSkipTlsVerify
|
|
177
|
-
? true
|
|
178
|
-
: (options.strictTls ? false : undefined);
|
|
179
|
-
const config = configureRegistryUrl(url, { tlsInsecureSkipVerify });
|
|
180
|
-
console.log(chalk.green('✓ Registry URL updated'));
|
|
181
|
-
console.log(`API base URL: ${config.apiBaseUrl}`);
|
|
182
|
-
console.log(`TLS verification: ${config.tlsInsecureSkipVerify ? 'skip for this URL' : 'strict'}`);
|
|
183
|
-
console.log(chalk.cyan('Restart the daemon for the new registry URL to be used by background heartbeats.'));
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error(chalk.red('Failed to update registry URL:'), error.message);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
registryCommand
|
|
191
|
-
.command('status')
|
|
192
|
-
.description('Show the effective daemon registry backend settings')
|
|
193
|
-
.action(() => {
|
|
194
|
-
const config = getRegistryConfig();
|
|
195
|
-
if (!config) {
|
|
196
|
-
console.log(chalk.yellow('Registry reporting is disabled'));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
console.log(`API base URL: ${config.apiBaseUrl}`);
|
|
200
|
-
console.log(`RC account ID: ${config.rcAccountId || '(empty)'}`);
|
|
201
|
-
console.log(`RC extension ID: ${config.rcExtensionId || '(empty)'}`);
|
|
202
|
-
console.log(`RC username: ${config.rcUsername || '(empty)'}`);
|
|
203
|
-
console.log(`TLS verification: ${config.tlsInsecureSkipVerify ? 'skip for this URL' : 'strict'}`);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
136
|
// Start command
|
|
207
137
|
program
|
|
208
138
|
.command('start')
|
|
@@ -215,9 +145,7 @@ program
|
|
|
215
145
|
try {
|
|
216
146
|
const mode = resolveRequestedMode(options) || 'native';
|
|
217
147
|
const modeResult = configureRequestedMode(mode);
|
|
218
|
-
syncRegistryConfigForDaemonStart();
|
|
219
148
|
start();
|
|
220
|
-
await reportRegistryLifecycle('start');
|
|
221
149
|
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
222
150
|
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
223
151
|
} else if (mode === 'native') {
|
|
@@ -243,7 +171,7 @@ program
|
|
|
243
171
|
const tail = startUnixLogFollow(logPath);
|
|
244
172
|
|
|
245
173
|
// Handle Ctrl+C - stop the daemon
|
|
246
|
-
process.on('SIGINT',
|
|
174
|
+
process.on('SIGINT', () => {
|
|
247
175
|
console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
|
|
248
176
|
tail.kill();
|
|
249
177
|
|
|
@@ -251,7 +179,6 @@ program
|
|
|
251
179
|
// Stop the daemon
|
|
252
180
|
const { stop } = require('../lib/daemon-manager');
|
|
253
181
|
stop();
|
|
254
|
-
await reportRegistryLifecycle('stop');
|
|
255
182
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
256
183
|
} catch (error) {
|
|
257
184
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -265,7 +192,7 @@ program
|
|
|
265
192
|
|
|
266
193
|
const pollInterval = startWindowsLogFollow(logPath);
|
|
267
194
|
|
|
268
|
-
process.on('SIGINT',
|
|
195
|
+
process.on('SIGINT', () => {
|
|
269
196
|
clearInterval(pollInterval);
|
|
270
197
|
console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
|
|
271
198
|
|
|
@@ -273,7 +200,6 @@ program
|
|
|
273
200
|
// Stop the daemon
|
|
274
201
|
const { stop } = require('../lib/daemon-manager');
|
|
275
202
|
stop();
|
|
276
|
-
await reportRegistryLifecycle('stop');
|
|
277
203
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
278
204
|
} catch (error) {
|
|
279
205
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -296,7 +222,6 @@ program
|
|
|
296
222
|
.action(async () => {
|
|
297
223
|
try {
|
|
298
224
|
stop();
|
|
299
|
-
await reportRegistryLifecycle('stop');
|
|
300
225
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
301
226
|
} catch (error) {
|
|
302
227
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -315,9 +240,7 @@ program
|
|
|
315
240
|
try {
|
|
316
241
|
const mode = resolveRequestedMode(options);
|
|
317
242
|
const modeResult = configureRequestedMode(mode);
|
|
318
|
-
syncRegistryConfigForDaemonStart();
|
|
319
243
|
restart();
|
|
320
|
-
await reportRegistryLifecycle('restart');
|
|
321
244
|
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
322
245
|
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
323
246
|
} else if (mode === 'native') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-webui/ai-desk-daemon",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.62",
|
|
4
4
|
"description": "AI Desk Daemon - CLI tool for managing the AI Desk daemon service",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/*"
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
"chalk": "^4.1.2"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"@agent-webui/ai-desk-daemon-darwin-arm64": "1.0.
|
|
43
|
-
"@agent-webui/ai-desk-daemon-darwin-x64": "1.0.
|
|
44
|
-
"@agent-webui/ai-desk-daemon-linux-arm64": "1.0.
|
|
45
|
-
"@agent-webui/ai-desk-daemon-linux-x64": "1.0.
|
|
46
|
-
"@agent-webui/ai-desk-daemon-win32-x64": "1.0.
|
|
47
|
-
"@agent-webui/ai-desk-python-darwin-arm64": "1.0.
|
|
48
|
-
"@agent-webui/ai-desk-python-darwin-x64": "1.0.
|
|
49
|
-
"@agent-webui/ai-desk-python-linux-arm64": "1.0.
|
|
50
|
-
"@agent-webui/ai-desk-python-linux-x64": "1.0.
|
|
51
|
-
"@agent-webui/ai-desk-python-win32-x64": "1.0.
|
|
42
|
+
"@agent-webui/ai-desk-daemon-darwin-arm64": "1.0.62",
|
|
43
|
+
"@agent-webui/ai-desk-daemon-darwin-x64": "1.0.62",
|
|
44
|
+
"@agent-webui/ai-desk-daemon-linux-arm64": "1.0.62",
|
|
45
|
+
"@agent-webui/ai-desk-daemon-linux-x64": "1.0.62",
|
|
46
|
+
"@agent-webui/ai-desk-daemon-win32-x64": "1.0.62",
|
|
47
|
+
"@agent-webui/ai-desk-python-darwin-arm64": "1.0.62",
|
|
48
|
+
"@agent-webui/ai-desk-python-darwin-x64": "1.0.62",
|
|
49
|
+
"@agent-webui/ai-desk-python-linux-arm64": "1.0.62",
|
|
50
|
+
"@agent-webui/ai-desk-python-linux-x64": "1.0.62",
|
|
51
|
+
"@agent-webui/ai-desk-python-win32-x64": "1.0.62"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
package/lib/daemon-registry.js
DELETED
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const http = require('http');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const crypto = require('crypto');
|
|
7
|
-
|
|
8
|
-
const DEFAULT_REGISTRY_API_BASE_URL = 'https://desk.int.rclabenv.com/';
|
|
9
|
-
|
|
10
|
-
function trim(value) {
|
|
11
|
-
if (typeof value === 'string') return value.trim();
|
|
12
|
-
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
13
|
-
return '';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function normalizeApiBaseUrl(value) {
|
|
17
|
-
const raw = trim(value).replace(/\/+$/, '');
|
|
18
|
-
if (!raw) return '';
|
|
19
|
-
return raw.endsWith('/api/v1') ? raw : `${raw}/api/v1`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseTruthyEnv(value) {
|
|
23
|
-
const raw = trim(value).toLowerCase();
|
|
24
|
-
if (!raw) return null;
|
|
25
|
-
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isPrivateRegistryHost(hostname) {
|
|
29
|
-
const host = trim(hostname).toLowerCase();
|
|
30
|
-
return (
|
|
31
|
-
host === 'localhost' ||
|
|
32
|
-
host === '127.0.0.1' ||
|
|
33
|
-
host.endsWith('.local') ||
|
|
34
|
-
/^10\./.test(host) ||
|
|
35
|
-
/^192\.168\./.test(host) ||
|
|
36
|
-
/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function inferTLSInsecureSkipVerify(apiBaseUrl) {
|
|
41
|
-
try {
|
|
42
|
-
const url = new URL(normalizeApiBaseUrl(apiBaseUrl));
|
|
43
|
-
if (url.protocol !== 'https:') return false;
|
|
44
|
-
if (url.hostname === 'desk.int.rclabenv.com') return false;
|
|
45
|
-
return isPrivateRegistryHost(url.hostname);
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function readRegistrySessionIdentity(homeDir = os.homedir()) {
|
|
52
|
-
const sessionPath = path.join(homeDir, '.aidesktop', 'session.json');
|
|
53
|
-
try {
|
|
54
|
-
if (!fs.existsSync(sessionPath)) return null;
|
|
55
|
-
const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
|
|
56
|
-
if (!session || typeof session !== 'object') return null;
|
|
57
|
-
const apiBaseUrl = normalizeApiBaseUrl(session.api_base_url || session.apiBaseUrl);
|
|
58
|
-
const rcAccountId = trim(session.account_id || session.accountId);
|
|
59
|
-
const rcExtensionId = trim(session.extension_id || session.extensionId);
|
|
60
|
-
const rcUsername = trim(session.rc_username || session.rcUsername);
|
|
61
|
-
const tlsInsecureSkipVerify = Boolean(
|
|
62
|
-
session.tls_insecure_skip_verify || session.tlsInsecureSkipVerify,
|
|
63
|
-
);
|
|
64
|
-
if (!apiBaseUrl && !rcAccountId && !rcExtensionId && !rcUsername) return null;
|
|
65
|
-
return { apiBaseUrl, rcAccountId, rcExtensionId, rcUsername, tlsInsecureSkipVerify };
|
|
66
|
-
} catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function readRegistryConfigFromDaemonConfig(homeDir = os.homedir()) {
|
|
72
|
-
const configPath = path.join(homeDir, '.aidesktop', 'daemon-config.json');
|
|
73
|
-
try {
|
|
74
|
-
if (!fs.existsSync(configPath)) return null;
|
|
75
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
76
|
-
const registry = config && typeof config === 'object' ? config.registry : null;
|
|
77
|
-
if (!registry || typeof registry !== 'object') return null;
|
|
78
|
-
const sessionIdentity = readRegistrySessionIdentity(homeDir);
|
|
79
|
-
const apiBaseUrl = normalizeApiBaseUrl(
|
|
80
|
-
registry.api_base_url || registry.apiBaseUrl || DEFAULT_REGISTRY_API_BASE_URL,
|
|
81
|
-
);
|
|
82
|
-
const rcAccountId = trim(registry.rc_account_id || registry.rcAccountId) ||
|
|
83
|
-
sessionIdentity?.rcAccountId || '';
|
|
84
|
-
const rcExtensionId = trim(registry.rc_extension_id || registry.rcExtensionId) ||
|
|
85
|
-
sessionIdentity?.rcExtensionId || '';
|
|
86
|
-
if (!apiBaseUrl) return null;
|
|
87
|
-
return {
|
|
88
|
-
apiBaseUrl,
|
|
89
|
-
rcAccountId,
|
|
90
|
-
rcExtensionId,
|
|
91
|
-
rcUsername: trim(registry.rc_username || registry.rcUsername) || sessionIdentity?.rcUsername || '',
|
|
92
|
-
tlsInsecureSkipVerify: Boolean(registry.tls_insecure_skip_verify || registry.tlsInsecureSkipVerify),
|
|
93
|
-
};
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function getRegistryConfig(env = process.env, homeDir = os.homedir()) {
|
|
100
|
-
const daemonConfig = readRegistryConfigFromDaemonConfig(homeDir);
|
|
101
|
-
const sessionIdentity = readRegistrySessionIdentity(homeDir);
|
|
102
|
-
const envApiBaseUrl = normalizeApiBaseUrl(
|
|
103
|
-
env.AI_DESK_API_BASE_URL || env.AI_DESK_BACKEND_API_URL || env.AI_DESK_BACKEND_URL,
|
|
104
|
-
);
|
|
105
|
-
const apiBaseUrl = normalizeApiBaseUrl(
|
|
106
|
-
envApiBaseUrl ||
|
|
107
|
-
daemonConfig?.apiBaseUrl ||
|
|
108
|
-
sessionIdentity?.apiBaseUrl ||
|
|
109
|
-
DEFAULT_REGISTRY_API_BASE_URL,
|
|
110
|
-
);
|
|
111
|
-
const rcAccountId = trim(env.AI_DESK_RC_ACCOUNT_ID || env.rcAccountId) ||
|
|
112
|
-
sessionIdentity?.rcAccountId ||
|
|
113
|
-
daemonConfig?.rcAccountId ||
|
|
114
|
-
'';
|
|
115
|
-
const rcExtensionId = trim(env.AI_DESK_RC_EXTENSION_ID || env.rcExtensionId) ||
|
|
116
|
-
sessionIdentity?.rcExtensionId ||
|
|
117
|
-
daemonConfig?.rcExtensionId ||
|
|
118
|
-
'';
|
|
119
|
-
if (apiBaseUrl) {
|
|
120
|
-
const tlsEnvValue = parseTruthyEnv(env.AI_DESK_TLS_INSECURE_SKIP_VERIFY);
|
|
121
|
-
return {
|
|
122
|
-
apiBaseUrl,
|
|
123
|
-
rcAccountId,
|
|
124
|
-
rcExtensionId,
|
|
125
|
-
rcUsername: trim(env.AI_DESK_RC_USERNAME || env.rcUsername) ||
|
|
126
|
-
sessionIdentity?.rcUsername ||
|
|
127
|
-
daemonConfig?.rcUsername ||
|
|
128
|
-
'',
|
|
129
|
-
tlsInsecureSkipVerify: tlsEnvValue === null
|
|
130
|
-
? Boolean(daemonConfig?.tlsInsecureSkipVerify ?? sessionIdentity?.tlsInsecureSkipVerify ?? inferTLSInsecureSkipVerify(apiBaseUrl))
|
|
131
|
-
: tlsEnvValue,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function daemonConfigPath(homeDir = os.homedir()) {
|
|
138
|
-
return path.join(homeDir, '.aidesktop', 'daemon-config.json');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function syncRegistryConfigToDaemonConfig(env = process.env, homeDir = os.homedir()) {
|
|
142
|
-
const config = getRegistryConfig(env, homeDir);
|
|
143
|
-
if (!config) return null;
|
|
144
|
-
|
|
145
|
-
const configPath = daemonConfigPath(homeDir);
|
|
146
|
-
let daemonConfig = {};
|
|
147
|
-
try {
|
|
148
|
-
if (fs.existsSync(configPath)) {
|
|
149
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
150
|
-
if (parsed && typeof parsed === 'object') daemonConfig = parsed;
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
daemonConfig = {};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
daemonConfig.registry = {
|
|
157
|
-
...(daemonConfig.registry && typeof daemonConfig.registry === 'object' ? daemonConfig.registry : {}),
|
|
158
|
-
api_base_url: config.apiBaseUrl,
|
|
159
|
-
rc_account_id: config.rcAccountId,
|
|
160
|
-
rc_extension_id: config.rcExtensionId,
|
|
161
|
-
rc_username: config.rcUsername,
|
|
162
|
-
tls_insecure_skip_verify: Boolean(config.tlsInsecureSkipVerify),
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
166
|
-
fs.writeFileSync(configPath, `${JSON.stringify(daemonConfig, null, 2)}\n`, 'utf8');
|
|
167
|
-
return config;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function configureRegistryUrl(apiBaseUrl, options = {}) {
|
|
171
|
-
const homeDir = options.homeDir || os.homedir();
|
|
172
|
-
const normalizedApiBaseUrl = normalizeApiBaseUrl(apiBaseUrl);
|
|
173
|
-
if (!normalizedApiBaseUrl) {
|
|
174
|
-
throw new Error('Registry URL is required');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let parsedUrl;
|
|
178
|
-
try {
|
|
179
|
-
parsedUrl = new URL(normalizedApiBaseUrl);
|
|
180
|
-
} catch {
|
|
181
|
-
throw new Error(`Invalid registry URL: ${apiBaseUrl}`);
|
|
182
|
-
}
|
|
183
|
-
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
184
|
-
throw new Error(`Registry URL must use http or https: ${apiBaseUrl}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const configPath = daemonConfigPath(homeDir);
|
|
188
|
-
let daemonConfig = {};
|
|
189
|
-
try {
|
|
190
|
-
if (fs.existsSync(configPath)) {
|
|
191
|
-
const parsedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
192
|
-
if (parsedConfig && typeof parsedConfig === 'object') daemonConfig = parsedConfig;
|
|
193
|
-
}
|
|
194
|
-
} catch {
|
|
195
|
-
daemonConfig = {};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const tlsInsecureSkipVerify = typeof options.tlsInsecureSkipVerify === 'boolean'
|
|
199
|
-
? options.tlsInsecureSkipVerify
|
|
200
|
-
: inferTLSInsecureSkipVerify(normalizedApiBaseUrl);
|
|
201
|
-
|
|
202
|
-
daemonConfig.registry = {
|
|
203
|
-
...(daemonConfig.registry && typeof daemonConfig.registry === 'object' ? daemonConfig.registry : {}),
|
|
204
|
-
api_base_url: normalizedApiBaseUrl,
|
|
205
|
-
tls_insecure_skip_verify: tlsInsecureSkipVerify,
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
209
|
-
fs.writeFileSync(configPath, `${JSON.stringify(daemonConfig, null, 2)}\n`, 'utf8');
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
apiBaseUrl: normalizedApiBaseUrl,
|
|
213
|
-
tlsInsecureSkipVerify,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function registryStatePaths(homeDir = os.homedir()) {
|
|
218
|
-
const root = path.join(homeDir, '.aidesktop');
|
|
219
|
-
return {
|
|
220
|
-
root,
|
|
221
|
-
instanceIdPath: path.join(root, 'daemon-instance-id'),
|
|
222
|
-
runStatePath: path.join(root, 'daemon-run-state.json'),
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function ensureDaemonInstanceId(paths = registryStatePaths()) {
|
|
227
|
-
try {
|
|
228
|
-
fs.mkdirSync(paths.root, { recursive: true });
|
|
229
|
-
if (fs.existsSync(paths.instanceIdPath)) {
|
|
230
|
-
const existing = fs.readFileSync(paths.instanceIdPath, 'utf8').trim();
|
|
231
|
-
if (existing) return existing;
|
|
232
|
-
}
|
|
233
|
-
const next = `daemon-${crypto.randomUUID()}`;
|
|
234
|
-
fs.writeFileSync(paths.instanceIdPath, `${next}\n`, 'utf8');
|
|
235
|
-
return next;
|
|
236
|
-
} catch {
|
|
237
|
-
return `daemon-${crypto.randomUUID()}`;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function readRunState(paths = registryStatePaths()) {
|
|
242
|
-
try {
|
|
243
|
-
if (!fs.existsSync(paths.runStatePath)) return null;
|
|
244
|
-
const data = JSON.parse(fs.readFileSync(paths.runStatePath, 'utf8'));
|
|
245
|
-
return data && typeof data === 'object' ? data : null;
|
|
246
|
-
} catch {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function writeRunState(state, paths = registryStatePaths()) {
|
|
252
|
-
try {
|
|
253
|
-
fs.mkdirSync(paths.root, { recursive: true });
|
|
254
|
-
fs.writeFileSync(paths.runStatePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
255
|
-
} catch {
|
|
256
|
-
// best effort only
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function listIpAddresses() {
|
|
261
|
-
const out = [];
|
|
262
|
-
const interfaces = os.networkInterfaces();
|
|
263
|
-
for (const items of Object.values(interfaces)) {
|
|
264
|
-
for (const item of items || []) {
|
|
265
|
-
if (!item.internal && item.address) {
|
|
266
|
-
out.push(item.address);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return [...new Set(out)];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function listMacAddresses() {
|
|
274
|
-
const out = [];
|
|
275
|
-
const interfaces = os.networkInterfaces();
|
|
276
|
-
for (const items of Object.values(interfaces)) {
|
|
277
|
-
for (const item of items || []) {
|
|
278
|
-
const mac = trim(item.mac).toLowerCase();
|
|
279
|
-
if (!item.internal && mac && mac !== '00:00:00:00:00:00') {
|
|
280
|
-
out.push(mac);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return [...new Set(out)];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function buildLifecyclePayload({
|
|
288
|
-
event,
|
|
289
|
-
runId,
|
|
290
|
-
seq = 1,
|
|
291
|
-
port,
|
|
292
|
-
version,
|
|
293
|
-
daemonInstanceId,
|
|
294
|
-
}) {
|
|
295
|
-
const macAddresses = listMacAddresses();
|
|
296
|
-
return {
|
|
297
|
-
daemonInstanceId,
|
|
298
|
-
runId,
|
|
299
|
-
event,
|
|
300
|
-
seq,
|
|
301
|
-
machineId: macAddresses[0] || daemonInstanceId,
|
|
302
|
-
hostname: os.hostname(),
|
|
303
|
-
osUsername: os.userInfo().username,
|
|
304
|
-
daemonVersion: version,
|
|
305
|
-
port,
|
|
306
|
-
ipAddresses: listIpAddresses(),
|
|
307
|
-
macAddresses,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function postJSON(urlString, payload, config) {
|
|
312
|
-
return new Promise((resolve, reject) => {
|
|
313
|
-
const url = new URL(urlString);
|
|
314
|
-
const body = JSON.stringify(payload);
|
|
315
|
-
const transport = url.protocol === 'https:' ? https : http;
|
|
316
|
-
const req = transport.request(
|
|
317
|
-
url,
|
|
318
|
-
{
|
|
319
|
-
method: 'POST',
|
|
320
|
-
...(url.protocol === 'https:' && config.tlsInsecureSkipVerify
|
|
321
|
-
? { agent: new https.Agent({ rejectUnauthorized: false }) }
|
|
322
|
-
: {}),
|
|
323
|
-
headers: {
|
|
324
|
-
'Content-Type': 'application/json',
|
|
325
|
-
'Content-Length': Buffer.byteLength(body),
|
|
326
|
-
...(config.rcAccountId ? { rcAccountId: config.rcAccountId } : {}),
|
|
327
|
-
...(config.rcExtensionId ? { rcExtensionId: config.rcExtensionId } : {}),
|
|
328
|
-
...(config.rcUsername ? { rcUsername: config.rcUsername } : {}),
|
|
329
|
-
},
|
|
330
|
-
timeout: 3000,
|
|
331
|
-
},
|
|
332
|
-
(res) => {
|
|
333
|
-
res.resume();
|
|
334
|
-
res.on('end', () => {
|
|
335
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
336
|
-
resolve(true);
|
|
337
|
-
} else {
|
|
338
|
-
reject(new Error(`registry returned ${res.statusCode}`));
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
},
|
|
342
|
-
);
|
|
343
|
-
req.on('timeout', () => {
|
|
344
|
-
req.destroy(new Error('registry request timed out'));
|
|
345
|
-
});
|
|
346
|
-
req.on('error', reject);
|
|
347
|
-
req.write(body);
|
|
348
|
-
req.end();
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function reportLifecycle(event, { port, version, seq = 1 } = {}) {
|
|
353
|
-
const config = getRegistryConfig();
|
|
354
|
-
if (!config) return false;
|
|
355
|
-
|
|
356
|
-
const paths = registryStatePaths();
|
|
357
|
-
const daemonInstanceId = ensureDaemonInstanceId(paths);
|
|
358
|
-
let runState = readRunState(paths);
|
|
359
|
-
if (event === 'start' || event === 'restart' || !runState?.runId) {
|
|
360
|
-
runState = { runId: crypto.randomUUID(), seq: 0 };
|
|
361
|
-
}
|
|
362
|
-
runState.seq = Math.max(Number(runState.seq || 0) + 1, seq);
|
|
363
|
-
writeRunState(runState, paths);
|
|
364
|
-
|
|
365
|
-
const payload = buildLifecyclePayload({
|
|
366
|
-
event,
|
|
367
|
-
runId: runState.runId,
|
|
368
|
-
seq: runState.seq,
|
|
369
|
-
port,
|
|
370
|
-
version,
|
|
371
|
-
daemonInstanceId,
|
|
372
|
-
});
|
|
373
|
-
await postJSON(`${config.apiBaseUrl}/daemon-instances/lifecycle`, payload, config);
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
module.exports = {
|
|
378
|
-
buildLifecyclePayload,
|
|
379
|
-
configureRegistryUrl,
|
|
380
|
-
DEFAULT_REGISTRY_API_BASE_URL,
|
|
381
|
-
inferTLSInsecureSkipVerify,
|
|
382
|
-
getRegistryConfig,
|
|
383
|
-
syncRegistryConfigToDaemonConfig,
|
|
384
|
-
readRegistrySessionIdentity,
|
|
385
|
-
readRegistryConfigFromDaemonConfig,
|
|
386
|
-
registryStatePaths,
|
|
387
|
-
ensureDaemonInstanceId,
|
|
388
|
-
readRunState,
|
|
389
|
-
writeRunState,
|
|
390
|
-
listMacAddresses,
|
|
391
|
-
reportLifecycle,
|
|
392
|
-
};
|