@openagents-org/agent-launcher 0.2.148 → 0.2.150
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/installer.js +51 -1
- package/src/update-check.js +61 -9
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -471,7 +471,7 @@ class Installer {
|
|
|
471
471
|
// Prefer the resolved absolute binary path (quoted) over the bare name:
|
|
472
472
|
// it's unambiguous and lets `--version` succeed for binaries resolved via
|
|
473
473
|
// the package-bin fallback (which are not on PATH).
|
|
474
|
-
const versionCmd = checkCmd ||
|
|
474
|
+
const versionCmd = checkCmd || this._versionProbeCommand(binary);
|
|
475
475
|
|
|
476
476
|
const version = this._detectVersion(binary, versionCmd);
|
|
477
477
|
const readiness = this._evaluateReadiness(agentType, entry, binary);
|
|
@@ -504,6 +504,56 @@ class Installer {
|
|
|
504
504
|
};
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Build the `<binary> --version` probe command for a resolved binary path.
|
|
509
|
+
*
|
|
510
|
+
* When the resolved binary is a JS entry (`.mjs`/`.cjs`/`.js` — e.g. OpenClaw,
|
|
511
|
+
* whose package `bin` is `openclaw.mjs` and is what `_resolvePackageBin`
|
|
512
|
+
* returns when the `.cmd` shim isn't on PATH), it MUST be run through node.
|
|
513
|
+
* execSync goes through a shell; on Windows `cmd.exe "C:\…\openclaw.mjs"
|
|
514
|
+
* --version` finds no PATHEXT association for `.mjs`, so it ShellExecutes the
|
|
515
|
+
* file and the OS pops the "choose an app to open this .mjs file" dialog. The
|
|
516
|
+
* launcher polls health/version on a timer, so that dialog reappears over and
|
|
517
|
+
* over — even at idle, and stacked (one per poll). Prefixing node makes the
|
|
518
|
+
* probe run headless. Harmless on Unix (node runs a .mjs regardless of any
|
|
519
|
+
* shebang), so it's applied on every platform for consistency.
|
|
520
|
+
*/
|
|
521
|
+
_versionProbeCommand(binary) {
|
|
522
|
+
if (/\.(mjs|cjs|js)$/i.test(binary)) {
|
|
523
|
+
return `"${this._nodeBinary()}" "${binary}" --version`;
|
|
524
|
+
}
|
|
525
|
+
return `"${binary}" --version`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Resolve a node executable to run JS-entry CLIs with. Prefers the launcher's
|
|
530
|
+
* portable node, then a PATH lookup, then the bare name. Deliberately does NOT
|
|
531
|
+
* use process.execPath: when the core runs inside the Electron main process
|
|
532
|
+
* that path is the Electron binary, and `electron foo.mjs` would launch the
|
|
533
|
+
* app instead of running the script.
|
|
534
|
+
*/
|
|
535
|
+
_nodeBinary() {
|
|
536
|
+
const isWin = process.platform === 'win32';
|
|
537
|
+
const nodeName = isWin ? 'node.exe' : 'node';
|
|
538
|
+
const portableDir = path.join(os.homedir(), '.openagents', 'nodejs');
|
|
539
|
+
for (const candidate of [
|
|
540
|
+
path.join(portableDir, nodeName),
|
|
541
|
+
path.join(portableDir, 'bin', nodeName),
|
|
542
|
+
]) {
|
|
543
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const found = execSync(isWin ? 'where node.exe' : 'which node', {
|
|
547
|
+
encoding: 'utf-8',
|
|
548
|
+
timeout: 5000,
|
|
549
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
550
|
+
env: getEnhancedEnv(),
|
|
551
|
+
}).split(/\r?\n/)[0].trim();
|
|
552
|
+
if (found) return found;
|
|
553
|
+
} catch {}
|
|
554
|
+
return 'node';
|
|
555
|
+
}
|
|
556
|
+
|
|
507
557
|
/**
|
|
508
558
|
* Detect a binary's version string, cached briefly. Returns null on failure.
|
|
509
559
|
*/
|
package/src/update-check.js
CHANGED
|
@@ -115,6 +115,44 @@ function npmPrefixFromBin(npmBin, platform = process.platform) {
|
|
|
115
115
|
return platform === 'win32' ? binDir : p.dirname(binDir);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Detect when the running launcher lives in an *isolated runtime* — the local
|
|
120
|
+
* npm project install.sh creates at `~/.openagents/nodejs`, where the package
|
|
121
|
+
* is at `<dir>/node_modules/@openagents-org/agent-launcher` and `<dir>` has its
|
|
122
|
+
* own `package.json`.
|
|
123
|
+
*
|
|
124
|
+
* This matters because a `-g` install (what a normal global/nvm install needs)
|
|
125
|
+
* lands in `<dir>/lib/node_modules`, but the isolated runtime's daemon loads
|
|
126
|
+
* from `<dir>/node_modules` — so `agn update` would report success while the
|
|
127
|
+
* running code never changed (the acen "update no-op" incident). For an
|
|
128
|
+
* isolated runtime we must do a *local* install into `<dir>` instead.
|
|
129
|
+
*
|
|
130
|
+
* Returns the project dir (`<dir>`) for an isolated runtime, else null (caller
|
|
131
|
+
* falls back to the global `-g` path). Global/nvm layouts are excluded: their
|
|
132
|
+
* package sits under `.../lib/node_modules` (basename 'lib') and the prefix
|
|
133
|
+
* root has no package.json.
|
|
134
|
+
*/
|
|
135
|
+
function isolatedRuntimeDir(opts = {}) {
|
|
136
|
+
const dir = opts.dir || __dirname; // <pkgRoot>/src
|
|
137
|
+
const exists = opts.exists || fs.existsSync;
|
|
138
|
+
// Path impl is injectable so tests can force POSIX/Windows semantics
|
|
139
|
+
// regardless of the host OS (mirrors npmPrefixFromBin's `platform`).
|
|
140
|
+
const p = opts.path || path;
|
|
141
|
+
try {
|
|
142
|
+
const pkgRoot = p.resolve(dir, '..'); // .../agent-launcher
|
|
143
|
+
const nodeModules = p.resolve(pkgRoot, '..', '..'); // .../node_modules
|
|
144
|
+
if (p.basename(nodeModules) !== 'node_modules') return null;
|
|
145
|
+
const projectDir = p.resolve(nodeModules, '..'); // dir containing node_modules
|
|
146
|
+
// Global npm layout is <prefix>/lib/node_modules — not an isolated project.
|
|
147
|
+
if (p.basename(projectDir) === 'lib') return null;
|
|
148
|
+
// A real local project has its own package.json; a global prefix does not.
|
|
149
|
+
if (!exists(p.join(projectDir, 'package.json'))) return null;
|
|
150
|
+
return projectDir;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
118
156
|
/**
|
|
119
157
|
* Locate the npm executable next to the running node binary, falling back to
|
|
120
158
|
* a PATH lookup. On Windows the runnable file is `npm.cmd`; the extensionless
|
|
@@ -150,15 +188,28 @@ function runUpdate(opts = {}) {
|
|
|
150
188
|
const platform = opts.platform || process.platform;
|
|
151
189
|
const spawn = opts.spawn || spawnSync;
|
|
152
190
|
const npmBin = opts.npmBin || findNpmBin({ platform });
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
const isoDir = opts.isolatedDir !== undefined ? opts.isolatedDir : isolatedRuntimeDir();
|
|
192
|
+
|
|
193
|
+
let args;
|
|
194
|
+
if (isoDir) {
|
|
195
|
+
// Isolated runtime (install.sh's ~/.openagents/nodejs): a LOCAL npm project
|
|
196
|
+
// whose package lives in <dir>/node_modules — NOT <dir>/lib/node_modules.
|
|
197
|
+
// A `-g` install lands in lib/node_modules and silently no-ops the running
|
|
198
|
+
// launcher/daemon (which loads from node_modules). Install locally into the
|
|
199
|
+
// project dir instead. `--no-save` leaves package.json untouched (install.sh
|
|
200
|
+
// owns it), and because <dir>'s package.json already lists our deps npm has
|
|
201
|
+
// nothing extraneous to prune.
|
|
202
|
+
args = ['install', '--no-save', `${PKG_NAME}@latest`, '--prefix', isoDir];
|
|
203
|
+
} else {
|
|
204
|
+
// Global / nvm install. A LOCAL install (`npm install <pkg> --prefix <dir>`)
|
|
205
|
+
// here would treat <dir> as a project root and prune every node_modules
|
|
206
|
+
// entry not reachable from its package.json — deleting node's own bundled
|
|
207
|
+
// npm/corepack. A global install only adds/updates the named package into
|
|
208
|
+
// <prefix>/node_modules and never prunes its siblings.
|
|
209
|
+
const prefix = opts.prefix !== undefined ? opts.prefix : npmPrefixFromBin(npmBin, platform);
|
|
210
|
+
args = ['install', '-g', `${PKG_NAME}@latest`];
|
|
211
|
+
if (prefix) args.push('--prefix', prefix);
|
|
212
|
+
}
|
|
162
213
|
|
|
163
214
|
// On Windows, npm.cmd is a batch file and must be invoked through cmd.exe.
|
|
164
215
|
// Passing the args via cmd.exe with shell:false lets Node quote paths that
|
|
@@ -240,5 +291,6 @@ module.exports = {
|
|
|
240
291
|
runUpdate,
|
|
241
292
|
findNpmBin,
|
|
242
293
|
npmPrefixFromBin,
|
|
294
|
+
isolatedRuntimeDir,
|
|
243
295
|
currentVersion,
|
|
244
296
|
};
|