@openagents-org/agent-launcher 0.2.178 → 0.2.179
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/paths.js +113 -14
package/package.json
CHANGED
package/src/paths.js
CHANGED
|
@@ -19,21 +19,27 @@ const SEP = IS_WINDOWS ? ';' : ':';
|
|
|
19
19
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
20
20
|
const PATH_LOOKUP_CACHE_TTL_MS = 30 * 1000;
|
|
21
21
|
|
|
22
|
+
let knownBinDirsCache = { value: null, at: 0 };
|
|
22
23
|
let extraBinDirsCache = { value: null, at: 0, path: '' };
|
|
23
24
|
const whichBinaryCache = new Map();
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* Every directory an agent CLI is known to land in, filtered to the ones that
|
|
28
|
+
* exist on this machine. Ranked: a copy the launcher installed outranks a copy
|
|
29
|
+
* the user installed, which outranks whatever the login shell happens to add.
|
|
30
|
+
*
|
|
31
|
+
* This is the whole curated set, INCLUDING dirs that are already on PATH —
|
|
32
|
+
* which is what separates it from getExtraBinDirs(). Anything resolving a
|
|
33
|
+
* binary off the filesystem wants this one: that lookup exists precisely for
|
|
34
|
+
* the cases where asking the shell failed, and a dir being on PATH is no
|
|
35
|
+
* evidence at all that `where`/`which` succeeded (see resolveBinaryInKnownDirs).
|
|
28
36
|
*/
|
|
29
|
-
function
|
|
30
|
-
const currentPATH = process.env.PATH || '';
|
|
37
|
+
function getKnownBinDirs() {
|
|
31
38
|
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Date.now() - extraBinDirsCache.at < PATH_LOOKUP_CACHE_TTL_MS
|
|
39
|
+
knownBinDirsCache.value &&
|
|
40
|
+
Date.now() - knownBinDirsCache.at < PATH_LOOKUP_CACHE_TTL_MS
|
|
35
41
|
) {
|
|
36
|
-
return [...
|
|
42
|
+
return [...knownBinDirsCache.value];
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
const dirs = [];
|
|
@@ -99,8 +105,6 @@ function getExtraBinDirs() {
|
|
|
99
105
|
const seen = new Set();
|
|
100
106
|
const value = dirs.filter(d => {
|
|
101
107
|
if (!d || seen.has(d)) return false;
|
|
102
|
-
// Skip if already in PATH (case-insensitive on Windows)
|
|
103
|
-
if (IS_WINDOWS ? currentPATH.toLowerCase().includes(d.toLowerCase()) : currentPATH.includes(d)) return false;
|
|
104
108
|
seen.add(d);
|
|
105
109
|
try {
|
|
106
110
|
return fs.statSync(d).isDirectory();
|
|
@@ -108,6 +112,32 @@ function getExtraBinDirs() {
|
|
|
108
112
|
return false;
|
|
109
113
|
}
|
|
110
114
|
});
|
|
115
|
+
knownBinDirsCache = { value, at: Date.now() };
|
|
116
|
+
return [...value];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The known bin dirs that are NOT already on PATH — i.e. what has to be
|
|
121
|
+
* PREPENDED to build an environment in which every agent CLI resolves.
|
|
122
|
+
*
|
|
123
|
+
* Only ever use this to build a PATH. It is a strict subset of
|
|
124
|
+
* getKnownBinDirs(), and the dirs it drops (%APPDATA%\npm, /usr/local/bin, …)
|
|
125
|
+
* are the most common install locations there are, so using it as "the dirs to
|
|
126
|
+
* search" hides exactly the agents a user installed themselves.
|
|
127
|
+
*/
|
|
128
|
+
function getExtraBinDirs() {
|
|
129
|
+
const currentPATH = process.env.PATH || '';
|
|
130
|
+
if (
|
|
131
|
+
extraBinDirsCache.value &&
|
|
132
|
+
extraBinDirsCache.path === currentPATH &&
|
|
133
|
+
Date.now() - extraBinDirsCache.at < PATH_LOOKUP_CACHE_TTL_MS
|
|
134
|
+
) {
|
|
135
|
+
return [...extraBinDirsCache.value];
|
|
136
|
+
}
|
|
137
|
+
const value = getKnownBinDirs().filter((d) =>
|
|
138
|
+
// Case-insensitive on Windows.
|
|
139
|
+
IS_WINDOWS ? !currentPATH.toLowerCase().includes(d.toLowerCase()) : !currentPATH.includes(d),
|
|
140
|
+
);
|
|
111
141
|
extraBinDirsCache = { value, at: Date.now(), path: currentPATH };
|
|
112
142
|
return [...value];
|
|
113
143
|
}
|
|
@@ -176,7 +206,13 @@ function whichBinary(name) {
|
|
|
176
206
|
const result = execSync(cmd, {
|
|
177
207
|
encoding: 'utf-8',
|
|
178
208
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
179
|
-
|
|
209
|
+
// getEnhancedEnv(), never `{...process.env, PATH}`: spreading process.env
|
|
210
|
+
// on Windows yields a "Path" key, so assigning PATH creates a SECOND path
|
|
211
|
+
// variable and which of the two the child reads is undefined. It also
|
|
212
|
+
// supplies ComSpec, without which execSync's shell cannot even start in an
|
|
213
|
+
// Electron process that has no cmd.exe on PATH — and a `where` that never
|
|
214
|
+
// ran looks exactly like a CLI that isn't installed.
|
|
215
|
+
env: getEnhancedEnv(),
|
|
180
216
|
timeout: 5000,
|
|
181
217
|
windowsHide: true,
|
|
182
218
|
});
|
|
@@ -354,6 +390,35 @@ function _addAgentInstallerPaths(dirs) {
|
|
|
354
390
|
_push(dirs, path.join(HOME, '.npm-global', 'bin'));
|
|
355
391
|
_push(dirs, path.join(HOME, '.openagents', 'npm-global', 'bin'));
|
|
356
392
|
|
|
393
|
+
// codebuddy — the npm package drops the usual global shim, but CodeBuddy also
|
|
394
|
+
// ships a native build (the engine behind the WorkBuddy desktop app) that
|
|
395
|
+
// installs to its own directory. The codebuddy adapter has always searched
|
|
396
|
+
// these; the installer that decides whether it EXISTS had not.
|
|
397
|
+
_push(dirs, path.join(HOME, '.codebuddy', 'bin'));
|
|
398
|
+
|
|
399
|
+
// claude — `claude install` (the native, non-npm build) relocates the CLI to
|
|
400
|
+
// ~/.claude/local and reaches it through a shell alias, which a GUI process
|
|
401
|
+
// inherits no more than it inherits an rc-file PATH edit.
|
|
402
|
+
_push(dirs, path.join(HOME, '.claude', 'local'));
|
|
403
|
+
|
|
404
|
+
// hermes — the Unix installer's own bin dir. Its Windows counterparts are in
|
|
405
|
+
// _addWindowsPaths; this is the half nothing covered.
|
|
406
|
+
_push(dirs, path.join(HOME, '.hermes', 'bin'));
|
|
407
|
+
|
|
408
|
+
// pipx — `pipx install X` puts the executable in <PIPX_HOME>/venvs/X/bin and
|
|
409
|
+
// only SYMLINKS it into PIPX_BIN_DIR. When that link step is skipped, or its
|
|
410
|
+
// dir is one a GUI launch can't see, the venv copy is the only one there is.
|
|
411
|
+
// Enumerated rather than listed by name: aider, mini-swe-agent and openworker
|
|
412
|
+
// are all installable this way, and so is the next Python agent.
|
|
413
|
+
for (const root of _pipxHomes()) {
|
|
414
|
+
try {
|
|
415
|
+
const venvs = path.join(root, 'venvs');
|
|
416
|
+
for (const d of fs.readdirSync(venvs, { withFileTypes: true })) {
|
|
417
|
+
if (d.isDirectory()) _push(dirs, path.join(venvs, d.name, IS_WINDOWS ? 'Scripts' : 'bin'));
|
|
418
|
+
}
|
|
419
|
+
} catch {}
|
|
420
|
+
}
|
|
421
|
+
|
|
357
422
|
// The plain ~/bin some installers fall back to when ~/.local/bin is absent.
|
|
358
423
|
_push(dirs, path.join(HOME, 'bin'));
|
|
359
424
|
|
|
@@ -365,9 +430,32 @@ function _addAgentInstallerPaths(dirs) {
|
|
|
365
430
|
// cursor-agent also ships under Programs\ on some Windows installs — the
|
|
366
431
|
// cursor adapter checks both, the installer only knew one.
|
|
367
432
|
_push(dirs, path.join(lad, 'Programs', 'cursor-agent'));
|
|
433
|
+
// codebuddy's native Windows install.
|
|
434
|
+
_push(dirs, path.join(lad, 'CodeBuddy', 'bin'));
|
|
435
|
+
// winget's shim dir — how GitHub Copilot CLI arrives on Windows for anyone
|
|
436
|
+
// who didn't take the npm route. The copilot adapter knew it; nothing else did.
|
|
437
|
+
_push(dirs, path.join(lad, 'Microsoft', 'WinGet', 'Links'));
|
|
368
438
|
}
|
|
369
439
|
}
|
|
370
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Roots pipx may keep its venvs under. PIPX_HOME wins when exported; otherwise
|
|
443
|
+
* pipx has moved its default once (~/.local/pipx → the platform user-data dir),
|
|
444
|
+
* so both layouts are still in the field.
|
|
445
|
+
*/
|
|
446
|
+
function _pipxHomes() {
|
|
447
|
+
if (process.env.PIPX_HOME) return [process.env.PIPX_HOME];
|
|
448
|
+
const roots = [path.join(HOME, '.local', 'pipx')];
|
|
449
|
+
if (IS_WINDOWS) {
|
|
450
|
+
if (process.env.LOCALAPPDATA) roots.push(path.join(process.env.LOCALAPPDATA, 'pipx', 'pipx'));
|
|
451
|
+
} else if (IS_MACOS) {
|
|
452
|
+
roots.push(path.join(HOME, 'Library', 'Application Support', 'pipx'));
|
|
453
|
+
} else {
|
|
454
|
+
roots.push(path.join(process.env.XDG_DATA_HOME || path.join(HOME, '.local', 'share'), 'pipx'));
|
|
455
|
+
}
|
|
456
|
+
return roots;
|
|
457
|
+
}
|
|
458
|
+
|
|
371
459
|
let npmPrefixCache;
|
|
372
460
|
|
|
373
461
|
/**
|
|
@@ -767,6 +855,7 @@ function defaultAgentWorkdir(agentName) {
|
|
|
767
855
|
* `whichBinary(name) === null` cached before install doesn't survive.
|
|
768
856
|
*/
|
|
769
857
|
function clearBinaryLookupCache() {
|
|
858
|
+
knownBinDirsCache = { value: null, at: 0 };
|
|
770
859
|
extraBinDirsCache = { value: null, at: 0, path: '' };
|
|
771
860
|
whichBinaryCache.clear();
|
|
772
861
|
// The login-shell / npm-prefix probes are deliberately NOT cleared: they each
|
|
@@ -785,7 +874,7 @@ function clearBinaryLookupCache() {
|
|
|
785
874
|
function primeBinaryLookup() {
|
|
786
875
|
try { loginShellDirs(); } catch {}
|
|
787
876
|
try { _npmPrefix(); } catch {}
|
|
788
|
-
try {
|
|
877
|
+
try { getKnownBinDirs(); } catch {}
|
|
789
878
|
}
|
|
790
879
|
|
|
791
880
|
/**
|
|
@@ -845,10 +934,19 @@ const BIN_EXTS = IS_WINDOWS ? ['.cmd', '.exe', '.bat', ''] : [''];
|
|
|
845
934
|
* find it" and then opened a terminal running a bare command that could only
|
|
846
935
|
* fail with "'cursor-agent' is not recognized".
|
|
847
936
|
*
|
|
848
|
-
* Deliberately reuses
|
|
937
|
+
* Deliberately reuses getKnownBinDirs() rather than a second hardcoded list:
|
|
849
938
|
* that list already IS the curated set, and a copy of it is exactly how the
|
|
850
939
|
* launcher and the adapters drifted apart in the first place.
|
|
851
940
|
*
|
|
941
|
+
* It has to be the KNOWN dirs, not the extra ones. getExtraBinDirs() drops
|
|
942
|
+
* everything already on PATH, on the reasoning that PATH covers it — but the
|
|
943
|
+
* only way execution gets here is a `where`/`which` that ALREADY failed, and it
|
|
944
|
+
* fails wholesale on Windows (mangled OEM-codepage output on a non-English
|
|
945
|
+
* install, a 5s timeout, a cmd.exe that isn't resolvable from an Electron
|
|
946
|
+
* process). With the filter in place that failure erased every agent installed
|
|
947
|
+
* to a normal location — %APPDATA%\npm and friends — and the marketplace
|
|
948
|
+
* offered to install CodeBuddy/opencode/dsh over the copies already there.
|
|
949
|
+
*
|
|
852
950
|
* Runs only after a PATH lookup has already missed, so it can add resolutions
|
|
853
951
|
* but never change one that already works.
|
|
854
952
|
*
|
|
@@ -868,7 +966,7 @@ function resolveBinaryInKnownDirs(names, agentType) {
|
|
|
868
966
|
if (agentType) push(path.join(getRuntimePrefix(agentType), 'node_modules', '.bin'));
|
|
869
967
|
push(path.join(HOME, '.openagents', 'nodejs', 'node_modules', '.bin'));
|
|
870
968
|
try { push(path.dirname(process.execPath)); } catch {}
|
|
871
|
-
for (const d of
|
|
969
|
+
for (const d of getKnownBinDirs()) push(d);
|
|
872
970
|
|
|
873
971
|
for (const dir of dirs) {
|
|
874
972
|
for (const name of list) {
|
|
@@ -887,6 +985,7 @@ function resolveBinaryInKnownDirs(names, agentType) {
|
|
|
887
985
|
|
|
888
986
|
|
|
889
987
|
module.exports = {
|
|
988
|
+
getKnownBinDirs,
|
|
890
989
|
getExtraBinDirs,
|
|
891
990
|
getEnhancedPATH,
|
|
892
991
|
getEnhancedEnv,
|