@openagents-org/agent-launcher 0.2.181 → 0.2.183

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.
@@ -35,7 +35,11 @@
35
35
  const fs = require('fs');
36
36
  const os = require('os');
37
37
  const path = require('path');
38
- const { execSync, spawn } = require('child_process');
38
+ const { execSync } = require('child_process');
39
+ // spawn() here is the WSL bridge from ../wsl: same signature as
40
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
41
+ // lives on the other side of the Windows/WSL boundary.
42
+ const { spawn, resolveWslBinary } = require('../wsl');
39
43
 
40
44
  const BaseAdapter = require('./base');
41
45
  const { whichBinary, getEnhancedEnv, aiderBinDirs } = require('../paths');
@@ -262,7 +266,7 @@ class AiderAdapter extends BaseAdapter {
262
266
  // PATH (nvm/fnm/volta/homebrew + the Aider install dirs added to paths.js)
263
267
  // — covers the GUI/daemon "not on PATH" case for uv-tool / pipx / pip-user
264
268
  // installs.
265
- const resolved = whichBinary('aider');
269
+ const resolved = whichBinary('aider', { allowWsl: false });
266
270
  if (resolved) return resolved;
267
271
 
268
272
  // Explicit fallback over every real install dir (XDG bin, XDG_DATA_HOME/../
@@ -274,7 +278,10 @@ class AiderAdapter extends BaseAdapter {
274
278
  if (fs.existsSync(c)) return c;
275
279
  }
276
280
  }
277
- return null;
281
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
282
+ // installed in their distro is a real install, and the marked path it comes
283
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
284
+ return resolveWslBinary('aider');
278
285
  }
279
286
 
280
287
  // ------------------------------------------------------------------
@@ -25,7 +25,11 @@
25
25
  const fs = require('fs');
26
26
  const os = require('os');
27
27
  const path = require('path');
28
- const { execSync, spawn } = require('child_process');
28
+ const { execSync } = require('child_process');
29
+ // spawn() here is the WSL bridge from ../wsl: same signature as
30
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
31
+ // lives on the other side of the Windows/WSL boundary.
32
+ const { spawn, resolveWslBinary } = require('../wsl');
29
33
 
30
34
  const BaseAdapter = require('./base');
31
35
  const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
@@ -123,7 +127,7 @@ class AmpAdapter extends BaseAdapter {
123
127
  // the Amp installer's ~/.amp/bin. This is what makes detection work inside
124
128
  // a GUI- or daemon-spawned process that did NOT inherit the user's
125
129
  // interactive shell PATH (the common "installed but not found" case).
126
- const resolved = whichBinary('amp');
130
+ const resolved = whichBinary('amp', { allowWsl: false });
127
131
  if (resolved) return resolved;
128
132
 
129
133
  // Tier 2: explicit known install dirs as a last resort. The official
@@ -145,7 +149,10 @@ class AmpAdapter extends BaseAdapter {
145
149
  if (c && fs.existsSync(c)) return c;
146
150
  }
147
151
 
148
- return null;
152
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
153
+ // installed in their distro is a real install, and the marked path it comes
154
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
155
+ return resolveWslBinary('amp');
149
156
  }
150
157
 
