@agent-webui/ai-desk 1.0.65 → 1.0.67

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.
Files changed (2) hide show
  1. package/bin/aidesk.js +196 -37
  2. package/package.json +3 -3
package/bin/aidesk.js CHANGED
@@ -2,12 +2,19 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { spawnSync } = require('child_process');
5
+ const { spawn, spawnSync } = require('child_process');
6
6
 
7
7
  const PACKAGE_NAME = '@agent-webui/ai-desk';
8
8
  const SKIP_UPDATE_ENV = 'AI_DESK_SKIP_SELF_UPDATE';
9
9
  const UPDATE_COMMANDS = new Set(['start', 'restart', 'upgrade']);
10
- const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org/';
10
+ const DEFAULT_NPM_REGISTRIES = [
11
+ 'https://mirrors.cloud.tencent.com/npm/',
12
+ 'https://r.cnpmjs.org/',
13
+ 'https://mirrors.huaweicloud.com/repository/npm/',
14
+ 'https://nexus-xmn02.int.rclabenv.com/nexus/content/groups/npm-all/',
15
+ ];
16
+ const NPM_REGISTRIES_ENV = 'AI_DESK_NPM_REGISTRIES';
17
+ const NPM_REGISTRY_TIMEOUT_MS = 5000;
11
18
 
12
19
  function readJSON(filePath) {
13
20
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -69,43 +76,189 @@ function runNpm(args, options = {}) {
69
76
  });
70
77
  }
71
78
 
79
+ function runNpmAsync(args, options = {}) {
80
+ const npm = npmCommand();
81
+ const timeoutMs = options.timeoutMs || 0;
82
+
83
+ return new Promise((resolve) => {
84
+ const child = spawn(npm.command, [...npm.args, ...args], {
85
+ cwd: options.cwd,
86
+ env: process.env,
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ });
89
+ let stdout = '';
90
+ let stderr = '';
91
+ let settled = false;
92
+
93
+ const finish = (result) => {
94
+ if (settled) {
95
+ return;
96
+ }
97
+ settled = true;
98
+ if (timer) {
99
+ clearTimeout(timer);
100
+ }
101
+ resolve({
102
+ stdout,
103
+ stderr,
104
+ ...result,
105
+ });
106
+ };
107
+
108
+ const timer = timeoutMs > 0
109
+ ? setTimeout(() => {
110
+ child.kill();
111
+ finish({
112
+ status: null,
113
+ signal: 'SIGTERM',
114
+ error: new Error(`npm command timed out after ${timeoutMs}ms`),
115
+ });
116
+ }, timeoutMs)
117
+ : null;
118
+
119
+ child.stdout.on('data', (chunk) => {
120
+ stdout += chunk;
121
+ });
122
+ child.stderr.on('data', (chunk) => {
123
+ stderr += chunk;
124
+ });
125
+ child.on('error', (error) => {
126
+ finish({
127
+ status: null,
128
+ error,
129
+ });
130
+ });
131
+ child.on('close', (status, signal) => {
132
+ finish({
133
+ status,
134
+ signal,
135
+ });
136
+ });
137
+ });
138
+ }
139
+
72
140
  function npmOutput(result) {
73
141
  return `${result.stderr || ''}${result.stdout || ''}`.trim();
74
142
  }
75
143
 
