@agent-webui/ai-desk-daemon 1.0.61 → 1.0.62-beta1
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 +79 -2
- package/lib/daemon-registry.js +392 -0
- package/package.json +12 -12
package/bin/cli.js
CHANGED
|
@@ -11,6 +11,13 @@ 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');
|
|
14
21
|
const { upgradePackage } = require('../lib/self-upgrade');
|
|
15
22
|
|
|
16
23
|
function wait(ms) {
|
|
@@ -128,11 +135,74 @@ function configureRequestedMode(mode) {
|
|
|
128
135
|
});
|
|
129
136
|
}
|
|
130
137
|
|
|
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
|
+
|
|
131
157
|
program
|
|
132
158
|
.name('aidesk')
|
|
133
159
|
.description('AI Desk Daemon - CLI tool for managing the AI Desk daemon service')
|
|
134
160
|
.version(VERSION);
|
|
135
161
|
|
|
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
|
+
|
|
136
206
|
// Start command
|
|
137
207
|
program
|
|
138
208
|
.command('start')
|
|
@@ -145,7 +215,9 @@ program
|
|
|
145
215
|
try {
|
|
146
216
|
const mode = resolveRequestedMode(options) || 'native';
|
|
147
217
|
const modeResult = configureRequestedMode(mode);
|
|
218
|
+
syncRegistryConfigForDaemonStart();
|
|
148
219
|
start();
|
|
220
|
+
await reportRegistryLifecycle('start');
|
|
149
221
|
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
150
222
|
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
151
223
|
} else if (mode === 'native') {
|
|
@@ -171,7 +243,7 @@ program
|
|
|
171
243
|
const tail = startUnixLogFollow(logPath);
|
|
172
244
|
|
|
173
245
|
// Handle Ctrl+C - stop the daemon
|
|
174
|
-
process.on('SIGINT', () => {
|
|
246
|
+
process.on('SIGINT', async () => {
|
|
175
247
|
console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
|
|
176
248
|
tail.kill();
|
|
177
249
|
|
|
@@ -179,6 +251,7 @@ program
|
|
|
179
251
|
// Stop the daemon
|
|
180
252
|
const { stop } = require('../lib/daemon-manager');
|
|
181
253
|
stop();
|
|
254
|
+
await reportRegistryLifecycle('stop');
|
|
182
255
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
183
256
|
} catch (error) {
|
|
184
257
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -192,7 +265,7 @@ program
|
|
|
192
265
|
|
|
193
266
|
const pollInterval = startWindowsLogFollow(logPath);
|
|
194
267
|
|
|
195
|
-
process.on('SIGINT', () => {
|
|
268
|
+
process.on('SIGINT', async () => {
|
|
196
269
|
clearInterval(pollInterval);
|
|
197
270
|
console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
|
|
198
271
|
|
|
@@ -200,6 +273,7 @@ program
|
|
|
200
273
|
// Stop the daemon
|
|
201
274
|
const { stop } = require('../lib/daemon-manager');
|
|
202
275
|
stop();
|
|
276
|
+
await reportRegistryLifecycle('stop');
|
|
203
277
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
204
278
|
} catch (error) {
|
|
205
279
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -222,6 +296,7 @@ program
|
|
|
222
296
|
.action(async () => {
|
|
223
297
|
try {
|
|
224
298
|
stop();
|
|
299
|
+
await reportRegistryLifecycle('stop');
|
|
225
300
|
console.log(chalk.green('✓ Daemon stopped successfully'));
|
|
226
301
|
} catch (error) {
|
|
227
302
|
console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
|
|
@@ -240,7 +315,9 @@ program
|
|
|
240
315
|
try {
|
|
241
316
|
const mode = resolveRequestedMode(options);
|
|
242
317
|
const modeResult = configureRequestedMode(mode);
|
|
318
|
+
syncRegistryConfigForDaemonStart();
|
|
243
319
|
restart();
|
|
320
|
+
await reportRegistryLifecycle('restart');
|
|
244
321
|
if (mode === 'cli-anything' && modeResult?.runtimeInfo?.cliAnythingPath) {
|
|
245
322
|
console.log(chalk.cyan(`CLI-Anything mode configured: ${modeResult.runtimeInfo.cliAnythingPath}`));
|
|
246
323
|
} else if (mode === 'native') {
|
|
@@ -0,0 +1,392 @@
|
|
|
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
|
+
};
|
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-beta1",
|
|
4
4
|
"description": "AI Desk Daemon - CLI tool for managing the AI Desk daemon service",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/*"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"postinstall": "node scripts/postinstall.js",
|
|
14
14
|
"prepare:packages": "node scripts/prepare-npm-packages.js",
|
|
15
15
|
"publish:npm:workspaces": "node scripts/publish-npm-packages.js",
|
|
16
|
-
"test": "node bin/cli.js --help
|
|
16
|
+
"test": "node bin/cli.js --help"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"ai-desk",
|
|
@@ -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-beta1",
|
|
43
|
+
"@agent-webui/ai-desk-daemon-darwin-x64": "1.0.62-beta1",
|
|
44
|
+
"@agent-webui/ai-desk-daemon-linux-arm64": "1.0.62-beta1",
|
|
45
|
+
"@agent-webui/ai-desk-daemon-linux-x64": "1.0.62-beta1",
|
|
46
|
+
"@agent-webui/ai-desk-daemon-win32-x64": "1.0.62-beta1",
|
|
47
|
+
"@agent-webui/ai-desk-python-darwin-arm64": "1.0.62-beta1",
|
|
48
|
+
"@agent-webui/ai-desk-python-darwin-x64": "1.0.62-beta1",
|
|
49
|
+
"@agent-webui/ai-desk-python-linux-arm64": "1.0.62-beta1",
|
|
50
|
+
"@agent-webui/ai-desk-python-linux-x64": "1.0.62-beta1",
|
|
51
|
+
"@agent-webui/ai-desk-python-win32-x64": "1.0.62-beta1"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|