@openagents-org/agent-launcher 0.2.157 → 0.2.158
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 +23 -2
- package/src/daemon.js +37 -2
- package/src/workspace-client.js +1 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -311,7 +311,28 @@ async function cmdList(connector) {
|
|
|
311
311
|
table(rows, ['NAME', 'TYPE', 'ROLE', 'NETWORK']);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
async function cmdRuntimes(connector) {
|
|
314
|
+
async function cmdRuntimes(connector, flags) {
|
|
315
|
+
// Machine-readable detection for every supported type: installed? logged-in?
|
|
316
|
+
// Used by the daemon to report node runtimes to the workspace (Add-agent
|
|
317
|
+
// gallery). Emits ONLY JSON on stdout.
|
|
318
|
+
if (flags && flags.json) {
|
|
319
|
+
const runtimes = connector.registry.getCatalogSync().map((e) => {
|
|
320
|
+
let h;
|
|
321
|
+
try { h = connector.healthCheck(e.name); } catch { h = {}; }
|
|
322
|
+
return {
|
|
323
|
+
type: e.name,
|
|
324
|
+
installed: !!h.installed,
|
|
325
|
+
ready: !!h.ready,
|
|
326
|
+
version: h.version || null,
|
|
327
|
+
reason: h.reason || null,
|
|
328
|
+
message: h.message || null,
|
|
329
|
+
authStatus: h.auth_status || null,
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
process.stdout.write(JSON.stringify(runtimes));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
315
336
|
let catalog;
|
|
316
337
|
try {
|
|
317
338
|
catalog = await connector.getCatalog();
|
|
@@ -922,7 +943,7 @@ async function main() {
|
|
|
922
943
|
install: () => cmdInstall(connector, flags, positional),
|
|
923
944
|
uninstall: () => cmdUninstall(connector, flags, positional),
|
|
924
945
|
search: () => cmdSearch(connector, flags, positional),
|
|
925
|
-
runtimes: () => cmdRuntimes(connector),
|
|
946
|
+
runtimes: () => cmdRuntimes(connector, flags),
|
|
926
947
|
connect: () => cmdConnect(connector, flags, positional),
|
|
927
948
|
node: () => cmdNode(connector, flags, positional),
|
|
928
949
|
disconnect: () => cmdDisconnect(connector, flags, positional),
|
package/src/daemon.js
CHANGED
|
@@ -33,6 +33,8 @@ class Daemon {
|
|
|
33
33
|
this._nodeHeartbeatInterval = null; // device-level heartbeat (connect-a-node)
|
|
34
34
|
this._nodeClient = null; // lazily-created WorkspaceClient for the node
|
|
35
35
|
this._runningCommands = new Set(); // node-command ids currently executing
|
|
36
|
+
this._runtimes = []; // detected agent runtimes for the node view
|
|
37
|
+
this._runtimesInterval = null;
|
|
36
38
|
this._reloadInFlight = null; // serialize concurrent _reload() calls
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -49,7 +51,7 @@ class Daemon {
|
|
|
49
51
|
if (!this._nodeClient) {
|
|
50
52
|
this._nodeClient = new WorkspaceClient(n.endpoint);
|
|
51
53
|
}
|
|
52
|
-
const info = { ...nodeCfg.gatherDeviceInfo(), agents: this._buildRoster() };
|
|
54
|
+
const info = { ...nodeCfg.gatherDeviceInfo(), agents: this._buildRoster(), runtimes: this._runtimes };
|
|
53
55
|
const resp = await this._nodeClient.nodeHeartbeat(n.node_id, n.token, info);
|
|
54
56
|
// The heartbeat response is our push channel: run any queued remote
|
|
55
57
|
// agent-management commands the workspace enqueued for this node. Fire
|
|
@@ -86,6 +88,24 @@ class Daemon {
|
|
|
86
88
|
return roster;
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Detect, for every supported agent type, whether its runtime is installed and
|
|
93
|
+
* logged-in/ready on this device. Runs `agn runtimes --json` in a CHILD process
|
|
94
|
+
* so the (synchronous, execSync-heavy) version/login probes never block the
|
|
95
|
+
* daemon's event loop. Result is reported to the workspace on the heartbeat.
|
|
96
|
+
*/
|
|
97
|
+
async _refreshRuntimes() {
|
|
98
|
+
try {
|
|
99
|
+
const r = await this._runAgn(['runtimes', '--json']);
|
|
100
|
+
if (r.code === 0 && r.stdout) {
|
|
101
|
+
const parsed = JSON.parse(r.stdout.trim());
|
|
102
|
+
if (Array.isArray(parsed)) this._runtimes = parsed;
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// keep the previous snapshot on failure
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
/**
|
|
90
110
|
* Execute one remote command by shelling out to this same launcher's CLI, so
|
|
91
111
|
* all of create/install/connect/start/stop/remove reuses the exact code path a
|
|
@@ -100,7 +120,9 @@ class Daemon {
|
|
|
100
120
|
try {
|
|
101
121
|
if (action === 'create_agent') {
|
|
102
122
|
const type = (args.type || '').trim();
|
|
103
|
-
const
|
|
123
|
+
const createArgs = ['create', name, '--type', type, '--install'];
|
|
124
|
+
if (args.workingDir) createArgs.push('--path', String(args.workingDir));
|
|
125
|
+
const r1 = await this._runAgn(createArgs);
|
|
104
126
|
if (r1.code !== 0) throw new Error(r1.stderr || r1.stdout || 'create failed');
|
|
105
127
|
// Optional credentials for API-key agents (generic → provider mapping
|
|
106
128
|
// happens in env resolution).
|
|
@@ -124,6 +146,13 @@ class Daemon {
|
|
|
124
146
|
const r = await this._runAgn(['remove', name]);
|
|
125
147
|
ok = r.code === 0;
|
|
126
148
|
message = ok ? `Agent '${name}' removed` : (r.stderr || r.stdout || 'remove failed');
|
|
149
|
+
} else if (action === 'detect_runtimes') {
|
|
150
|
+
await this._refreshRuntimes();
|
|
151
|
+
// Push the fresh detection right away rather than waiting for the next
|
|
152
|
+
// heartbeat, so the workspace's Add-agent gallery updates promptly.
|
|
153
|
+
this._nodeHeartbeat();
|
|
154
|
+
ok = true;
|
|
155
|
+
message = `Detected ${this._runtimes.length} runtime(s)`;
|
|
127
156
|
} else {
|
|
128
157
|
message = `Unknown action '${action}'`;
|
|
129
158
|
}
|
|
@@ -226,6 +255,11 @@ class Daemon {
|
|
|
226
255
|
this._nodeHeartbeat();
|
|
227
256
|
this._nodeHeartbeatInterval = setInterval(() => this._nodeHeartbeat(), 10000);
|
|
228
257
|
|
|
258
|
+
// Detect installed/logged-in agent runtimes for the Add-agent gallery. Runs
|
|
259
|
+
// in a child process (off the event loop), refreshed periodically.
|
|
260
|
+
this._refreshRuntimes();
|
|
261
|
+
this._runtimesInterval = setInterval(() => this._refreshRuntimes(), 120000);
|
|
262
|
+
|
|
229
263
|
// Watch config file for hot-reload
|
|
230
264
|
this._watchConfig();
|
|
231
265
|
|
|
@@ -252,6 +286,7 @@ class Daemon {
|
|
|
252
286
|
if (this._statusInterval) clearInterval(this._statusInterval);
|
|
253
287
|
if (this._cmdInterval) clearInterval(this._cmdInterval);
|
|
254
288
|
if (this._nodeHeartbeatInterval) clearInterval(this._nodeHeartbeatInterval);
|
|
289
|
+
if (this._runtimesInterval) clearInterval(this._runtimesInterval);
|
|
255
290
|
if (this._configWatcher) { try { this._configWatcher.close(); } catch {} }
|
|
256
291
|
|
|
257
292
|
// Kill all child processes
|
package/src/workspace-client.js
CHANGED
|
@@ -134,6 +134,7 @@ class WorkspaceClient {
|
|
|
134
134
|
if (deviceInfo.os) body.os = deviceInfo.os;
|
|
135
135
|
if (deviceInfo.launcherVersion) body.launcher_version = deviceInfo.launcherVersion;
|
|
136
136
|
if (Array.isArray(deviceInfo.agents)) body.agents = deviceInfo.agents;
|
|
137
|
+
if (Array.isArray(deviceInfo.runtimes)) body.runtimes = deviceInfo.runtimes;
|
|
137
138
|
const data = await this._post('/v1/nodes/heartbeat', body, this._wsHeaders(token));
|
|
138
139
|
return data.data || data;
|
|
139
140
|
}
|