151
158
  /**
@@ -181,6 +188,7 @@ class AmpAdapter extends BaseAdapter {
181
188
  endpoint: this.endpoint,
182
189
  token: this.token,
183
190
  mode: this._mode,
191
+ model: this.modelLabel(),
184
192
  disabledModules: this.disabledModules,
185
193
  browserEnabled,
186
194
  ...this.pinnedPromptOpts(channelName),
@@ -17,7 +17,11 @@
17
17
  const fs = require('fs');
18
18
  const os = require('os');
19
19
  const path = require('path');
20
- const { execSync, spawn } = require('child_process');
20
+ const { execSync } = require('child_process');
21
+ // spawn() here is the WSL bridge from ../wsl: same signature as
22
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
23
+ // lives on the other side of the Windows/WSL boundary.
24
+ const { spawn, resolveWslBinary } = require('../wsl');
21
25
 
22
26
  const BaseAdapter = require('./base');
23
27
  const { whereBinary } = require('../paths');
@@ -131,7 +135,10 @@ class AntigravityAdapter extends BaseAdapter {
131
135
  for (const c of candidates) {
132
136
  if (fs.existsSync(c)) return c;
133
137
  }
134
- return null;
138
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
139
+ // installed in their distro is a real install, and the marked path it comes
140
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
141
+ return resolveWslBinary('agy');
135
142
  }
136
143
 
137
144
  /**
@@ -178,6 +185,7 @@ class AntigravityAdapter extends BaseAdapter {
178
185
  workspaceId: this.workspaceId,
179
186
  channelName,
180
187
  mode: this._mode,
188
+ model: this.modelLabel(),
181
189
  decisionLog: pinned.decisionLog ? { ...pinned.decisionLog, writeAccess: false } : null,
182
190
  glossary: pinned.glossary ? { ...pinned.glossary, writeAccess: false } : null,
183
191
  });
@@ -60,6 +60,18 @@ const CURSOR_RESUME_MAX_AGE_MS = 24 * 60 * 60 * 1000;
60
60
  // heartbeat_failed up to the daemon. A success resets the streak immediately.
61
61
  const HEARTBEAT_ERROR_THRESHOLD = 2;
62
62
 
63
+ /**
64
+ * Model variables whose name does not follow `<TYPE>_MODEL`. Only the ones an
65
+ * adapter really reads — see `modelLabel`, which tries `<TYPE>_MODEL` first
66
+ * and `LLM_MODEL` last, so nothing else needs an entry here.
67
+ */
68
+ const MODEL_ENV_ALIASES = {
69
+ claude: ['ANTHROPIC_MODEL', 'CLAUDE_MODEL'],
70
+ gemini: ['GEMINI_MODEL', 'GOOGLE_GEMINI_MODEL'],
71
+ antigravity: ['ANTIGRAVITY_MODEL', 'AGY_MODEL'],
72
+ codex: ['CODEX_MODEL', 'OPENCLAW_MODEL'],
73
+ };
74
+
63
75
  class BaseAdapter {
64
76
  /**
65
77
  * @param {object} opts
@@ -220,6 +232,40 @@ class BaseAdapter {
220
232
  }
221
233
  }
222
234
 
235
+ /**
236
+ * The model this agent is actually running on, for the workspace prompt.
237
+ *
238
+ * The one fact about itself an agent cannot look up. Asked "what model are
239
+ * you?", a CLI-driven agent answers from what its weights remember about
240
+ * their own training, which is a guess — and a user who had configured
241
+ * deepseek was told, confidently, "I am Claude, by Anthropic". We know the
242
+ * real answer: it is the id we hand the CLI.
243
+ *
244
+ * Resolution order is the same one the adapters spawn with: a model chosen
245
+ * in the workspace wins, then this agent type's own model variable, then the
246
+ * generic LLM_MODEL that the LLM-direct types and the daemon's model.set
247
+ * both write. Adapters that qualify the id further (OpenWorker prefixes it
248
+ * with the provider) override this and return exactly what they send.
249
+ *
250
+ * Null when nothing is configured — the CLI is then running its own default
251
+ * and we have nothing truthful to say about it. Better silent than wrong.
252
+ */
253
+ modelLabel() {
254
+ const fromWorkspace = String(this.workspaceModel || '').trim();
255
+ if (fromWorkspace) return fromWorkspace;
256
+ const type = String(this.agentType || '').toUpperCase().replace(/[^A-Z0-9]+/g, '_');
257
+ const candidates = [
258
+ ...(type ? [`${type}_MODEL`] : []),
259
+ ...(MODEL_ENV_ALIASES[this.agentType] || []),
260
+ 'LLM_MODEL',
261
+ ];
262
+ for (const key of candidates) {
263
+ const value = String((this.agentEnv && this.agentEnv[key]) || '').trim();
264
+ if (value) return value;
265
+ }
266
+ return null;
267
+ }
268
+
223
269
  async run() {
224
270
  this._running = true;
225
271
 
@@ -14,7 +14,11 @@
14
14
  const fs = require('fs');
15
15
  const os = require('os');
16
16
  const path = require('path');
17
- const { execSync, spawn } = require('child_process');
17
+ const { execSync } = require('child_process');
18
+ // spawn() here is the WSL bridge from ../wsl: same signature as
19
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
20
+ // lives on the other side of the Windows/WSL boundary.
21
+ const { spawn } = require('../wsl');
18
22
 
19
23
  const BaseAdapter = require('./base');
20
24
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle, redactSecrets } = require('./utils');
@@ -462,6 +466,7 @@ class ClaudeAdapter extends BaseAdapter {
462
466
  workspaceId: this.workspaceId,
463
467
  channelName,
464
468
  mode: this._mode,
469
+ model: this.modelLabel(),
465
470
  browserEnabled,
466
471
  toolMode: this.toolMode,
467
472
  decisionLog,
@@ -26,7 +26,11 @@
26
26
  const fs = require('fs');
27
27
  const os = require('os');
28
28
  const path = require('path');
29
- const { execSync, execFile, spawn } = require('child_process');
29
+ const { execSync, execFile } = require('child_process');
30
+ // spawn() here is the WSL bridge from ../wsl: same signature as
31
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
32
+ // lives on the other side of the Windows/WSL boundary.
33
+ const { spawn } = require('../wsl');
30
34
 
31
35
  const BaseAdapter = require('./base');
32
36
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -49,7 +49,11 @@
49
49
  const fs = require('fs');
50
50
  const os = require('os');
51
51
  const path = require('path');
52
- const { execFileSync, spawn } = require('child_process');
52
+ const { execFileSync } = require('child_process');
53
+ // spawn() here is the WSL bridge from ../wsl: same signature as
54
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
55
+ // lives on the other side of the Windows/WSL boundary.
56
+ const { spawn } = require('../wsl');
53
57
 
54
58
  const BaseAdapter = require('./base');
55
59
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -824,6 +828,7 @@ class CodeBuddyAdapter extends BaseAdapter {
824
828
  workspaceId: this.workspaceId,
825
829
  channelName: channel,
826
830
  mode: this._mode,
831
+ model: this.modelLabel(),
827
832
  browserEnabled: await this.getBrowserEnabled(),
828
833
  toolMode: 'mcp',
829
834
  });
@@ -15,7 +15,11 @@
15
15
  const fs = require('fs');
16
16
  const os = require('os');
17
17
  const path = require('path');
18
- const { execSync, spawn } = require('child_process');
18
+ const { execSync } = require('child_process');
19
+ // spawn() here is the WSL bridge from ../wsl: same signature as
20
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
21
+ // lives on the other side of the Windows/WSL boundary.
22
+ const { spawn, resolveWslBinary } = require('../wsl');
19
23
  const http = require('http');
20
24
  const https = require('https');
21
25
 
@@ -165,7 +169,10 @@ class CodexAdapter extends BaseAdapter {
165
169
  if (fs.existsSync(c)) return c;
166
170
  }
167
171
 
168
- return null;
172
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
173
+ // installed in their distro is a real install, and the marked path it comes
174
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
175
+ return resolveWslBinary('codex');
169
176
  }
