@openagents-org/agent-launcher 0.2.187 → 0.2.188

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-launcher",
3
- "version": "0.2.187",
3
+ "version": "0.2.188",
4
4
  "description": "OpenAgents Launcher \u2014 install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -42,7 +42,7 @@ const {
42
42
  buildModePrompt,
43
43
  buildPinnedSections,
44
44
  } = require('./workspace-prompt');
45
- const { whichBinary, getEnhancedEnv, defaultAgentWorkdir } = require('../paths');
45
+ const { whichBinary, whereBinary, getEnhancedEnv, defaultAgentWorkdir } = require('../paths');
46
46
 
47
47
  const IS_WINDOWS = process.platform === 'win32';
48
48
 
@@ -105,10 +105,9 @@ function findGooseBinary() {
105
105
  try {
106
106
  const env = getEnhancedEnv();
107
107
  if (IS_WINDOWS) {
108
- const r = execSync('where goose.exe 2>nul || where goose.cmd 2>nul || where goose 2>nul', {
109
- encoding: 'utf-8', timeout: 5000, windowsHide: true, env,
110
- });
111
- const hit = r.split(/\r?\n/)[0].trim();
108
+ // In-process PATH walk, not `where`: its OEM-codepage output mangled
109
+ // non-ASCII profile paths into a hit that could never be spawned.
110
+ const hit = whereBinary(['goose'], env);
112
111
  if (hit) return hit;
113
112
  } else {
114
113
  const hit = execSync('command -v goose', {
@@ -21,6 +21,7 @@ const path = require('path');
21
21
  const { execFile, execFileSync } = require('child_process');
22
22
 
23
23
  const { redactSecrets } = require('./nanoclaw-protocol');
24
+ const { whereAll } = require('../paths');
24
25
 
25
26
  const IS_WINDOWS = process.platform === 'win32';
26
27
 
@@ -176,14 +177,7 @@ function looksLikeNanoclaw(dir) {
176
177
  /** Follow `ncl` on PATH back to the checkout root (bin/ncl → <root>). */
177
178
  function homeFromNclBinary() {
178
179
  try {
179
- const which = IS_WINDOWS ? 'where' : 'which';
180
- const out = execFileSync(which, ['ncl'], {
181
- encoding: 'utf-8',
182
- timeout: 5000,
183
- stdio: ['ignore', 'pipe', 'ignore'],
184
- })
185
- .split(/\r?\n/)[0]
186
- .trim();
180
+ const out = whichSafe('ncl');
187
181
  if (!out) return null;
188
182
  let real = out;
189
183
  try {
@@ -233,15 +227,9 @@ function findNanoclawHome(env = process.env) {
233
227
 
234
228
  function whichSafe(bin) {
235
229
  try {
236
- const which = IS_WINDOWS ? 'where' : 'which';
237
- const out = execFileSync(which, [bin], {
238
- encoding: 'utf-8',
239
- timeout: 5000,
240
- stdio: ['ignore', 'pipe', 'ignore'],
241
- })
242
- .split(/\r?\n/)[0]
243
- .trim();
244
- return out || null;
230
+ // Walks PATH in-process: `where` prints in the OEM codepage, which
231
+ // mangled any non-ASCII profile path when read back as UTF-8.
232
+ return whereAll(bin)[0] || null;
245
233
  } catch {
246
234
  return null;
247
235
  }
@@ -23,7 +23,7 @@ const { spawn } = require('../wsl');
23
23
  const BaseAdapter = require('./base');
24
24
  const { formatAttachmentsForPrompt } = require('./utils');
25
25
  const { buildOpenclawSkillMd, buildOpenclawSystemPrompt, workspaceSkillName } = require('./workspace-prompt');
26
- const { getRuntimePrefix } = require('../paths');
26
+ const { getRuntimePrefix, whereBinary } = require('../paths');
27
27
 
28
28
  const IS_WINDOWS = process.platform === 'win32';
29
29
  const OPENCLAW_STATE_DIR = path.join(
@@ -74,31 +74,17 @@ class OpenClawAdapter extends BaseAdapter {
74
74
  const mjs = path.join(portableDir, 'node_modules', 'openclaw', 'openclaw.mjs');
75
75
  if (fs.existsSync(mjs)) return mjs;
76
76
 
77
- // Fallback: check if openclaw is on PATH (system install)
78
- // On Windows, resolve .cmd shim to actual .mjs path to avoid spawn issues
79
- try {
80
- const cmd = IS_WINDOWS ? 'where openclaw.cmd' : 'which openclaw';
81
- const result = execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })
82
- .split(/\r?\n/)[0].trim();
83
- if (result) {
84
- const resolved = this._resolveShimToMjs(result);
85
- if (resolved) return resolved;
86
- // On Unix, which returns the actual binary/symlink
87
- if (!IS_WINDOWS) return result;
88
- }
89
- } catch {}
90
- // Windows: also try without .cmd extension (for system installs on PATH)
91
- if (IS_WINDOWS) {
92
- try {
93
- const result = execSync('where openclaw', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })
94
- .split(/\r?\n/)[0].trim();
95
- if (result) {
96
- // Try the .cmd variant of this path
97
- const cmdPath = result.replace(/(?:\.cmd)?$/i, '.cmd');
98
- const resolved = this._resolveShimToMjs(cmdPath);
99
- if (resolved) return resolved;
100
- }
101
- } catch {}
77
+ // Fallback: a copy the user installed themselves (npm -g, nvm, a custom
78
+ // npm prefix). whereBinary searches the directories itself instead of
79
+ // shelling out to `where`, which prints in the OEM codepage and mangled
80
+ // any non-ASCII profile path (C:\Users\王…) past recognition.
81
+ const found = whereBinary('openclaw');
82
+ if (found) {
83
+ // Windows: resolve the .cmd shim to openclaw.mjs to avoid spawn issues.
84
+ // Unix: `which` hands back the binary or its symlink, either runs as is.
85
+ const resolved = this._resolveShimToMjs(found);
86
+ if (resolved) return resolved;
87
+ if (!IS_WINDOWS) return found;
102
88
  }
103
89
  return null;
104
90
  }
@@ -359,13 +345,7 @@ class OpenClawAdapter extends BaseAdapter {
359
345
  // Unified path first (symlink on Unix), then legacy bin/ fallback, then system node
360
346
  const nodeUnified = path.join(portableDir, IS_WINDOWS ? 'node.exe' : 'node');
361
347
  let nodeBin = fs.existsSync(nodeUnified) ? nodeUnified : path.join(portableDir, 'bin', 'node');
362
- if (!fs.existsSync(nodeBin)) {
363
- try {
364
- const cmd = IS_WINDOWS ? 'where node.exe' : 'which node';
365
- nodeBin = execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })
366
- .split(/\r?\n/)[0].trim();
367
- } catch { nodeBin = 'node'; }
368
- }
348
+ if (!fs.existsSync(nodeBin)) nodeBin = whereBinary('node') || 'node';
369
349
 
370
350
  // binary from _findOpenclawBinary() is already resolved to .mjs when possible
371
351
  let spawnBin, spawnArgs;
package/src/installer.js CHANGED
@@ -14,6 +14,7 @@ const {
14
14
  resolveManagedNpmBinary,
15
15
  resolveManagedNpmPackageBin,
16
16
  isNodeShebangScript,
17
+ whereAll,
17
18
  } = require('./paths');
18
19
  const { isWslBinary, bridgedCommandString, wslHomeUnc } = require('./wsl');
19
20
  const { canBlock } = require('./probe-mode');
@@ -2087,18 +2088,10 @@ class Installer {
2087
2088
  */
2088
2089
  _hasSystemNode(bundledDir) {
2089
2090
  try {
2090
- const { execFileSync } = require('child_process');
2091
- const cmd = process.platform === 'win32' ? 'where' : 'which';
2092
- const args = process.platform === 'win32' ? ['node'] : ['node'];
2093
- const out = execFileSync(cmd, args, {
2094
- encoding: 'utf-8',
2095
- stdio: ['ignore', 'pipe', 'ignore'],
2096
- timeout: 5000,
2097
- windowsHide: true,
2098
- env: { ...process.env },
2099
- });
2091
+ // In-process PATH walk: `where` prints in the OEM codepage, so on a
2092
+ // non-ASCII profile its UTF-8-decoded lines never matched bundledDir.
2100
2093
  const prefix = (bundledDir || '').toLowerCase();
2101
- for (const line of out.split(/\r?\n/).map(s => s.trim()).filter(Boolean)) {
2094
+ for (const line of whereAll('node')) {
2102
2095
  if (!prefix || !line.toLowerCase().startsWith(prefix)) return true;
2103
2096
  }
2104
2097
  return false;
@@ -2446,7 +2439,10 @@ class Installer {
2446
2439
  const psTokens = /\b(irm|iwr|iex|Invoke-RestMethod|Invoke-WebRequest|Invoke-Expression|Expand-Archive|Get-[A-Z]\w*|Set-[A-Z]\w*)\b/;
2447
2440
  if (!psTokens.test(cmd)) return cmd;
2448
2441
  const escaped = cmd.replace(/"/g, '\\"');
2449
- return `powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "${escaped}"`;
2442
+ // PowerShell writes to a pipe in the OEM codepage (936 on zh-CN) and this
2443
+ // output is read as UTF-8, so a non-ASCII path in an installer's log or
2444
+ // error came back garbled. Switch the console encoding first.
2445
+ return `powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Console]::OutputEncoding=[Text.Encoding]::UTF8; ${escaped}"`;
2450
2446
  }
2451
2447
 
2452
2448
  _execShell(cmd, timeoutMs = 300000, envOverride = null) {
package/src/paths.js CHANGED
@@ -403,6 +403,20 @@ function whereBinary(names, env) {
403
403
  return null;
404
404
  }
405
405
 
406
+ /**
407
+ * Every PATH hit for a name, in PATH order — for callers that filter the list
408
+ * (skip the bundled node, find the first one that runs). Like whereBinary it
409
+ * walks PATH in-process: `where` prints in the OEM codepage, and decoding that
410
+ * as UTF-8 mangles any non-ASCII profile path.
411
+ *
412
+ * @param {string} name
413
+ * @param {object} [env] env whose PATH to search (defaults to process.env)
414
+ * @returns {string[]}
415
+ */
416
+ function whereAll(name, env) {
417
+ return _searchPath(name, env || process.env);
418
+ }
419
+
406
420
  /**
407
421
  * Drop the bin-dir snapshots and every lookup answered from them — for when a
408
422
  * background probe has just learned of directories they did not include.
@@ -1304,6 +1318,7 @@ module.exports = {
1304
1318
  getEnhancedEnv,
1305
1319
  whichBinary,
1306
1320
  whereBinary,
1321
+ whereAll,
1307
1322
  resolveManagedNpmBinary,
1308
1323
  resolveManagedNpmPackageBin,
1309
1324
  isNodeShebangScript,
@@ -13,8 +13,9 @@ const fs = require('fs');
13
13
  const os = require('os');
14
14
  const path = require('path');
15
15
  const readline = require('readline');
16
- const { execSync, spawnSync } = require('child_process');
16
+ const { spawnSync } = require('child_process');
17
17
  const { npmUrls } = require('./mirrors');
18
+ const { whereBinary } = require('./paths');
18
19
 
19
20
  const PKG_NAME = '@openagents-org/agent-launcher';
20
21
  const CACHE_FILE = path.join(os.homedir(), '.openagents', '.update-check.json');
@@ -187,10 +188,9 @@ function findNpmBin(opts = {}) {
187
188
  }
188
189
  // PATH fallback (injectable so tests can exercise the hard fallback
189
190
  // deterministically regardless of the host OS).
190
- const lookup = opts.lookup || (() =>
191
- execSync(platform === 'win32' ? 'where npm' : 'which npm', {
192
- encoding: 'utf-8', timeout: 3000,
193
- }).trim().split(/\r?\n/)[0]);
191
+ // Walked in-process rather than via `where`, whose OEM-codepage output
192
+ // mangled non-ASCII profile paths.
193
+ const lookup = opts.lookup || (() => whereBinary('npm'));
194
194
  try {
195
195
  return lookup() || (platform === 'win32' ? 'npm.cmd' : 'npm');
196
196
  } catch { return platform === 'win32' ? 'npm.cmd' : 'npm'; }