@openagents-org/agent-launcher 0.2.163 → 0.2.165

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.163",
3
+ "version": "0.2.165",
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": {
package/src/cli.js CHANGED
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ // Before ./index so the daemon and every `agn` subprocess inherit the same
4
+ // no-stray-console-window default on Windows. See win-console.js.
5
+ require('./win-console').installWindowsHideDefault();
6
+
3
7
  const { AgentConnector, Daemon } = require('./index');
4
8
  const { hasCredentialMetadata, formatAuthGuidance } = require('./auth-guidance');
5
9
 
package/src/daemon.js CHANGED
@@ -136,6 +136,18 @@ class Daemon {
136
136
  * so the (synchronous, execSync-heavy) version/login probes never block the
137
137
  * daemon's event loop. Result is reported to the workspace on the heartbeat.
138
138
  */
139
+ /**
140
+ * Persist an agent's model. Sets the generic LLM_MODEL (used by LLM-direct
141
+ * adapters) plus the env var the agent's own CLI reads, so a model picked in
142
+ * the workspace actually takes effect (e.g. Claude reads ANTHROPIC_MODEL).
143
+ */
144
+ async _setModelEnv(type, model) {
145
+ const val = String(model || '');
146
+ const nativeVar = { claude: 'ANTHROPIC_MODEL', gemini: 'GEMINI_MODEL' }[type];
147
+ await this._runAgn(['env', type, '--set', `LLM_MODEL=${val}`]);
148
+ if (nativeVar) await this._runAgn(['env', type, '--set', `${nativeVar}=${val}`]);
149
+ }
150
+
139
151
  async _refreshRuntimes() {
140
152
  try {
141
153
  const r = await this._runAgn(['runtimes', '--json']);
@@ -174,7 +186,7 @@ class Daemon {
174
186
  // Optional credentials for API-key agents (generic → provider mapping
175
187
  // happens in env resolution).
176
188
  if (args.apiKey) await this._runAgn(['env', type, '--set', `LLM_API_KEY=${args.apiKey}`]);
177
- if (args.model) await this._runAgn(['env', type, '--set', `LLM_MODEL=${args.model}`]);
189
+ if (args.model) await this._setModelEnv(type, args.model);
178
190
  if (args.baseUrl) await this._runAgn(['env', type, '--set', `LLM_BASE_URL=${args.baseUrl}`]);
179
191
  // Attach to this node's workspace so the agent shows up as a member.
180
192
  const r2 = await this._runAgn(['connect', name, n.token, '--endpoint', n.endpoint]);
@@ -199,9 +211,7 @@ class Daemon {
199
211
  // recreate (agn has no in-place path change) at the new path.
200
212
  const type = (args.type || '').trim();
201
213
  if (args.apiKey) await this._runAgn(['env', type, '--set', `LLM_API_KEY=${args.apiKey}`]);
202
- if (args.model !== undefined) {
203
- await this._runAgn(['env', type, '--set', `LLM_MODEL=${String(args.model || '')}`]);
204
- }
214
+ if (args.model !== undefined) await this._setModelEnv(type, args.model);
205
215
  const newDir = (args.workingDir || '').trim();
206
216
  if (newDir && newDir !== (args.currentWorkingDir || '')) {
207
217
  try { fs.mkdirSync(newDir, { recursive: true }); } catch {}
@@ -255,6 +265,10 @@ class Daemon {
255
265
  // Run from home so a created agent's default working dir is sensible
256
266
  // (not the ~/.openagents config dir).
257
267
  cwd: os.homedir(),
268
+ // The daemon has no console of its own (it is spawned DETACHED), so a
269
+ // child without this gets a fresh console window — the blank black box
270
+ // that used to appear every time _refreshRuntimes() ran.
271
+ windowsHide: true,
258
272
  });
259
273
  let stdout = '';
260
274
  let stderr = '';
@@ -911,6 +925,9 @@ class Daemon {
911
925
  stdio: ['ignore', 'pipe', 'pipe'],
912
926
  env: getEnhancedEnv(opts.env),
913
927
  cwd: opts.cwd,
928
+ // Windows: cmd.exe would otherwise open a console window per agent (the
929
+ // daemon has none to inherit) and leave it up for the agent's lifetime.
930
+ windowsHide: true,
914
931
  };
915
932
 
916
933
  if (IS_WINDOWS) {
package/src/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ // Must run before anything captures a child_process reference: on Windows it
4
+ // keeps every spawn from popping a stray console window. See win-console.js.
5
+ require('./win-console').installWindowsHideDefault();
6
+
3
7
  const { Config } = require('./config');
4
8
  const { EnvManager } = require('./env');
5
9
  const { Registry } = require('./registry');
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Stop Windows from popping a console window for every child process we spawn.
5
+ *
6
+ * Neither process that runs this code owns a console: the daemon is spawned
7
+ * DETACHED (`agn up --foreground`), and the launcher's Electron main process is
8
+ * a GUI app. On Windows, when a console-less process launches a console
9
+ * executable *without* CREATE_NO_WINDOW, the OS allocates a brand-new console
10
+ * for the child — an empty black cmd/Windows-Terminal window that pops to the
11
+ * foreground and steals focus. With stdio piped nothing is ever drawn in it,
12
+ * so what the user sees is a blank window appearing out of nowhere.
13
+ *
14
+ * That is the "命令框 keeps popping up" report: `_refreshRuntimes()` shells out
15
+ * to `agn runtimes --json` every two minutes, and the probe it runs keeps the
16
+ * window on screen for as long as it takes to check every installed CLI.
17
+ *
18
+ * Node exposes CREATE_NO_WINDOW as the `windowsHide` spawn option and most call
19
+ * sites pass it — but there are ~60 of them across the adapters and a single
20
+ * miss brings the window back. So the default lives here, applied once at the
21
+ * process entry points (src/index.js, src/cli.js) before any module has
22
+ * captured a reference to child_process.
23
+ *
24
+ * Two things this deliberately does NOT do:
25
+ * - override an explicit `windowsHide: false` — the launcher opens a real
26
+ * terminal for CLI sign-in that way, and that window is the point; and
27
+ * - break interactive output: libuv only applies CREATE_NO_WINDOW when no
28
+ * stdio handle is inherited, so `agn` run from a terminal (stdio 'inherit')
29
+ * is unaffected by this and still prints where the user can see it.
30
+ */
31
+
32
+ const childProcess = require('child_process');
33
+
34
+ const PATCHED = Symbol.for('openagents.windowsHideDefault');
35
+ const WRAPPED = ['spawn', 'spawnSync', 'exec', 'execSync', 'execFile', 'execFileSync'];
36
+
37
+ /**
38
+ * Return a copy of `args` whose options object carries `windowsHide: true`.
39
+ *
40
+ * Every child_process signature ends with `[options][, callback]`, so the
41
+ * options object — if there is one — is the last non-function argument. The
42
+ * caller's object is copied rather than mutated: callers reuse option objects
43
+ * and must not see ours leak into their state.
44
+ */
45
+ function withWindowsHide(args) {
46
+ const out = args.slice();
47
+ const cbIdx = typeof out[out.length - 1] === 'function' ? out.length - 1 : -1;
48
+ const optIdx = cbIdx === -1 ? out.length - 1 : cbIdx - 1;
49
+ const opts = optIdx >= 0 ? out[optIdx] : undefined;
50
+
51
+ if (opts && typeof opts === 'object' && !Array.isArray(opts)) {
52
+ if ('windowsHide' in opts) return args; // explicit wins, either way
53
+ out[optIdx] = { ...opts, windowsHide: true };
54
+ return out;
55
+ }
56
+ // No options argument at all — insert one ahead of any callback.
57
+ out.splice(cbIdx === -1 ? out.length : cbIdx, 0, { windowsHide: true });
58
+ return out;
59
+ }
60
+
61
+ /**
62
+ * Default `windowsHide: true` for this process's child_process calls.
63
+ * No-op off Windows, and idempotent.
64
+ */
65
+ function installWindowsHideDefault() {
66
+ if (process.platform !== 'win32') return;
67
+ if (childProcess[PATCHED]) return;
68
+
69
+ for (const name of WRAPPED) {
70
+ const original = childProcess[name];
71
+ if (typeof original !== 'function') continue;
72
+ const wrapper = function (...args) {
73
+ return original.apply(this, withWindowsHide(args));
74
+ };
75
+ // Keep util.promisify(exec) and friends working.
76
+ for (const sym of Object.getOwnPropertySymbols(original)) {
77
+ wrapper[sym] = original[sym];
78
+ }
79
+ Object.defineProperty(wrapper, 'name', { value: name });
80
+ childProcess[name] = wrapper;
81
+ }
82
+ childProcess[PATCHED] = true;
83
+ }
84
+
85
+ module.exports = { installWindowsHideDefault, withWindowsHide };