@openagents-org/agent-launcher 0.2.187 → 0.2.189
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/registry.json +4 -0
- package/src/adapters/goose.js +4 -5
- package/src/adapters/nanoclaw-control.js +5 -17
- package/src/adapters/openclaw.js +70 -66
- package/src/installer.js +8 -12
- package/src/paths.js +15 -0
- package/src/update-check.js +5 -5
package/package.json
CHANGED
package/registry.json
CHANGED
package/src/adapters/goose.js
CHANGED
|
@@ -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
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
}
|
package/src/adapters/openclaw.js
CHANGED
|
@@ -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(
|
|
@@ -31,6 +31,41 @@ const OPENCLAW_STATE_DIR = path.join(
|
|
|
31
31
|
'.openclaw'
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Archive the legacy auth-profiles.json this adapter used to write.
|
|
36
|
+
*
|
|
37
|
+
* Current OpenClaw keeps credentials in SQLite and refuses to run while a
|
|
38
|
+
* legacy JSON store sits beside an empty one ("requires legacy credential
|
|
39
|
+
* migration; run openclaw doctor --fix"); beside a populated one it silently
|
|
40
|
+
* ignores the file, so a changed key never took effect either. The key now
|
|
41
|
+
* reaches OpenClaw through the env and openclaw.json, so the file only gets
|
|
42
|
+
* in the way.
|
|
43
|
+
*
|
|
44
|
+
* Only a file made entirely of our entries is moved: the `custom` provider
|
|
45
|
+
* exists only because configureNativeAuth created it, and any other entry must
|
|
46
|
+
* carry a key we hold ourselves. A profile the user made (OpenClaw's own
|
|
47
|
+
* paste-token also names them `<provider>:manual`) keeps the file in place,
|
|
48
|
+
* for `openclaw doctor --fix` to migrate. Renamed, never deleted.
|
|
49
|
+
*
|
|
50
|
+
* @param {Array<string|undefined>} knownKeys API keys this agent is configured with
|
|
51
|
+
*/
|
|
52
|
+
function retireOwnLegacyAuthProfiles(knownKeys) {
|
|
53
|
+
const file = path.join(OPENCLAW_STATE_DIR, 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
54
|
+
let data;
|
|
55
|
+
try { data = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return false; }
|
|
56
|
+
const keys = new Set((knownKeys || []).filter(Boolean));
|
|
57
|
+
const profiles = Object.values((data && data.profiles) || {});
|
|
58
|
+
const ours = profiles.every((p) => p && p.type === 'token'
|
|
59
|
+
&& (p.provider === 'custom' || keys.has(p.token)));
|
|
60
|
+
if (!ours) return false;
|
|
61
|
+
try {
|
|
62
|
+
fs.renameSync(file, `${file}.openagents-retired`);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
34
69
|
class OpenClawAdapter extends BaseAdapter {
|
|
35
70
|
/**
|
|
36
71
|
* @param {object} opts - BaseAdapter opts plus:
|
|
@@ -54,6 +89,14 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
54
89
|
this._log('OpenClaw binary not found — agent will not be able to process messages');
|
|
55
90
|
}
|
|
56
91
|
|
|
92
|
+
// Agents configured before the key stopped going into auth-profiles.json
|
|
93
|
+
// still have that file; clear it before the first run rather than on the
|
|
94
|
+
// next save.
|
|
95
|
+
const env = this.agentEnv || {};
|
|
96
|
+
if (retireOwnLegacyAuthProfiles([env.LLM_API_KEY, env.OPENAI_API_KEY, env.ANTHROPIC_API_KEY])) {
|
|
97
|
+
this._log('Archived the legacy auth-profiles.json this launcher wrote (OpenClaw now stores credentials in SQLite)');
|
|
98
|
+
}
|
|
99
|
+
|
|
57
100
|
// Install workspace skill
|
|
58
101
|
this._installWorkspaceSkill();
|
|
59
102
|
}
|
|
@@ -74,31 +117,17 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
74
117
|
const mjs = path.join(portableDir, 'node_modules', 'openclaw', 'openclaw.mjs');
|
|
75
118
|
if (fs.existsSync(mjs)) return mjs;
|
|
76
119
|
|
|
77
|
-
// Fallback:
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 {}
|
|
120
|
+
// Fallback: a copy the user installed themselves (npm -g, nvm, a custom
|
|
121
|
+
// npm prefix). whereBinary searches the directories itself instead of
|
|
122
|
+
// shelling out to `where`, which prints in the OEM codepage and mangled
|
|
123
|
+
// any non-ASCII profile path (C:\Users\王…) past recognition.
|
|
124
|
+
const found = whereBinary('openclaw');
|
|
125
|
+
if (found) {
|
|
126
|
+
// Windows: resolve the .cmd shim to openclaw.mjs to avoid spawn issues.
|
|
127
|
+
// Unix: `which` hands back the binary or its symlink, either runs as is.
|
|
128
|
+
const resolved = this._resolveShimToMjs(found);
|
|
129
|
+
if (resolved) return resolved;
|
|
130
|
+
if (!IS_WINDOWS) return found;
|
|
102
131
|
}
|
|
103
132
|
return null;
|
|
104
133
|
}
|
|
@@ -359,13 +388,7 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
359
388
|
// Unified path first (symlink on Unix), then legacy bin/ fallback, then system node
|
|
360
389
|
const nodeUnified = path.join(portableDir, IS_WINDOWS ? 'node.exe' : 'node');
|
|
361
390
|
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
|
-
}
|
|
391
|
+
if (!fs.existsSync(nodeBin)) nodeBin = whereBinary('node') || 'node';
|
|
369
392
|
|
|
370
393
|
// binary from _findOpenclawBinary() is already resolved to .mjs when possible
|
|
371
394
|
let spawnBin, spawnArgs;
|
|
@@ -606,9 +629,10 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
606
629
|
* LLM_API_KEY / LLM_BASE_URL / LLM_MODEL values.
|
|
607
630
|
* Called by the Launcher's saveAgentEnv when type === 'openclaw'.
|
|
608
631
|
*
|
|
609
|
-
* For standard providers (OpenAI, Anthropic),
|
|
610
|
-
* For custom endpoints, uses
|
|
611
|
-
* gives full tool support via the
|
|
632
|
+
* For standard providers (OpenAI, Anthropic), the key travels in the env
|
|
633
|
+
* and only the model is set here. For custom endpoints, uses
|
|
634
|
+
* models.providers in openclaw.json which gives full tool support via the
|
|
635
|
+
* CLI gateway mode.
|
|
612
636
|
*/
|
|
613
637
|
static configureNativeAuth(env) {
|
|
614
638
|
const apiKey = env.LLM_API_KEY;
|
|
@@ -622,26 +646,19 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
622
646
|
const isAnthropic = baseUrl.includes('api.anthropic.com');
|
|
623
647
|
const configFile = path.join(OPENCLAW_STATE_DIR, 'openclaw.json');
|
|
624
648
|
|
|
649
|
+
// The key itself never goes into auth-profiles.json any more: current
|
|
650
|
+
// OpenClaw keeps credentials in SQLite and refuses to start while a legacy
|
|
651
|
+
// JSON store sits beside an empty one ("requires legacy credential
|
|
652
|
+
// migration; run openclaw doctor --fix"). A standard provider reads the
|
|
653
|
+
// key from the OPENAI_API_KEY / ANTHROPIC_API_KEY the resolve rules put
|
|
654
|
+
// in its env; a custom one from models.providers below.
|
|
655
|
+
retireOwnLegacyAuthProfiles([apiKey]);
|
|
656
|
+
|
|
625
657
|
if (isOpenAI || isAnthropic) {
|
|
626
|
-
// Standard provider — use auth-profiles.json
|
|
627
658
|
const provider = isAnthropic ? 'anthropic' : 'openai';
|
|
628
|
-
const profileId = `${provider}:manual`;
|
|
629
|
-
const agentDir = path.join(OPENCLAW_STATE_DIR, 'agents', 'main', 'agent');
|
|
630
|
-
|
|
631
|
-
try {
|
|
632
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
633
|
-
const authFile = path.join(agentDir, 'auth-profiles.json');
|
|
634
|
-
let authData = { version: 1, profiles: {} };
|
|
635
|
-
try { authData = JSON.parse(fs.readFileSync(authFile, 'utf-8')); } catch {}
|
|
636
|
-
authData.profiles = authData.profiles || {};
|
|
637
|
-
authData.profiles[profileId] = { type: 'token', provider, token: apiKey };
|
|
638
|
-
authData.lastGood = authData.lastGood || {};
|
|
639
|
-
authData.lastGood[provider] = profileId;
|
|
640
|
-
fs.writeFileSync(authFile, JSON.stringify(authData, null, 2), 'utf-8');
|
|
641
|
-
} catch {}
|
|
642
|
-
|
|
643
659
|
// Set model
|
|
644
660
|
try {
|
|
661
|
+
fs.mkdirSync(OPENCLAW_STATE_DIR, { recursive: true });
|
|
645
662
|
let config = {};
|
|
646
663
|
try { config = JSON.parse(fs.readFileSync(configFile, 'utf-8')); } catch {}
|
|
647
664
|
config.agents = config.agents || {};
|
|
@@ -673,22 +690,9 @@ class OpenClawAdapter extends BaseAdapter {
|
|
|
673
690
|
|
|
674
691
|
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf-8');
|
|
675
692
|
} catch {}
|
|
676
|
-
|
|
677
|
-
// Also write auth-profiles.json for the custom provider
|
|
678
|
-
try {
|
|
679
|
-
const agentDir = path.join(OPENCLAW_STATE_DIR, 'agents', 'main', 'agent');
|
|
680
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
681
|
-
const authFile = path.join(agentDir, 'auth-profiles.json');
|
|
682
|
-
let authData = { version: 1, profiles: {} };
|
|
683
|
-
try { authData = JSON.parse(fs.readFileSync(authFile, 'utf-8')); } catch {}
|
|
684
|
-
authData.profiles = authData.profiles || {};
|
|
685
|
-
authData.profiles['custom:manual'] = { type: 'token', provider: 'custom', token: apiKey };
|
|
686
|
-
authData.lastGood = authData.lastGood || {};
|
|
687
|
-
authData.lastGood.custom = 'custom:manual';
|
|
688
|
-
fs.writeFileSync(authFile, JSON.stringify(authData, null, 2), 'utf-8');
|
|
689
|
-
} catch {}
|
|
690
693
|
}
|
|
691
694
|
}
|
|
692
695
|
}
|
|
693
696
|
|
|
694
697
|
module.exports = OpenClawAdapter;
|
|
698
|
+
module.exports.retireOwnLegacyAuthProfiles = retireOwnLegacyAuthProfiles;
|
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
|
-
|
|
2091
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
package/src/update-check.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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'; }
|