170
177
 
171
178
  _buildSystemContext(channelName) {
@@ -176,6 +183,7 @@ class CodexAdapter extends BaseAdapter {
176
183
  endpoint: this.endpoint,
177
184
  token: this.token,
178
185
  mode: this._mode,
186
+ model: this.modelLabel(),
179
187
  disabledModules: this.disabledModules,
180
188
  ...this.pinnedPromptOpts(channelName),
181
189
  });
@@ -45,7 +45,11 @@
45
45
  const fs = require('fs');
46
46
  const os = require('os');
47
47
  const path = require('path');
48
- const { execFileSync, spawn } = require('child_process');
48
+ const { execFileSync } = require('child_process');
49
+ // spawn() here is the WSL bridge from ../wsl: same signature as
50
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
51
+ // lives on the other side of the Windows/WSL boundary.
52
+ const { spawn } = require('../wsl');
49
53
 
50
54
  const BaseAdapter = require('./base');
51
55
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -38,7 +38,11 @@
38
38
  const fs = require('fs');
39
39
  const os = require('os');
40
40
  const path = require('path');
41
- const { execSync, execFileSync, spawn } = require('child_process');
41
+ const { execSync, execFileSync } = require('child_process');
42
+ // spawn() here is the WSL bridge from ../wsl: same signature as
43
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
44
+ // lives on the other side of the Windows/WSL boundary.
45
+ const { spawn } = require('../wsl');
42
46
 
43
47
  const BaseAdapter = require('./base');
44
48
  const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
@@ -326,6 +330,7 @@ class CopilotAdapter extends BaseAdapter {
326
330
  endpoint: this.endpoint,
327
331
  token: this.token,
328
332
  mode: this._mode,
333
+ model: this.modelLabel(),
329
334
  disabledModules: this.disabledModules,
330
335
  ...this.pinnedPromptOpts(channelName),
331
336
  });
