@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 +1 -1
- package/src/cli.js +4 -0
- package/src/daemon.js +21 -4
- package/src/index.js +4 -0
- package/src/win-console.js +85 -0
package/package.json
CHANGED
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.
|
|
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 };
|