@openagents-org/agent-launcher 0.2.182 → 0.2.184
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/icons/codearts.svg +9 -0
- package/package.json +1 -1
- package/registry.json +68 -0
- package/src/adapters/codearts.js +316 -0
- package/src/adapters/index.js +4 -1
- package/src/adapters/kimi-stream.js +44 -2
- package/src/adapters/kimi.js +116 -16
- package/src/adapters/opencode.js +69 -22
- package/src/daemon.js +22 -1
- package/src/installer.js +48 -16
- package/src/paths.js +187 -92
- package/src/probe-mode.js +36 -0
- package/src/wsl.js +217 -45
package/src/wsl.js
CHANGED
|
@@ -27,20 +27,50 @@
|
|
|
27
27
|
* The mirror direction needs no marker: a path ending in .exe/.cmd/.bat while
|
|
28
28
|
* we are running inside a distro is unambiguously a Windows binary.
|
|
29
29
|
*
|
|
30
|
-
* Cost: resolution never spawns wsl.exe per binary. One probe
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* Cost: resolution never spawns wsl.exe per binary. One probe (5 min TTL) reads
|
|
31
|
+
* the distro's login PATH, and every later lookup is a filesystem check through
|
|
32
|
+
* the \\wsl.localhost UNC mount.
|
|
33
|
+
*
|
|
34
|
+
* Inside the desktop app's main process none of that may block: starting
|
|
35
|
+
* wsl.exe can mean booting the WSL VM, and the first touch of the UNC mount
|
|
36
|
+
* waits on it too — seconds during which the window is frozen. There every
|
|
37
|
+
* probe answers from its cache and refreshes in the background (probe-mode.js).
|
|
34
38
|
*/
|
|
35
39
|
|
|
36
40
|
'use strict';
|
|
37
41
|
|
|
38
42
|
const fs = require('fs');
|
|
39
|
-
const { spawn: nodeSpawn, execFileSync } = require('child_process');
|
|
43
|
+
const { spawn: nodeSpawn, execFile, execFileSync } = require('child_process');
|
|
44
|
+
const { canBlock } = require('./probe-mode');
|
|
40
45
|
|
|
41
46
|
const IS_WINDOWS = process.platform === 'win32';
|
|
42
47
|
const PROBE_TIMEOUT_MS = 10000;
|
|
48
|
+
/** Which binaries were found. Short: an install inside the distro should show up. */
|
|
43
49
|
const CACHE_TTL_MS = 30 * 1000;
|
|
50
|
+
/**
|
|
51
|
+
* The distro list, its UNC root and its login PATH describe the machine, not
|
|
52
|
+
* any one install, and each costs a wsl.exe start — re-asking every 30s kept
|
|
53
|
+
* the VM (and, in the desktop app, the window) busy for nothing.
|
|
54
|
+
*/
|
|
55
|
+
const MACHINE_TTL_MS = 5 * 60 * 1000;
|
|
56
|
+
|
|
57
|
+
function _fresh(cache, ttl) {
|
|
58
|
+
return !!cache.at && Date.now() - cache.at < ttl;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** execFile as a promise of stdout, or null on any failure. stdin is closed. */
|
|
62
|
+
function _execFileOut(file, argv, opts) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
try {
|
|
65
|
+
const child = execFile(file, argv, { windowsHide: true, timeout: PROBE_TIMEOUT_MS, ...opts }, (err, stdout) => {
|
|
66
|
+
resolve(err ? null : stdout);
|
|
67
|
+
});
|
|
68
|
+
if (child.stdin) child.stdin.end();
|
|
69
|
+
} catch {
|
|
70
|
+
resolve(null);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
44
74
|
|
|
45
75
|
/** Marks a resolved path as living inside the distro: `wsl:/home/u/bin/claude`. */
|
|
46
76
|
const WSL_PREFIX = 'wsl:';
|
|
@@ -95,6 +125,36 @@ function isRunningInWsl() {
|
|
|
95
125
|
// ---------------------------------------------------------------------------
|
|
96
126
|
|
|
97
127
|
let distrosCache = { value: null, at: 0 };
|
|
128
|
+
let distrosRefresh = null;
|
|
129
|
+
|
|
130
|
+
function _parseDistros(buf) {
|
|
131
|
+
return Buffer.from(buf)
|
|
132
|
+
.toString('utf16le')
|
|
133
|
+
.split(/\r?\n/)
|
|
134
|
+
.map((l) => l.replace(/\0/g, '').trim())
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Record the distro list; a changed one invalidates everything derived from it. */
|
|
139
|
+
function _setDistros(names) {
|
|
140
|
+
const prev = distrosCache.value;
|
|
141
|
+
distrosCache = { value: names, at: Date.now() };
|
|
142
|
+
if (prev && prev.join('\0') !== names.join('\0')) {
|
|
143
|
+
uncRootCache = { value: null, at: 0 };
|
|
144
|
+
shellProbeCache = { value: null, at: 0 };
|
|
145
|
+
wslBinaryCache.clear();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _refreshDistros() {
|
|
150
|
+
if (distrosCache.value && _fresh(distrosCache, MACHINE_TTL_MS)) return Promise.resolve();
|
|
151
|
+
if (!distrosRefresh) {
|
|
152
|
+
distrosRefresh = _execFileOut('wsl.exe', ['-l', '-q'], { encoding: 'buffer', maxBuffer: 1024 * 1024 })
|
|
153
|
+
.then((out) => _setDistros(out ? _parseDistros(out) : []))
|
|
154
|
+
.finally(() => { distrosRefresh = null; });
|
|
155
|
+
}
|
|
156
|
+
return distrosRefresh;
|
|
157
|
+
}
|
|
98
158
|
|
|
99
159
|
/**
|
|
100
160
|
* Registered, non-broken distro names, default first.
|
|
@@ -110,9 +170,13 @@ let distrosCache = { value: null, at: 0 };
|
|
|
110
170
|
*/
|
|
111
171
|
function wslDistros() {
|
|
112
172
|
if (!IS_WINDOWS) return [];
|
|
113
|
-
if (distrosCache.value &&
|
|
173
|
+
if (distrosCache.value && _fresh(distrosCache, MACHINE_TTL_MS)) {
|
|
114
174
|
return [...distrosCache.value];
|
|
115
175
|
}
|
|
176
|
+
if (!canBlock()) {
|
|
177
|
+
void _refreshDistros();
|
|
178
|
+
return [...(distrosCache.value || [])];
|
|
179
|
+
}
|
|
116
180
|
let names = [];
|
|
117
181
|
try {
|
|
118
182
|
const buf = execFileSync('wsl.exe', ['-l', '-q'], {
|
|
@@ -121,15 +185,11 @@ function wslDistros() {
|
|
|
121
185
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
122
186
|
maxBuffer: 1024 * 1024,
|
|
123
187
|
});
|
|
124
|
-
names = buf
|
|
125
|
-
.toString('utf16le')
|
|
126
|
-
.split(/\r?\n/)
|
|
127
|
-
.map((l) => l.replace(/\0/g, '').trim())
|
|
128
|
-
.filter(Boolean);
|
|
188
|
+
names = _parseDistros(buf);
|
|
129
189
|
} catch {
|
|
130
190
|
names = [];
|
|
131
191
|
}
|
|
132
|
-
|
|
192
|
+
_setDistros(names);
|
|
133
193
|
return [...names];
|
|
134
194
|
}
|
|
135
195
|
|
|
@@ -148,19 +208,51 @@ function defaultDistro() {
|
|
|
148
208
|
// ---------------------------------------------------------------------------
|
|
149
209
|
|
|
150
210
|
let uncRootCache = { value: null, at: 0 };
|
|
211
|
+
let uncRootRefresh = null;
|
|
212
|
+
|
|
213
|
+
const UNC_PREFIXES = ['\\\\wsl.localhost\\', '\\\\wsl$\\'];
|
|
214
|
+
|
|
215
|
+
function _refreshUncRoot() {
|
|
216
|
+
if (_fresh(uncRootCache, MACHINE_TTL_MS)) return Promise.resolve();
|
|
217
|
+
if (!uncRootRefresh) {
|
|
218
|
+
uncRootRefresh = (async () => {
|
|
219
|
+
await _refreshDistros();
|
|
220
|
+
const distro = (distrosCache.value || [])[0];
|
|
221
|
+
if (!distro) return;
|
|
222
|
+
let root = null;
|
|
223
|
+
for (const prefix of UNC_PREFIXES) {
|
|
224
|
+
try {
|
|
225
|
+
await fs.promises.access(prefix + distro);
|
|
226
|
+
root = prefix + distro;
|
|
227
|
+
break;
|
|
228
|
+
} catch {
|
|
229
|
+
/* unreachable share — try the next spelling */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
uncRootCache = { value: root, at: Date.now() };
|
|
233
|
+
})()
|
|
234
|
+
.catch(() => {})
|
|
235
|
+
.finally(() => { uncRootRefresh = null; });
|
|
236
|
+
}
|
|
237
|
+
return uncRootRefresh;
|
|
238
|
+
}
|
|
151
239
|
|
|
152
240
|
/**
|
|
153
241
|
* The UNC prefix that maps the default distro's root into Windows, or null.
|
|
154
242
|
* WSL2 serves \\wsl.localhost\<distro>; WSL1 and older builds only \\wsl$\.
|
|
155
243
|
*/
|
|
156
244
|
function _uncRoot() {
|
|
157
|
-
if (
|
|
245
|
+
if (_fresh(uncRootCache, MACHINE_TTL_MS)) {
|
|
246
|
+
return uncRootCache.value;
|
|
247
|
+
}
|
|
248
|
+
if (!canBlock()) {
|
|
249
|
+
void _refreshUncRoot();
|
|
158
250
|
return uncRootCache.value;
|
|
159
251
|
}
|
|
160
252
|
const distro = defaultDistro();
|
|
161
253
|
if (!distro) return null;
|
|
162
254
|
let root = null;
|
|
163
|
-
for (const prefix of
|
|
255
|
+
for (const prefix of UNC_PREFIXES) {
|
|
164
256
|
const candidate = prefix + distro;
|
|
165
257
|
try {
|
|
166
258
|
if (fs.existsSync(candidate)) { root = candidate; break; }
|
|
@@ -198,9 +290,70 @@ function toUncPath(linuxPath) {
|
|
|
198
290
|
|
|
199
291
|
const PROBE_DELIM = '__OPENAGENTS_WSL__';
|
|
200
292
|
let shellProbeCache = { value: null, at: 0 };
|
|
293
|
+
let shellProbeRefresh = null;
|
|
294
|
+
|
|
295
|
+
const SHELL_PROBE_SCRIPT =
|
|
296
|
+
'echo ' + PROBE_DELIM + '; echo "$PATH"; echo "$HOME"; ' +
|
|
297
|
+
"wslpath -u 'C:\\' 2>/dev/null || echo /mnt/c/";
|
|
298
|
+
const SHELL_PROBE_ARGVS = [
|
|
299
|
+
['-e', 'bash', '-ilc', SHELL_PROBE_SCRIPT],
|
|
300
|
+
['-e', 'sh', '-lc', SHELL_PROBE_SCRIPT],
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
function _emptyShellProbe() {
|
|
304
|
+
return { dirs: [], home: null, automount: '/mnt/' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The probe's delimited output as {dirs, home, automount}, or null if unusable. */
|
|
308
|
+
function _parseShellProbe(raw) {
|
|
309
|
+
const idx = raw.indexOf(PROBE_DELIM);
|
|
310
|
+
if (idx === -1) return null;
|
|
311
|
+
const lines = raw.slice(idx + PROBE_DELIM.length).split(/\r?\n/).map((l) => l.trim());
|
|
312
|
+
const pathLine = lines[1];
|
|
313
|
+
const homeLine = lines[2];
|
|
314
|
+
const cRoot = lines[3];
|
|
315
|
+
if (!pathLine) return null;
|
|
316
|
+
const dirs = [];
|
|
317
|
+
for (const d of pathLine.split(':')) {
|
|
318
|
+
const dir = d.trim();
|
|
319
|
+
// /mnt/<drive> entries are the Windows PATH bounced back by interop.
|
|
320
|
+
// They hold Windows binaries, never the in-distro install we are after.
|
|
321
|
+
if (dir && dir.startsWith('/') && !/^\/mnt\/[a-z]\//i.test(dir) && !dirs.includes(dir)) {
|
|
322
|
+
dirs.push(dir);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// `wslpath -u 'C:\'` answers /mnt/c/ by default, but /etc/wsl.conf can
|
|
326
|
+
// move the automount root — derive it rather than hardcoding /mnt.
|
|
327
|
+
let automount = '/mnt/';
|
|
328
|
+
const m = /^(\/.*\/)c\/?$/i.exec(cRoot || '');
|
|
329
|
+
if (m) automount = m[1];
|
|
330
|
+
return { dirs, home: homeLine && homeLine.startsWith('/') ? homeLine : null, automount };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _refreshShellProbe() {
|
|
334
|
+
if (_fresh(shellProbeCache, MACHINE_TTL_MS)) return Promise.resolve();
|
|
335
|
+
if (!shellProbeRefresh) {
|
|
336
|
+
shellProbeRefresh = (async () => {
|
|
337
|
+
await _refreshDistros();
|
|
338
|
+
if (!(distrosCache.value || []).length) return;
|
|
339
|
+
let out = null;
|
|
340
|
+
for (const argv of SHELL_PROBE_ARGVS) {
|
|
341
|
+
const raw = await _execFileOut('wsl.exe', argv, { encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 });
|
|
342
|
+
out = raw == null ? null : _parseShellProbe(raw);
|
|
343
|
+
if (out) break;
|
|
344
|
+
}
|
|
345
|
+
shellProbeCache = { value: out || _emptyShellProbe(), at: Date.now() };
|
|
346
|
+
// Lookups answered before the PATH was known searched too few dirs.
|
|
347
|
+
if (out) wslBinaryCache.clear();
|
|
348
|
+
})()
|
|
349
|
+
.catch(() => {})
|
|
350
|
+
.finally(() => { shellProbeRefresh = null; });
|
|
351
|
+
}
|
|
352
|
+
return shellProbeRefresh;
|
|
353
|
+
}
|
|
201
354
|
|
|
202
355
|
/**
|
|
203
|
-
* One wsl.exe call
|
|
356
|
+
* One wsl.exe call (5 min TTL) that answers everything later lookups
|
|
204
357
|
* need: the login PATH, $HOME, and the real automount root.
|
|
205
358
|
*
|
|
206
359
|
* `bash -ilc` — INTERACTIVE as well as login — because the version managers
|
|
@@ -211,18 +364,18 @@ let shellProbeCache = { value: null, at: 0 };
|
|
|
211
364
|
* stderr is dropped: an interactive shell with no tty warns about job control.
|
|
212
365
|
*/
|
|
213
366
|
function _shellProbe() {
|
|
214
|
-
const empty =
|
|
215
|
-
if (
|
|
367
|
+
const empty = _emptyShellProbe();
|
|
368
|
+
if (_fresh(shellProbeCache, MACHINE_TTL_MS)) {
|
|
369
|
+
return shellProbeCache.value || empty;
|
|
370
|
+
}
|
|
371
|
+
if (!canBlock()) {
|
|
372
|
+
void _refreshShellProbe();
|
|
216
373
|
return shellProbeCache.value || empty;
|
|
217
374
|
}
|
|
218
375
|
if (!isWslAvailable()) return empty;
|
|
219
376
|
|
|
220
|
-
const script =
|
|
221
|
-
'echo ' + PROBE_DELIM + '; echo "$PATH"; echo "$HOME"; ' +
|
|
222
|
-
"wslpath -u 'C:\\' 2>/dev/null || echo /mnt/c/";
|
|
223
|
-
|
|
224
377
|
let out = null;
|
|
225
|
-
for (const argv of
|
|
378
|
+
for (const argv of SHELL_PROBE_ARGVS) {
|
|
226
379
|
try {
|
|
227
380
|
const raw = execFileSync('wsl.exe', argv, {
|
|
228
381
|
encoding: 'utf-8',
|
|
@@ -231,29 +384,8 @@ function _shellProbe() {
|
|
|
231
384
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
232
385
|
maxBuffer: 4 * 1024 * 1024,
|
|
233
386
|
});
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
const lines = raw.slice(idx + PROBE_DELIM.length).split(/\r?\n/).map((l) => l.trim());
|
|
237
|
-
const pathLine = lines[1];
|
|
238
|
-
const homeLine = lines[2];
|
|
239
|
-
const cRoot = lines[3];
|
|
240
|
-
if (!pathLine) continue;
|
|
241
|
-
const dirs = [];
|
|
242
|
-
for (const d of pathLine.split(':')) {
|
|
243
|
-
const dir = d.trim();
|
|
244
|
-
// /mnt/<drive> entries are the Windows PATH bounced back by interop.
|
|
245
|
-
// They hold Windows binaries, never the in-distro install we are after.
|
|
246
|
-
if (dir && dir.startsWith('/') && !/^\/mnt\/[a-z]\//i.test(dir) && !dirs.includes(dir)) {
|
|
247
|
-
dirs.push(dir);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// `wslpath -u 'C:\'` answers /mnt/c/ by default, but /etc/wsl.conf can
|
|
251
|
-
// move the automount root — derive it rather than hardcoding /mnt.
|
|
252
|
-
let automount = '/mnt/';
|
|
253
|
-
const m = /^(\/.*\/)c\/?$/i.exec(cRoot || '');
|
|
254
|
-
if (m) automount = m[1];
|
|
255
|
-
out = { dirs, home: homeLine && homeLine.startsWith('/') ? homeLine : null, automount };
|
|
256
|
-
break;
|
|
387
|
+
out = _parseShellProbe(raw);
|
|
388
|
+
if (out) break;
|
|
257
389
|
} catch {
|
|
258
390
|
/* try the next shell */
|
|
259
391
|
}
|
|
@@ -290,6 +422,42 @@ function wslBinDirs() {
|
|
|
290
422
|
}
|
|
291
423
|
|
|
292
424
|
const wslBinaryCache = new Map();
|
|
425
|
+
const wslBinaryRefresh = new Map();
|
|
426
|
+
|
|
427
|
+
/** resolveWslBinary's search, off the calling thread. See probe-mode.js. */
|
|
428
|
+
function _refreshWslBinary(key, list) {
|
|
429
|
+
if (wslBinaryRefresh.has(key)) return wslBinaryRefresh.get(key);
|
|
430
|
+
const job = (async () => {
|
|
431
|
+
await _refreshDistros();
|
|
432
|
+
let value = null;
|
|
433
|
+
if ((distrosCache.value || []).length) {
|
|
434
|
+
await Promise.all([_refreshUncRoot(), _refreshShellProbe()]);
|
|
435
|
+
if (uncRootCache.value) {
|
|
436
|
+
search:
|
|
437
|
+
for (const dir of wslBinDirs()) {
|
|
438
|
+
for (const name of list) {
|
|
439
|
+
const linuxPath = dir.replace(/\/+$/, '') + '/' + name;
|
|
440
|
+
const unc = toUncPath(linuxPath);
|
|
441
|
+
if (!unc) continue;
|
|
442
|
+
try {
|
|
443
|
+
if ((await fs.promises.stat(unc)).isFile()) {
|
|
444
|
+
value = WSL_PREFIX + linuxPath;
|
|
445
|
+
break search;
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
/* unreadable path — keep looking */
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
wslBinaryCache.set(key, { value, at: Date.now() });
|
|
455
|
+
})()
|
|
456
|
+
.catch(() => {})
|
|
457
|
+
.finally(() => wslBinaryRefresh.delete(key));
|
|
458
|
+
wslBinaryRefresh.set(key, job);
|
|
459
|
+
return job;
|
|
460
|
+
}
|
|
293
461
|
|
|
294
462
|
/**
|
|
295
463
|
* Resolve an agent binary inside the default distro, or null.
|
|
@@ -309,6 +477,10 @@ function resolveWslBinary(names) {
|
|
|
309
477
|
const key = list.join('\0');
|
|
310
478
|
const cached = wslBinaryCache.get(key);
|
|
311
479
|
if (cached && Date.now() - cached.at < CACHE_TTL_MS) return cached.value;
|
|
480
|
+
if (!canBlock()) {
|
|
481
|
+
void _refreshWslBinary(key, list);
|
|
482
|
+
return cached ? cached.value : null;
|
|
483
|
+
}
|
|
312
484
|
|
|
313
485
|
let value = null;
|
|
314
486
|
if (isWslAvailable() && _uncRoot()) {
|