@@ -12,7 +12,11 @@
12
12
  const fs = require('fs');
13
13
  const os = require('os');
14
14
  const path = require('path');
15
- const { execSync, spawn } = require('child_process');
15
+ const { execSync } = require('child_process');
16
+ // spawn() here is the WSL bridge from ../wsl: same signature as
17
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
18
+ // lives on the other side of the Windows/WSL boundary.
19
+ const { spawn } = require('../wsl');
16
20
 
17
21
  const BaseAdapter = require('./base');
18
22
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -47,7 +47,11 @@
47
47
  const fs = require('fs');
48
48
  const os = require('os');
49
49
  const path = require('path');
50
- const { execFileSync, spawn, execSync } = require('child_process');
50
+ const { execFileSync, execSync } = require('child_process');
51
+ // spawn() here is the WSL bridge from ../wsl: same signature as
52
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
53
+ // lives on the other side of the Windows/WSL boundary.
54
+ const { spawn } = require('../wsl');
51
55
 
52
56
  const BaseAdapter = require('./base');
53
57
  const { whichBinary, getEnhancedEnv } = require('../paths');
@@ -11,7 +11,11 @@
11
11
  const fs = require('fs');
12
12
  const os = require('os');
13
13
  const path = require('path');
14
- const { execSync, spawn } = require('child_process');
14
+ const { execSync } = require('child_process');
15
+ // spawn() here is the WSL bridge from ../wsl: same signature as
16
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
17
+ // lives on the other side of the Windows/WSL boundary.
18
+ const { spawn, resolveWslBinary } = require('../wsl');
15
19
 
16
20
  const BaseAdapter = require('./base');
17
21
  const { whereBinary } = require('../paths');
@@ -183,7 +187,10 @@ class GeminiAdapter extends BaseAdapter {
183
187
  if (fs.existsSync(c)) return c;
184
188
  }
185
189
 
186
- return null;
190
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
191
+ // installed in their distro is a real install, and the marked path it comes
192
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
193
+ return resolveWslBinary('gemini');
187
194
  }
188
195
 
189
196
  // The Gemini CLI refuses to run non-interactively unless an auth method is
@@ -232,6 +239,7 @@ class GeminiAdapter extends BaseAdapter {
232
239
  workspaceId: this.workspaceId,
233
240
  channelName,
234
241
  mode: this._mode,
242
+ model: this.modelLabel(),
235
243
  decisionLog: pinned.decisionLog ? { ...pinned.decisionLog, writeAccess: false } : null,
236
244
  glossary: pinned.glossary ? { ...pinned.glossary, writeAccess: false } : null,
237
245
  });
@@ -28,7 +28,11 @@ const fs = require('fs');
28
28
  const path = require('path');
29
29
  const os = require('os');
30
30
  const crypto = require('crypto');
31
- const { spawn, execSync, execFileSync } = require('child_process');
31
+ const { execSync, execFileSync } = require('child_process');
32
+ // spawn() here is the WSL bridge from ../wsl: same signature as
33
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
34
+ // lives on the other side of the Windows/WSL boundary.
35
+ const { spawn } = require('../wsl');
32
36
 
33
37
  const BaseAdapter = require('./base');
34
38
  const { GooseStreamParser, classifyGooseError, redactSecrets } = require('./goose-stream');
