@openagents-org/agent-launcher 0.2.25 → 0.2.27

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.25",
3
+ "version": "0.2.27",
4
4
  "description": "OpenAgents Launcher — install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -39,5 +39,8 @@
39
39
  "dependencies": {
40
40
  "blessed": "^0.1.81",
41
41
  "ws": "^8.18.0"
42
+ },
43
+ "optionalDependencies": {
44
+ "node-pty": "^1.1.0"
42
45
  }
43
46
  }
@@ -191,62 +191,16 @@ class OpenClawAdapter extends BaseAdapter {
191
191
 
192
192
  const spawnEnv = { ...(this.agentEnv || process.env) };
193
193
  if (IS_WINDOWS) {
194
- // Ensure node and npm global bin are on PATH
195
194
  const nodeBinDir = path.dirname(process.execPath);
196
195
  const npmBin = path.join(process.env.APPDATA || '', 'npm');
197
- const extraPaths = [nodeBinDir, npmBin].filter(Boolean);
198
- for (const p of extraPaths) {
196
+ const portableDir = path.join(os.homedir(), '.openagents', 'nodejs');
197
+ for (const p of [nodeBinDir, npmBin, portableDir]) {
199
198
  if (p && !(spawnEnv.PATH || '').includes(p)) {
200
- spawnEnv.PATH = p + ';' + (spawnEnv.PATH || '');
199
+ spawnEnv.PATH = p + path.delimiter + (spawnEnv.PATH || '');
201
200
  }
202
201
  }
203
202
  }
204
203
 
205
- let spawnBinary = binary;
206
- let spawnArgs = args;
207
- const spawnOpts = {
208
- stdio: ['ignore', 'pipe', 'pipe'],
209
- env: spawnEnv,
210
- timeout: 600000,
211
- windowsHide: true,
212
- };
213
-
214
- if (IS_WINDOWS) {
215
- // Find node.exe to spawn openclaw.mjs directly (unbuffered stderr)
216
- const portableNodeDir = path.join(os.homedir(), '.openagents', 'nodejs');
217
- const searchDirs = [portableNodeDir];
218
- try { const { getExtraBinDirs } = require('../paths'); searchDirs.push(...getExtraBinDirs()); } catch {}
219
- let nodeBin = null;
220
- for (const d of searchDirs) {
221
- const candidate = path.join(d, 'node.exe');
222
- if (fs.existsSync(candidate)) { nodeBin = candidate; break; }
223
- }
224
- // Find openclaw entry point
225
- const possibleEntries = [
226
- path.join(path.dirname(binary), 'node_modules', 'openclaw', 'openclaw.mjs'),
227
- path.join(os.homedir(), '.openagents', 'nodejs', 'node_modules', 'openclaw', 'openclaw.mjs'),
228
- ];
229
- let entryPoint = null;
230
- for (const e of possibleEntries) {
231
- if (fs.existsSync(e)) { entryPoint = e; break; }
232
- }
233
-
234
- if (nodeBin && entryPoint) {
235
- // Direct spawn — unbuffered stderr for real-time tool streaming
236
- spawnBinary = nodeBin;
237
- spawnArgs = [entryPoint, ...args];
238
- } else {
239
- // Fallback to cmd.exe (buffered stderr — no real-time status)
240
- spawnBinary = process.env.COMSPEC || 'cmd.exe';
241
- const quotedArgs = args.map((a) => a.includes(' ') ? `"${a}"` : a);
242
- spawnArgs = ['/C', binary, ...quotedArgs];
243
- }
244
- }
245
-
246
- const proc = spawn(spawnBinary, spawnArgs, spawnOpts);
247
- let stdout = '';
248
- let stderr = '';
249
-
250
204
  // Tool name → human-readable status
251
205
  const toolLabels = {
252
206
  exec: 'Running command...',
@@ -261,72 +215,106 @@ class OpenClawAdapter extends BaseAdapter {
261
215
  memory_search: 'Searching memory...',
262
216
  };
263
217
 
264
- // Stream stderr in real-time to detect tool usage and send status updates
265
- let stderrBuffer = '';
266
- if (proc.stdout) proc.stdout.on('data', (d) => { stdout += d; });
267
- if (proc.stderr) proc.stderr.on('data', (d) => {
268
- const chunk = d.toString();
269
- stderr += chunk;
270
- stdout += chunk;
271
- stderrBuffer += chunk;
272
-
273
- // Process complete lines
274
- const lines = stderrBuffer.split('\n');
275
- stderrBuffer = lines.pop() || ''; // keep incomplete last line
276
-
277
- for (const line of lines) {
278
- // Debug: log all diagnostic lines from stderr
279
- if (line.includes('[agent/embedded]') || line.includes('[diagnostic]')) {
280
- this._log(`stderr: ${line.trim().slice(0, 120)}`);
281
- }
218
+ let output = '';
219
+ let lineBuffer = '';
282
220
 
283
- const toolStart = line.match(/embedded run tool start:.*tool=(\w+)/);
284
- if (toolStart) {
285
- const toolName = toolStart[1];
286
- const label = toolLabels[toolName] || `Using ${toolName}...`;
287
- this._log(`Tool status: ${label}`);
288
- this.sendStatus(channel, label).catch(() => {});
289
- }
290
- const agentStart = line.match(/embedded run agent start/);
291
- if (agentStart) {
292
- this.sendStatus(channel, 'thinking...').catch(() => {});
293
- }
221
+ const processLine = (line) => {
222
+ const toolStart = line.match(/embedded run tool start:.*tool=(\w+)/);
223
+ if (toolStart) {
224
+ const label = toolLabels[toolStart[1]] || `Using ${toolStart[1]}...`;
225
+ this._log(`Tool: ${label}`);
226
+ this.sendStatus(channel, label).catch(() => {});
294
227
  }
295
- });
296
-
297
- proc.on('error', (err) => reject(err));
298
- proc.on('exit', (code) => {
299
- if (code !== 0) {
300
- reject(new Error(`CLI exited ${code}: ${stderr.slice(0, 300)}`));
301
- return;
228
+ if (line.match(/embedded run agent start/)) {
229
+ this.sendStatus(channel, 'thinking...').catch(() => {});
302
230
  }
231
+ };
303
232
 
304
- stdout = stdout.trim();
305
- if (!stdout) { resolve(''); return; }
306
-
307
- // Parse JSON output — find first '{'
308
- const jsonStart = stdout.indexOf('{');
309
- if (jsonStart < 0) { resolve(stdout); return; }
310
-
311
- try {
312
- const data = JSON.parse(stdout.slice(jsonStart));
313
- // Extract response text from JSON
314
- const payloads = data.payloads || [];
315
- if (payloads.length > 0) {
316
- const texts = payloads
317
- .filter((p) => p.text)
318
- .map((p) => p.text);
319
- resolve(texts.join('\n\n'));
320
- } else {
321
- resolve('');
233
+ // Try node-pty for line-buffered output (real-time tool streaming)
234
+ let pty;
235
+ try { pty = require('node-pty'); } catch {}
236
+
237
+ if (pty) {
238
+ const shell = IS_WINDOWS ? binary : binary;
239
+ this._log('Spawn: node-pty (line-buffered)');
240
+ const proc = pty.spawn(binary, args, {
241
+ name: 'xterm',
242
+ cols: 200,
243
+ rows: 50,
244
+ cwd: process.cwd(),
245
+ env: spawnEnv,
246
+ });
247
+
248
+ proc.onData((data) => {
249
+ output += data;
250
+ lineBuffer += data;
251
+ const lines = lineBuffer.split('\n');
252
+ lineBuffer = lines.pop() || '';
253
+ for (const line of lines) processLine(line);
254
+ });
255
+
256
+ const timeout = setTimeout(() => {
257
+ proc.kill();
258
+ reject(new Error('CLI timed out after 600 seconds'));
259
+ }, 600000);
260
+
261
+ proc.onExit(({ exitCode }) => {
262
+ clearTimeout(timeout);
263
+ // Process remaining buffer
264
+ if (lineBuffer) processLine(lineBuffer);
265
+
266
+ if (exitCode !== 0) {
267
+ reject(new Error(`CLI exited ${exitCode}: ${output.slice(-300)}`));
268
+ return;
322
269
  }
323
- } catch {
324
- // Failed to parse JSON — return raw output
325
- resolve(stdout);
270
+ this._parseCliOutput(output, resolve);
271
+ });
272
+ } else {
273
+ // Fallback: regular spawn (buffered on Windows)
274
+ this._log('Spawn: fallback (no node-pty)');
275
+ let spawnBin = binary;
276
+ let spawnArgs = args;
277
+ if (IS_WINDOWS) {
278
+ spawnBin = process.env.COMSPEC || 'cmd.exe';
279
+ spawnArgs = ['/C', binary, ...args.map(a => a.includes(' ') ? `"${a}"` : a)];
326
280
  }
327
- });
281
+ const proc = spawn(spawnBin, spawnArgs, {
282
+ stdio: ['ignore', 'pipe', 'pipe'],
283
+ env: spawnEnv,
284
+ timeout: 600000,
285
+ windowsHide: true,
286
+ });
287
+ if (proc.stdout) proc.stdout.on('data', (d) => { output += d; });
288
+ if (proc.stderr) proc.stderr.on('data', (d) => { output += d; });
289
+ proc.on('error', (err) => reject(err));
290
+ proc.on('exit', (code) => {
291
+ if (code !== 0) {
292
+ reject(new Error(`CLI exited ${code}: ${output.slice(-300)}`));
293
+ return;
294
+ }
295
+ this._parseCliOutput(output, resolve);
296
+ });
297
+ }
328
298
  });
329
299
  }
300
+
301
+ _parseCliOutput(output, resolve) {
302
+ const text = output.trim();
303
+ if (!text) { resolve(''); return; }
304
+ const jsonStart = text.indexOf('{');
305
+ if (jsonStart < 0) { resolve(text); return; }
306
+ try {
307
+ const data = JSON.parse(text.slice(jsonStart));
308
+ const payloads = data.payloads || [];
309
+ if (payloads.length > 0) {
310
+ resolve(payloads.filter(p => p.text).map(p => p.text).join('\n\n'));
311
+ } else {
312
+ resolve('');
313
+ }
314
+ } catch {
315
+ resolve(text);
316
+ }
317
+ }
330
318
  // ------------------------------------------------------------------
331
319
  // Static: configure OpenClaw's native auth from LLM env vars
332
320
  // ------------------------------------------------------------------