76
- function withOfficialRegistry(args) {
77
- return [...args, `--registry=${OFFICIAL_NPM_REGISTRY}`];
144
+ function normalizeRegistryUrl(registry) {
145
+ const value = String(registry || '').trim();
146
+ if (!value) {
147
+ return '';
148
+ }
149
+ return value.endsWith('/') ? value : `${value}/`;
78
150
  }
79
151
 
80
- function runNpmWithRegistryFallback(args, options = {}) {
81
- const primaryResult = runNpm(withOfficialRegistry(args), options);
82
- if (primaryResult.status === 0) {
83
- return primaryResult;
84
- }
152
+ function configuredNpmRegistries() {
153
+ const configured = String(process.env[NPM_REGISTRIES_ENV] || '')
154
+ .split(',')
155
+ .map(normalizeRegistryUrl)
156
+ .filter(Boolean);
157
+ return Array.from(new Set([
158
+ ...DEFAULT_NPM_REGISTRIES,
159
+ ...configured,
160
+ ]));
161
+ }
85
162
 
86
- const fallbackResult = runNpm(args, options);
87
- if (fallbackResult.status === 0) {
88
- return fallbackResult;
163
+ function packageScope(packageName) {
164
+ const match = String(packageName || '').match(/^(@[^/]+)\//);
165
+ return match ? match[1] : '';
166
+ }
167
+
168
+ function registryArgsForPackage(packageName, registry) {
169
+ const normalizedRegistry = normalizeRegistryUrl(registry);
170
+ const args = [`--registry=${normalizedRegistry}`];
171
+ const scope = packageScope(packageName);
172
+ if (scope) {
173
+ args.push(`--${scope}:registry=${normalizedRegistry}`);
89
174
  }
175
+ return args;
176
+ }
90
177
 
91
- fallbackResult.primaryError = npmOutput(primaryResult);
92
- return fallbackResult;
178
+ function withPackageRegistry(args, packageName, registry) {
179
+ return [
180
+ ...args,
181
+ ...registryArgsForPackage(packageName, registry),
182
+ ];
93
183
  }
94
184
 
95
- function latestPublishedVersion() {
96
- const result = runNpmWithRegistryFallback(['view', PACKAGE_NAME, 'version'], {
97
- stdio: ['ignore', 'pipe', 'pipe'],
185
+ function timeoutError(registry, timeoutMs) {
186
+ return new Error(`npm registry ${registry} timed out after ${timeoutMs}ms`);
187
+ }
188
+
189
+ function withTimeout(promise, timeoutMs, error) {
190
+ let timer;
191
+ const timeout = new Promise((_, reject) => {
192
+ timer = setTimeout(() => reject(error), timeoutMs);
98
193
  });
99
194
 
195
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
196
+ }
197
+
198
+ async function latestVersionFromRegistry(registry) {
199
+ const normalizedRegistry = normalizeRegistryUrl(registry);
200
+ const args = withPackageRegistry(['view', PACKAGE_NAME, 'version'], PACKAGE_NAME, normalizedRegistry);
201
+ const result = await withTimeout(runNpmAsync(args, {
202
+ stdio: ['ignore', 'pipe', 'pipe'],
203
+ timeoutMs: NPM_REGISTRY_TIMEOUT_MS,
204
+ }), NPM_REGISTRY_TIMEOUT_MS, timeoutError(normalizedRegistry, NPM_REGISTRY_TIMEOUT_MS));
205
+
206
+ if (result.error) {
207
+ throw result.error;
208
+ }
100
209
  if (result.status !== 0) {
101
210
  const output = npmOutput(result);
102
- const primaryError = result.primaryError ? `Official registry error: ${result.primaryError}\n` : '';
103
- const fallbackError = output ? `Fallback registry error: ${output}` : '';
104
- const errorMessage = `${primaryError}${fallbackError}`.trim();
105
- throw new Error(errorMessage || 'Unable to check npm registry');
211
+ throw new Error(output || `Unable to check npm registry ${normalizedRegistry}`);
106
212
  }
107
213
 
108
- return String(result.stdout || '').trim();
214
+ const version = String(result.stdout || '').trim();
215
+ if (!version) {
216
+ throw new Error(`npm registry ${normalizedRegistry} returned an empty version`);
217
+ }
218
+ return version;
219
+ }
220
+
221
+ function chooseLatestPackageSource(currentBest, candidate) {
222
+ if (!currentBest) {
223
+ return candidate;
224
+ }
225
+
226
+ return compareVersions(candidate.version, currentBest.version) > 0 ? candidate : currentBest;
227
+ }
228
+
229
+ async function latestPublishedPackageSource() {
230
+ const checks = await Promise.all(configuredNpmRegistries().map(async (registry) => {
231
+ try {
232
+ return {
233
+ registry,
234
+ version: await latestVersionFromRegistry(registry),
235
+ };
236
+ } catch (error) {
237
+ return {
238
+ registry,
239
+ error,
240
+ };
241
+ }
242
+ }));
243
+
244
+ const errors = [];
245
+ let selected = null;
246
+ for (const check of checks) {
247
+ if (check.version) {
248
+ selected = chooseLatestPackageSource(selected, {
249
+ version: check.version,
250
+ registry: check.registry,
251
+ });
252
+ } else {
253
+ errors.push(`${check.registry}: ${check.error.message}`);
254
+ }
255
+ }
256
+
257
+ if (selected) {
258
+ return selected;
259
+ }
260
+
261
+ throw new Error(`Unable to check npm registries.\n${errors.join('\n')}`.trim());
109
262
  }
110
263
 
111
264
  function realpathIfExists(targetPath) {
@@ -154,19 +307,20 @@ function findLocalProjectRoot(packageRoot) {
154
307
  return '';
155
308
  }
156
309
 
157
- function installLatestVersion(latestVersion) {
310
+ function installLatestVersion(latestVersion, registry = DEFAULT_NPM_REGISTRIES[0]) {
158
311
  const packageRoot = currentPackageRoot();
159
312
  const projectRoot = findLocalProjectRoot(packageRoot);
160
313
  const installTarget = `${PACKAGE_NAME}@${latestVersion}`;
314
+ const installArgs = withPackageRegistry(['install', installTarget, '--include=optional'], PACKAGE_NAME, registry);
161
315
 
162
316
  if (projectRoot) {
163
- return runNpmWithRegistryFallback(['install', installTarget, '--include=optional'], {
317
+ return runNpm(installArgs, {
164
318
  cwd: projectRoot,
165
319
  stdio: 'inherit',
166
320
  });
167
321
  }
168
322
 
169
- return runNpmWithRegistryFallback(['install', '-g', installTarget, '--include=optional'], {
323
+ return runNpm(withPackageRegistry(['install', '-g', installTarget, '--include=optional'], PACKAGE_NAME, registry), {
170
324
  stdio: 'inherit',
171
325
  });
172
326
  }
@@ -217,7 +371,7 @@ function restartWithUpdatedCli(args) {
217
371
  process.exit(typeof result.status === 'number' ? result.status : 1);
218
372
  }
219
373
 
220
- function updateBeforeCommand(args) {
374
+ async function updateBeforeCommand(args) {
221
375
  if (!shouldCheckForUpdate(args)) {
222
376
  return;
223
377
  }
@@ -225,14 +379,15 @@ function updateBeforeCommand(args) {
225
379
  const command = requestedCommand(args);
226
380
  console.log('Checking for AI Desk updates...');
227
381
 
228
- let latestVersion;
382
+ let latestSource;
229
383
  try {
230
- latestVersion = latestPublishedVersion();
384
+ latestSource = await latestPublishedPackageSource();
231
385
  } catch (error) {
232
386
  console.warn(`Unable to check for AI Desk updates: ${error.message}`);
233
387
  return;
234
388
  }
235
389
 
390
+ const latestVersion = latestSource.version;
236
391
  const installedVersion = currentVersion();
237
392
  const forceUpgrade = command === 'upgrade' && args.includes('--force');
238
393
  const alreadyLatest = compareVersions(latestVersion, installedVersion) <= 0;
@@ -246,7 +401,7 @@ function updateBeforeCommand(args) {
246
401
 
247
402
  const verb = alreadyLatest ? 'Reinstalling' : 'Updating';
248
403
  console.log(`${verb} AI Desk from ${installedVersion} to ${latestVersion}...`);
249
- const result = installLatestVersion(latestVersion);
404
+ const result = installLatestVersion(latestVersion, latestSource.registry);
250
405
  if (result.error) {
251
406
  throw result.error;
252
407
  }
@@ -263,13 +418,6 @@ function updateBeforeCommand(args) {
263
418
  restartWithUpdatedCli(args);
264
419
  }
265
420
 
266
- try {
267
- updateBeforeCommand(process.argv.slice(2));
268
- } catch (error) {
269
- console.error(`Failed to update AI Desk: ${error.message}`);
270
- process.exit(1);
271
- }
272
-
273
421
  function loadDaemonCli() {
274
422
  if (!isPackagedInstall()) {
275
423
  require('../../../bin/cli.js');
@@ -283,4 +431,15 @@ function loadDaemonCli() {
283
431
  }
284
432
  }
285
433
 
286
- loadDaemonCli();
434
+ async function main() {
435
+ try {
436
+ await updateBeforeCommand(process.argv.slice(2));
437
+ } catch (error) {
438
+ console.error(`Failed to update AI Desk: ${error.message}`);
439
+ process.exit(1);
440
+ }
441
+
442
+ loadDaemonCli();
443
+ }
444
+
445
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-webui/ai-desk",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "Single-install AI Desk package with daemon and default harnesses",
5
5
  "bin": {
6
6
  "aidesk": "bin/aidesk.js"
@@ -14,8 +14,8 @@
14
14
  "README.md"
15
15
  ],
16
16
  "dependencies": {
17
- "@agent-webui/ai-desk-daemon": "1.0.65",
18
- "@agent-webui/ai-desk-harness-gimp": "1.0.65"
17
+ "@agent-webui/ai-desk-daemon": "1.0.67",
18
+ "@agent-webui/ai-desk-harness-gimp": "1.0.67"
19
19
  },
20
20
  "license": "MIT"
21
21
  }