@@ -249,7 +253,7 @@ class GooseAdapter extends BaseAdapter {
249
253
 
250
254
  _buildSystemPrompt(channelName) {
251
255
  const pinned = this.pinnedPromptOpts(channelName);
252
- let prompt = buildWorkspaceIdentity(this.agentName, this.workspaceId, channelName, this._mode)
256
+ let prompt = buildWorkspaceIdentity(this.agentName, this.workspaceId, channelName, this._mode, 'mcp', this.modelLabel())
253
257
  + buildCollaborationPrompt()
254
258
  + buildModePrompt(this._mode)
255
259
  + buildPinnedSections({
@@ -17,7 +17,11 @@
17
17
  const fs = require('fs');
18
18
  const os = require('os');
19
19
  const path = require('path');
20
- const { execSync, spawn } = require('child_process');
20
+ const { execSync } = require('child_process');
21
+ // spawn() here is the WSL bridge from ../wsl: same signature as
22
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
23
+ // lives on the other side of the Windows/WSL boundary.
24
+ const { spawn, bridgeSpawn } = require('../wsl');
21
25
 
22
26
  const BaseAdapter = require('./base');
23
27
  const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
@@ -71,9 +75,6 @@ class HermesAdapter extends BaseAdapter {
71
75
 
72
76
  _findHermesBinary() {
73
77
  const home = os.homedir();
74
- // Reset each call. When set, this._hermesBin is a path INSIDE WSL and must
75
- // be invoked as `wsl -e <path> …` rather than spawned natively.
76
- this._hermesViaWsl = false;
77
78
 
78
79
  // Tier 1: PATH (enriched env so we see the dirs the launcher adds; a fresh
79
80
  // install updates the user PATH, which the running daemon won't pick up).
@@ -112,40 +113,13 @@ class HermesAdapter extends BaseAdapter {
112
113
  if (fs.existsSync(c)) return c;
113
114
  }
114
115
 
115
- // Tier 3: Deep scan of every known bin dir.
116
+ // Tier 3: Deep scan of every known bin dir — which ends inside WSL when
117
+ // nothing native turned up, so a hermes the user set up in their distro is
118
+ // found here and comes back marked for the ../wsl spawn bridge. Native
119
+ // Windows (Tiers 1-2) is still preferred.
116
120
  const viaWhich = whichBinary('hermes');
117
121
  if (viaWhich) return viaWhich;
118
122
 
119
- // Tier 4 (Windows only): fall back to a WSL install if one exists. Native
120
- // Windows (Tiers 1-3) is preferred, but a user who set hermes up inside
121
- // WSL2 still works — resolve its absolute in-WSL path; _runHermes then
122
- // invokes it as `wsl -e <path> …`.
123
- if (IS_WINDOWS) {
124
- const wslPath = this._resolveWslHermes();
125
- if (wslPath) {
126
- this._hermesViaWsl = true;
127
- return wslPath;
128
- }
129
- }
130
-
131
- return null;
132
- }
133
-
134
- /**
135
- * Resolve hermes's absolute path inside the default WSL distro, or null.
136
- * Uses a login shell (`bash -lc`) so the installer's PATH additions
137
- * (~/.local/bin) are visible. Returns an absolute Linux path like
138
- * /home/<user>/.local/bin/hermes.
139
- */
140
- _resolveWslHermes() {
141
- if (!IS_WINDOWS) return null;
142
- try {
143
- const out = execSync('wsl.exe -e bash -lc "command -v hermes"', {
144
- encoding: 'utf-8', timeout: 8000, windowsHide: true,
145
- }).trim();
146
- const p = out.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
147
- if (p && p.startsWith('/')) return p;
148
- } catch {}
149
123
  return null;
150
124
  }
151
125
 
@@ -240,6 +214,7 @@ class HermesAdapter extends BaseAdapter {
240
214
  endpoint: this.endpoint,
241
215
  token: this.token,
242
216
  mode: this._mode,
217
+ model: this.modelLabel(),
243
218
  disabledModules: this.disabledModules,
244
219
  ...this.pinnedPromptOpts(channelName),
245
220
  }),
@@ -314,23 +289,17 @@ class HermesAdapter extends BaseAdapter {
314
289
 
315
290
  const env = { ...(this.agentEnv || process.env) };
316
291
 
317
- // On Windows hermes lives inside WSL: invoke `wsl -e <wsl-hermes-path> …`.
318
- // `-e` runs the binary directly (no shell), so every arg — including the
319
- // multi-line prompt in `-q` — passes through verbatim with no quoting hazard.
320
- // hermes then uses its own config (~/.hermes inside WSL) for model/keys.
321
- let spawnBin = this._hermesBin;
322
- let spawnArgs = args;
323
- if (this._hermesViaWsl) {
324
- spawnBin = 'wsl.exe';
325
- spawnArgs = ['-e', this._hermesBin, ...args];
326
- }
327
-
328
- const proc = spawn(spawnBin, spawnArgs, {
292
+ // A hermes that lives inside WSL is rewritten to `wsl.exe -e <path> …` by
293
+ // the bridge in ../wsl (which also forces detached off, there being no
294
+ // process group on the far side, and hands the distro the agent's env via
295
+ // WSLENV). hermes then uses its own config — ~/.hermes inside the distro —
296
+ // for model and keys. Nothing to special-case here.
297
+ const proc = spawn(this._hermesBin, args, {
329
298
  env,
330
299
  stdio: ['ignore', 'pipe', 'pipe'],
331
- // No process group on Windows / WSL (can't signal a group); windowsHide
332
- // keeps the wsl.exe console from flashing up.
333
- detached: !IS_WINDOWS && !this._hermesViaWsl,
300
+ // No process group on Windows (can't signal one); windowsHide keeps a
301
+ // console from flashing up.
302
+ detached: !IS_WINDOWS,
334
303
  windowsHide: true,
335
304
  });
336
305
  this._channelProcesses[channelName] = proc;
@@ -469,9 +438,10 @@ class HermesAdapter extends BaseAdapter {
469
438
 
470
439
  const { execFileSync } = require('child_process');
471
440
  const run = (args) => {
472
- const spawnBin = probe._hermesViaWsl ? 'wsl.exe' : bin;
473
- const spawnArgs = probe._hermesViaWsl ? ['-e', bin, ...args] : args;
474
- execFileSync(spawnBin, spawnArgs, {
441
+ // Same rewrite the spawn bridge does, for the one call here that needs to
442
+ // be synchronous: an in-distro hermes is only reachable through wsl.exe.
443
+ const { file, args: argv } = bridgeSpawn(bin, args);
444
+ execFileSync(file, argv, {
475
445
  stdio: 'ignore',
476
446
  timeout: 20000,
477
447
  windowsHide: true,
@@ -29,7 +29,11 @@
29
29
  const fs = require('fs');
30
30
  const os = require('os');
31
31
  const path = require('path');
32
- const { execSync, spawn } = require('child_process');
32
+ const { execSync } = require('child_process');
33
+ // spawn() here is the WSL bridge from ../wsl: same signature as
34
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
35
+ // lives on the other side of the Windows/WSL boundary.
36
+ const { spawn } = require('../wsl');
33
37
 
34
38
  const LlmDirectAdapter = require('./llm-direct');
35
39
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -71,6 +71,11 @@ class LlmDirectAdapter extends BaseAdapter {
71
71
  }
72
72
  }
73
73
 
74
+ /** The model this runner actually calls with — see `_model` above. */
75
+ modelLabel() {
76
+ return String(this.workspaceModel || this._model || '').trim() || null;
77
+ }
78
+
74
79
  _buildSystemPrompt(channelName) {
75
80
  return buildOpenclawSystemPrompt({
76
81
  agentName: this.agentName,
@@ -79,6 +84,7 @@ class LlmDirectAdapter extends BaseAdapter {
79
84
  endpoint: this.endpoint,
80
85
  token: this.token,
81
86
  mode: this._mode,
87
+ model: this.modelLabel(),
82
88
  disabledModules: this.disabledModules,
83
89
  ...this.pinnedPromptOpts(channelName),
84
90
  });
@@ -50,7 +50,11 @@
50
50
  const fs = require('fs');
51
51
  const os = require('os');
52
52
  const path = require('path');
53
- const { execSync, spawn } = require('child_process');
53
+ const { execSync } = require('child_process');
54
+ // spawn() here is the WSL bridge from ../wsl: same signature as
55
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
56
+ // lives on the other side of the Windows/WSL boundary.
57
+ const { spawn, resolveWslBinary } = require('../wsl');
54
58
 
55
59
  const BaseAdapter = require('./base');
56
60
  const { whichBinary, getEnhancedEnv, aiderBinDirs } = require('../paths');
@@ -178,7 +182,7 @@ class MiniSweAgentAdapter extends BaseAdapter {
178
182
  // Shared cross-platform resolver runs `which`/`where` against an ENHANCED
179
183
  // PATH (nvm/fnm/volta/homebrew + pip/pipx/uv user-install dirs) — covers the
180
184
  // GUI/daemon "installed but not on PATH" case for a `pip install`.
181
- const resolved = whichBinary('mini');
185
+ const resolved = whichBinary('mini', { allowWsl: false });
182
186
  if (resolved) return this._resolveExePreference(resolved, IS_WINDOWS);
183
187
 
184
188
  // Explicit fallback over the pip/pipx/uv user-install bin dirs plus the uv
@@ -201,7 +205,10 @@ class MiniSweAgentAdapter extends BaseAdapter {
201
205
  if (fs.existsSync(c)) return c;
202
206
  }
203
207
  }
204
- return null;
208
+ // Nothing native anywhere. Last of all, look inside WSL: a CLI the user
209
+ // installed in their distro is a real install, and the marked path it comes
210
+ // back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
211
+ return resolveWslBinary('mini');
205
212
  }
206
213
 
207
214
  /**
@@ -14,7 +14,11 @@
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
16
  const os = require('os');
17
- const { spawn, execSync } = require('child_process');
17
+ const { execSync } = require('child_process');
18
+ // spawn() here is the WSL bridge from ../wsl: same signature as
19
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
20
+ // lives on the other side of the Windows/WSL boundary.
21
+ const { spawn } = require('../wsl');
18
22
 
19
23
  const BaseAdapter = require('./base');
20
24
  const { formatAttachmentsForPrompt } = require('./utils');
@@ -14,7 +14,11 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
  const os = require('os');
16
16
  const crypto = require('crypto');
17
- const { execSync, spawn } = require('child_process');
17
+ const { execSync } = require('child_process');
18
+ // spawn() here is the WSL bridge from ../wsl: same signature as
19
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
20
+ // lives on the other side of the Windows/WSL boundary.
21
+ const { spawn } = require('../wsl');
18
22
 
19
23
  const BaseAdapter = require('./base');
20
24
  const { formatAttachmentsForPrompt, redactSecrets } = require('./utils');
@@ -250,6 +254,7 @@ class OpenCodeAdapter extends BaseAdapter {
250
254
  endpoint: this.endpoint,
251
255
  token: this.token,
252
256
  mode: this._mode,
257
+ model: this.modelLabel(),
253
258
  disabledModules: this.disabledModules,
254
259
  ...this.pinnedPromptOpts(channelName),
255
260
  });
@@ -1100,6 +1105,14 @@ class OpenCodeAdapter extends BaseAdapter {
1100
1105
  ];
1101
1106
  if (stores.some(fileNonEmpty)) return 'present';
1102
1107
 
1108
+ // A model on a provider OpenCode sets up for itself is not ours to judge:
1109
+ // OpenCode Zen's `opencode/…` free models need no sign-in at all, and any
1110
+ // provider id other than openai/anthropic comes from OpenCode's own config.
1111
+ // Blocking those would refuse a run the CLI can make.
1112
+ const model = val('OPENCODE_MODEL') || val('LLM_MODEL');
1113
+ const provider = model.includes('/') ? model.split('/')[0].toLowerCase() : '';
1114
+ if (provider && provider !== 'openai' && provider !== 'anthropic') return 'unknown';
1115
+
1103
1116
  if (val('OPENAI_BASE_URL') || val('LLM_BASE_URL')) return 'unknown';
1104
1117
  if (Object.keys(env).some((k) => /(_API_KEY|_API_TOKEN|_TOKEN)$/.test(k) && val(k))) return 'unknown';
1105
1118
  return 'missing';
@@ -42,7 +42,11 @@ const net = require('net');
42
42
  const os = require('os');
43
43
  const path = require('path');
44
44
  const crypto = require('crypto');
45
- const { spawn, execFileSync } = require('child_process');
45
+ const { execFileSync } = require('child_process');
46
+ // spawn() here is the WSL bridge from ../wsl: same signature as
47
+ // child_process.spawn, and a straight pass-through unless the resolved CLI
48
+ // lives on the other side of the Windows/WSL boundary.
49
+ const { spawn } = require('../wsl');
46
50
 
47
51
  const BaseAdapter = require('./base');
48
52
  const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
@@ -203,6 +207,15 @@ class OpenWorkerAdapter extends BaseAdapter {
203
207
  return String(this.agentEnv.OPENWORKER_API_KEY || '').trim();
204
208
  }
205
209
 
210
+ /**
211
+ * What to tell the agent it is running on. `_model()` is the id we hand the
212
+ * server, provider prefix and all — the honest answer to "what model are
213
+ * you?", which OpenWorker's own weights cannot give.
214
+ */
215
+ modelLabel() {
216
+ return this._model() || null;
217
+ }
218
+
206
219
  /** The model for a turn: the workspace's selection wins over the saved env. */
207
220
  _model() {
208
221
  const raw = String(this.workspaceModel || this.agentEnv.OPENWORKER_MODEL || '').trim();
@@ -587,6 +600,7 @@ class OpenWorkerAdapter extends BaseAdapter {
587
600
  endpoint: this.endpoint,
588
601
  token: this.token,
589
602
  mode: this._mode,
603
+ model: this.modelLabel(),
590
604
  disabledModules: this.disabledModules,
591
605
  browserEnabled,
592
606
  });