@openagents-org/agent-launcher 0.2.87 → 0.2.88
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/package.json +1 -1
- package/src/adapters/claude.js +22 -5
- package/src/installer.js +39 -31
- package/src/paths.js +33 -4
- package/src/registry.js +1 -1
package/package.json
CHANGED
package/src/adapters/claude.js
CHANGED
|
@@ -152,9 +152,13 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
152
152
|
_findClaudeBinary() {
|
|
153
153
|
const home = os.homedir();
|
|
154
154
|
|
|
155
|
-
// Tier 0:
|
|
156
|
-
const portableBin = path.join(home, '.openagents', 'nodejs', 'node_modules', '.bin');
|
|
155
|
+
// Tier 0: Isolated runtime prefix (~/.openagents/runtimes/claude/)
|
|
157
156
|
const ext = IS_WINDOWS ? '.cmd' : '';
|
|
157
|
+
const runtimeCandidate = path.join(home, '.openagents', 'runtimes', 'claude', 'node_modules', '.bin', `claude${ext}`);
|
|
158
|
+
if (fs.existsSync(runtimeCandidate)) return runtimeCandidate;
|
|
159
|
+
|
|
160
|
+
// Tier 0b: Legacy portable install at ~/.openagents/nodejs/node_modules/.bin/
|
|
161
|
+
const portableBin = path.join(home, '.openagents', 'nodejs', 'node_modules', '.bin');
|
|
158
162
|
const portableCandidate = path.join(portableBin, `claude${ext}`);
|
|
159
163
|
if (fs.existsSync(portableCandidate)) return portableCandidate;
|
|
160
164
|
|
|
@@ -271,9 +275,22 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
271
275
|
// Find openagents binary (multi-tier)
|
|
272
276
|
let oaBin = null;
|
|
273
277
|
const home3 = os.homedir();
|
|
274
|
-
// Tier 0:
|
|
275
|
-
const
|
|
276
|
-
|
|
278
|
+
// Tier 0: Check all isolated runtime prefixes for openagents binary
|
|
279
|
+
const oaExt = IS_WINDOWS ? '.cmd' : '';
|
|
280
|
+
const runtimesRoot = path.join(home3, '.openagents', 'runtimes');
|
|
281
|
+
try {
|
|
282
|
+
for (const d of fs.readdirSync(runtimesRoot, { withFileTypes: true })) {
|
|
283
|
+
if (d.isDirectory()) {
|
|
284
|
+
const candidate = path.join(runtimesRoot, d.name, 'node_modules', '.bin', `openagents${oaExt}`);
|
|
285
|
+
if (fs.existsSync(candidate)) { oaBin = candidate; break; }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch {}
|
|
289
|
+
// Tier 0b: Legacy portable install
|
|
290
|
+
if (!oaBin) {
|
|
291
|
+
const oaPortable = path.join(home3, '.openagents', 'nodejs', 'node_modules', '.bin', `openagents${oaExt}`);
|
|
292
|
+
if (fs.existsSync(oaPortable)) oaBin = oaPortable;
|
|
293
|
+
}
|
|
277
294
|
// Tier 1: PATH
|
|
278
295
|
if (!oaBin) try {
|
|
279
296
|
if (IS_WINDOWS) {
|
package/src/installer.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { execSync, exec } = require('child_process');
|
|
7
|
-
const { whichBinary, getEnhancedEnv } = require('./paths');
|
|
7
|
+
const { whichBinary, getEnhancedEnv, getRuntimePrefix } = require('./paths');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Manages installation and uninstallation of agent runtimes.
|
|
@@ -36,13 +36,9 @@ class Installer {
|
|
|
36
36
|
* Checks binary on PATH first, then marker files.
|
|
37
37
|
*/
|
|
38
38
|
isInstalled(agentType) {
|
|
39
|
-
// Definitive check: does the package exist in our node_modules?
|
|
40
|
-
const portableDir = path.join(os.homedir(), '.openagents', 'nodejs');
|
|
41
|
-
const globalModules = path.join(portableDir, 'node_modules');
|
|
42
39
|
const entry = this.registry.getEntry(agentType);
|
|
43
40
|
const npmPkg = entry && entry.install ? entry.install.npm_package : null;
|
|
44
41
|
const binary = entry && entry.install ? entry.install.binary : agentType;
|
|
45
|
-
// Extract actual npm package name from install command (e.g. "npm install -g @anthropic-ai/claude-code" → "@anthropic-ai/claude-code")
|
|
46
42
|
const installCmd = entry && entry.install ? this._getInstallCommand(entry.install) : null;
|
|
47
43
|
let npmPkgFromCmd = null;
|
|
48
44
|
if (!npmPkg && installCmd && installCmd.includes('npm install')) {
|
|
@@ -50,29 +46,31 @@ class Installer {
|
|
|
50
46
|
if (match) npmPkgFromCmd = match[1];
|
|
51
47
|
}
|
|
52
48
|
const pkgName = npmPkg || npmPkgFromCmd || binary;
|
|
53
|
-
const pkgDir = path.join(globalModules, pkgName);
|
|
54
|
-
if (fs.existsSync(path.join(pkgDir, 'package.json'))) return true;
|
|
55
49
|
|
|
56
|
-
//
|
|
57
|
-
|
|
50
|
+
// Check isolated runtime prefix first (~/.openagents/runtimes/<type>/)
|
|
51
|
+
const runtimeModules = path.join(getRuntimePrefix(agentType), 'node_modules');
|
|
52
|
+
if (fs.existsSync(path.join(runtimeModules, pkgName, 'package.json'))) return true;
|
|
53
|
+
|
|
54
|
+
// Legacy: check shared prefix (~/.openagents/nodejs/node_modules/)
|
|
55
|
+
const legacyModules = path.join(os.homedir(), '.openagents', 'nodejs', 'node_modules');
|
|
56
|
+
if (fs.existsSync(path.join(legacyModules, pkgName, 'package.json'))) return true;
|
|
58
57
|
|
|
59
58
|
// Fallback: check if binary exists on PATH (system install)
|
|
60
59
|
const binaryPath = this._whichBinary(agentType);
|
|
61
60
|
if (!binaryPath) {
|
|
62
|
-
// Clean stale marker if package is gone
|
|
63
61
|
try { fs.unlinkSync(path.join(this.markersDir, agentType)); } catch {}
|
|
64
62
|
return false;
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
// Verify it's not a stale shim
|
|
68
|
-
|
|
69
|
-
if (binaryPath.startsWith(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
65
|
+
// Verify it's not a stale shim pointing to a missing package
|
|
66
|
+
const openagentsDir = path.join(os.homedir(), '.openagents');
|
|
67
|
+
if (binaryPath.startsWith(openagentsDir)) {
|
|
68
|
+
const hasRuntime = fs.existsSync(path.join(runtimeModules, pkgName, 'package.json'));
|
|
69
|
+
const hasLegacy = fs.existsSync(path.join(legacyModules, pkgName, 'package.json'));
|
|
70
|
+
if (!hasRuntime && !hasLegacy) {
|
|
71
|
+
// Stale shim — clean it up
|
|
72
|
+
for (const ext of ['', '.cmd', '.ps1']) {
|
|
73
|
+
try { const p = path.join(path.dirname(binaryPath), binary + ext); if (fs.existsSync(p)) fs.unlinkSync(p); } catch {}
|
|
76
74
|
}
|
|
77
75
|
return false;
|
|
78
76
|
}
|
|
@@ -195,7 +193,8 @@ class Installer {
|
|
|
195
193
|
|
|
196
194
|
// Use bundled node/npm if system npm not available
|
|
197
195
|
if (cmd.startsWith('npm install')) {
|
|
198
|
-
const prefixDir =
|
|
196
|
+
const prefixDir = getRuntimePrefix(agentType);
|
|
197
|
+
fs.mkdirSync(prefixDir, { recursive: true });
|
|
199
198
|
const args = cmd.replace('npm install', 'install --save').replace(' -g ', ` --prefix "${prefixDir}" `);
|
|
200
199
|
cmd = this._resolveNpmCommand(args);
|
|
201
200
|
}
|
|
@@ -231,9 +230,10 @@ class Installer {
|
|
|
231
230
|
// Resolve npm command
|
|
232
231
|
let cmd = rawCmd;
|
|
233
232
|
if (rawCmd.startsWith('npm install')) {
|
|
234
|
-
const
|
|
233
|
+
const prefixDir = getRuntimePrefix(agentType);
|
|
234
|
+
fs.mkdirSync(prefixDir, { recursive: true });
|
|
235
235
|
// Use --save so npm tracks the package in package.json (prevents pruning on next install)
|
|
236
|
-
const args = rawCmd.replace('npm install', 'install --loglevel=verbose --save').replace(' -g ', ` --prefix "${
|
|
236
|
+
const args = rawCmd.replace('npm install', 'install --loglevel=verbose --save').replace(' -g ', ` --prefix "${prefixDir}" `);
|
|
237
237
|
cmd = this._resolveNpmCommand(args);
|
|
238
238
|
} else if (rawCmd.startsWith('pip install') || rawCmd.startsWith('pipx install')) {
|
|
239
239
|
cmd = rawCmd; // pip commands stay as-is
|
|
@@ -281,14 +281,14 @@ class Installer {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
const installCmd = this._getInstallCommand(entry.install);
|
|
284
|
-
const uninstallCmd = this._deriveUninstallCommand(installCmd);
|
|
284
|
+
const uninstallCmd = this._deriveUninstallCommand(installCmd, agentType);
|
|
285
285
|
if (!uninstallCmd) {
|
|
286
286
|
throw new Error(`Cannot derive uninstall command for ${agentType}`);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
const output = await this._execShell(uninstallCmd);
|
|
290
290
|
this._markUninstalled(agentType);
|
|
291
|
-
// Clean stale shims
|
|
291
|
+
// Clean stale shims (npm sometimes leaves .cmd/.ps1 files behind)
|
|
292
292
|
this._cleanStaleShims(agentType);
|
|
293
293
|
return { success: true, output };
|
|
294
294
|
}
|
|
@@ -297,9 +297,15 @@ class Installer {
|
|
|
297
297
|
const entry = this.registry.getEntry(agentType);
|
|
298
298
|
const binary = entry && entry.install ? entry.install.binary : agentType;
|
|
299
299
|
if (!binary) return;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
|
|
301
|
+
// Clean from isolated runtime prefix
|
|
302
|
+
const runtimeBin = path.join(getRuntimePrefix(agentType), 'node_modules', '.bin');
|
|
303
|
+
|
|
304
|
+
// Clean from legacy shared prefix
|
|
305
|
+
const legacyDir = path.join(os.homedir(), '.openagents', 'nodejs');
|
|
306
|
+
const legacyBin = path.join(legacyDir, 'node_modules', '.bin');
|
|
307
|
+
|
|
308
|
+
for (const dir of [runtimeBin, legacyDir, legacyBin]) {
|
|
303
309
|
for (const ext of ['', '.cmd', '.ps1']) {
|
|
304
310
|
const shimPath = path.join(dir, binary + ext);
|
|
305
311
|
try { if (fs.existsSync(shimPath)) fs.unlinkSync(shimPath); } catch {}
|
|
@@ -318,7 +324,7 @@ class Installer {
|
|
|
318
324
|
}
|
|
319
325
|
|
|
320
326
|
const installCmd = this._getInstallCommand(entry.install);
|
|
321
|
-
let rawCmd = this._deriveUninstallCommand(installCmd);
|
|
327
|
+
let rawCmd = this._deriveUninstallCommand(installCmd, agentType);
|
|
322
328
|
if (!rawCmd) {
|
|
323
329
|
throw new Error(`Cannot derive uninstall command for ${agentType}`);
|
|
324
330
|
}
|
|
@@ -372,13 +378,15 @@ class Installer {
|
|
|
372
378
|
|
|
373
379
|
/**
|
|
374
380
|
* Derive uninstall command from install command.
|
|
381
|
+
* @param {string} installCmd
|
|
382
|
+
* @param {string} [agentType] - used to resolve the isolated runtime prefix
|
|
375
383
|
*/
|
|
376
|
-
_deriveUninstallCommand(installCmd) {
|
|
384
|
+
_deriveUninstallCommand(installCmd, agentType) {
|
|
377
385
|
if (!installCmd) return null;
|
|
378
386
|
|
|
379
|
-
// npm install -g <pkg> → npm uninstall --prefix <
|
|
387
|
+
// npm install -g <pkg> → npm uninstall --prefix <runtimeDir> <pkg>
|
|
380
388
|
if (installCmd.includes('npm install')) {
|
|
381
|
-
const prefixDir = path.join(os.homedir(), '.openagents', 'nodejs');
|
|
389
|
+
const prefixDir = agentType ? getRuntimePrefix(agentType) : path.join(os.homedir(), '.openagents', 'nodejs');
|
|
382
390
|
return installCmd
|
|
383
391
|
.replace('npm install -g', `npm uninstall --prefix "${prefixDir}"`)
|
|
384
392
|
.replace('npm install', 'npm uninstall')
|
package/src/paths.js
CHANGED
|
@@ -42,11 +42,24 @@ function getExtraBinDirs() {
|
|
|
42
42
|
if (nodeDir) _push(dirs, nodeDir);
|
|
43
43
|
} catch {}
|
|
44
44
|
|
|
45
|
-
// Add portable Node.js directory (~/.openagents/nodejs/)
|
|
45
|
+
// Add portable Node.js directory (~/.openagents/nodejs/)
|
|
46
46
|
const portableNode = path.join(HOME, '.openagents', 'nodejs');
|
|
47
|
-
_push(dirs, path.join(portableNode, 'node_modules', '.bin'));
|
|
48
47
|
_push(dirs, portableNode);
|
|
49
48
|
|
|
49
|
+
// Core library bin (~/.openagents/core/node_modules/.bin)
|
|
50
|
+
_push(dirs, path.join(HOME, '.openagents', 'core', 'node_modules', '.bin'));
|
|
51
|
+
|
|
52
|
+
// Per-agent runtime bins (~/.openagents/runtimes/<type>/node_modules/.bin)
|
|
53
|
+
const runtimesDir = path.join(HOME, '.openagents', 'runtimes');
|
|
54
|
+
try {
|
|
55
|
+
for (const d of fs.readdirSync(runtimesDir, { withFileTypes: true })) {
|
|
56
|
+
if (d.isDirectory()) _push(dirs, path.join(runtimesDir, d.name, 'node_modules', '.bin'));
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
60
|
+
// Legacy: shared node_modules/.bin (for backward compat with pre-isolation installs)
|
|
61
|
+
_push(dirs, path.join(portableNode, 'node_modules', '.bin'));
|
|
62
|
+
|
|
50
63
|
// Filter to existing directories only, deduplicate
|
|
51
64
|
const seen = new Set();
|
|
52
65
|
const currentPATH = process.env.PATH || '';
|
|
@@ -178,8 +191,7 @@ function _addUnixPaths(dirs) {
|
|
|
178
191
|
_push(dirs, '/usr/local/bin');
|
|
179
192
|
_push(dirs, '/usr/bin');
|
|
180
193
|
|
|
181
|
-
// npm
|
|
182
|
-
// No need for ~/.npm-global or ~/.openagents/npm-global
|
|
194
|
+
// npm agents install to isolated prefixes: ~/.openagents/runtimes/<type>/
|
|
183
195
|
|
|
184
196
|
// nvm
|
|
185
197
|
const nvmDir = process.env.NVM_DIR || path.join(HOME, '.nvm');
|
|
@@ -272,11 +284,28 @@ function _resolveNvmVersion(nvmDir, alias) {
|
|
|
272
284
|
return null;
|
|
273
285
|
}
|
|
274
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Get the isolated npm prefix directory for an agent runtime.
|
|
289
|
+
* Each agent type gets its own prefix to prevent cross-agent interference.
|
|
290
|
+
*/
|
|
291
|
+
function getRuntimePrefix(agentType) {
|
|
292
|
+
return path.join(HOME, '.openagents', 'runtimes', agentType);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get the core library directory (separate from agent runtimes).
|
|
297
|
+
*/
|
|
298
|
+
function getCorePrefix() {
|
|
299
|
+
return path.join(HOME, '.openagents', 'core');
|
|
300
|
+
}
|
|
301
|
+
|
|
275
302
|
module.exports = {
|
|
276
303
|
getExtraBinDirs,
|
|
277
304
|
getEnhancedPATH,
|
|
278
305
|
getEnhancedEnv,
|
|
279
306
|
whichBinary,
|
|
307
|
+
getRuntimePrefix,
|
|
308
|
+
getCorePrefix,
|
|
280
309
|
IS_WINDOWS,
|
|
281
310
|
IS_MACOS,
|
|
282
311
|
SEP,
|
package/src/registry.js
CHANGED
|
@@ -127,7 +127,7 @@ class Registry {
|
|
|
127
127
|
*/
|
|
128
128
|
async refresh() {
|
|
129
129
|
const remote = await this._fetchRemote();
|
|
130
|
-
if (remote) this._catalog = remote;
|
|
130
|
+
if (remote) this._catalog = this._mergeBundled(remote);
|
|
131
131
|
return this._catalog || this.getCatalogSync();
|
|
132
132
|
}
|
|
133
133
|
|