@openagents-org/agent-launcher 0.2.141 → 0.2.142

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.141",
3
+ "version": "0.2.142",
4
4
  "description": "OpenAgents Launcher — install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -93,7 +93,10 @@ class BaseAdapter {
93
93
 
94
94
  // Sync workspace-managed skills into disabledModules
95
95
  try {
96
- const agents = await this.client.getAgents(this.workspaceId, this.token);
96
+ const agents = await Promise.race([
97
+ this.client.getAgents(this.workspaceId, this.token),
98
+ new Promise((_, reject) => setTimeout(() => reject(new Error('skill sync timed out (10s)')), 10000)),
99
+ ]);
97
100
  const self = agents.find((a) => a.agentName === this.agentName);
98
101
  if (self && self.enabledSkills) {
99
102
  const { skillsToDisabledModules } = require('../skill-catalog');
@@ -73,6 +73,10 @@ class ClaudeAdapter extends BaseAdapter {
73
73
  async _onControlAction(action, payload) {
74
74
  if (action === 'stop') {
75
75
  const channel = (payload && typeof payload === 'object') ? payload.channel : null;
76
+ if (channel) {
77
+ const pp = this._persistentProcs[channel];
78
+ if (pp) pp.userStopped = true;
79
+ }
76
80
  if (channel && this._channelProcesses[channel]) {
77
81
  this._log(`Stopping process for channel=${channel}`);
78
82
  this._stoppingChannels.add(channel);
@@ -84,6 +88,7 @@ class ClaudeAdapter extends BaseAdapter {
84
88
  await this.sendResponse(channel, 'Execution stopped by user.');
85
89
  } catch {}
86
90
  } else {
91
+ for (const pp of Object.values(this._persistentProcs)) pp.userStopped = true;
87
92
  await this._stopAllProcesses('Execution stopped by user.');
88
93
  }
89
94
  return;
@@ -226,7 +231,7 @@ class ClaudeAdapter extends BaseAdapter {
226
231
  */
227
232
  async _buildChannelRecap(channelName, currentMessage) {
228
233
  const messages = await this.client.getRecentMessages(
229
- this.workspaceId, channelName, this.token, 30
234
+ this.workspaceId, channelName, this.token, 60
230
235
  );
231
236
  if (!messages || messages.length === 0) return null;
232
237
 
@@ -236,20 +241,16 @@ class ClaudeAdapter extends BaseAdapter {
236
241
  if (mt === 'status' || mt === 'thinking' || mt === 'loading') continue;
237
242
  const text = (m.content || '').trim();
238
243
  if (!text) continue;
239
- // Don't echo the user's current message back at them.
240
244
  if (text === currentMessage) continue;
241
245
  const who = m.senderType === 'human'
242
246
  ? (m.senderName || 'user')
243
247
  : (m.senderName || 'agent');
244
- // Cap each line so a single huge paste doesn't blow up the prompt.
245
- const truncated = text.length > 800 ? text.slice(0, 800) + '…' : text;
248
+ const truncated = text.length > 2000 ? text.slice(0, 2000) + '…' : text;
246
249
  lines.push(`[${who}] ${truncated}`);
247
250
  }
248
251
  if (lines.length === 0) return null;
249
252
 
250
- // Keep only the tail; older context has diminishing value and we
251
- // don't want to balloon the system prompt.
252
- const tail = lines.slice(-15).join('\n');
253
+ const tail = lines.slice(-20).join('\n');
253
254
  return (
254
255
  'You previously worked in this channel but your prior session is no ' +
255
256
  'longer available, so here is the recent conversation for context:\n\n' +
@@ -728,6 +729,7 @@ class ClaudeAdapter extends BaseAdapter {
728
729
  lastStdoutTime: Date.now(),
729
730
  watchdogTimer: null,
730
731
  awaitingToolResult: false,
732
+ userStopped: false,
731
733
  };
732
734
 
733
735
  if (proc.stderr) {
@@ -983,6 +985,12 @@ class ClaudeAdapter extends BaseAdapter {
983
985
  }
984
986
  return;
985
987
  }
988
+ if (existingPP.userStopped) {
989
+ if (!existingPP.everPostedAnything) {
990
+ try { await this.sendResponse(msgChannel, 'Execution stopped by user.'); } catch {}
991
+ }
992
+ return;
993
+ }
986
994
  // Process died mid-message — fall through to spawn a fresh one
987
995
  this._log(`Persistent process died, falling back to fresh spawn for ${msgChannel}`);
988
996
  }
@@ -1034,11 +1042,22 @@ class ClaudeAdapter extends BaseAdapter {
1034
1042
 
1035
1043
  // Spawn a persistent process and send the first message via stdin
1036
1044
  let effectiveContent = content;
1045
+
1046
+ // When starting without a session (no --resume), prepend channel
1047
+ // history so the fresh CLI has conversation context.
1048
+ if (!this._channelSessions[msgChannel]) {
1049
+ try {
1050
+ const recap = await this._buildChannelRecap(msgChannel, content);
1051
+ if (recap) effectiveContent = `${recap}\n\n---\n\n${content}`;
1052
+ } catch {}
1053
+ }
1054
+
1037
1055
  for (let attempt = 0; attempt < 2; attempt++) {
1038
1056
  if (mcpConfigFile) { try { fs.unlinkSync(mcpConfigFile); } catch {} mcpConfigFile = null; }
1039
1057
 
1040
1058
  if (attempt > 0) {
1041
1059
  this._killPersistentProc(msgChannel);
1060
+ // Rebuild recap for retry without resume
1042
1061
  try {
1043
1062
  const recap = await this._buildChannelRecap(msgChannel, content);
1044
1063
  if (recap) effectiveContent = `${recap}\n\n---\n\n${content}`;
@@ -1065,7 +1084,13 @@ class ClaudeAdapter extends BaseAdapter {
1065
1084
  const result = await this._sendToPersistentProc(pp, effectiveContent);
1066
1085
 
1067
1086
  if (result.exited) {
1068
- this._log(`Process exited during first message (attempt ${attempt + 1})`);
1087
+ this._log(`Process exited during first message (attempt ${attempt + 1}), userStopped=${pp.userStopped}`);
1088
+ if (pp.userStopped) {
1089
+ if (!pp.everPostedAnything) {
1090
+ try { await this.sendResponse(msgChannel, 'Execution stopped by user.'); } catch {}
1091
+ }
1092
+ break;
1093
+ }
1069
1094
  if (attempt === 0 && this._channelSessions[msgChannel]) {
1070
1095
  this._log(`Stale session detected, retrying without resume`);
1071
1096
  delete this._channelSessions[msgChannel];
@@ -285,6 +285,13 @@ class CursorAdapter extends BaseAdapter {
285
285
  if (jsMatch) {
286
286
  return [nodeBin, path.resolve(cmdDir, jsMatch[1])];
287
287
  }
288
+ // .cmd shims that forward to a native .exe — resolve to the exe and spawn
289
+ // it directly. Wrapping such a .cmd in `cmd.exe /c` caps the command line
290
+ // at cmd.exe's 8191-char limit, truncating long args (e.g. system prompts).
291
+ const exeMatch = cmdContent.match(/%dp0%\\([^\s"*?]+\.exe)/i);
292
+ if (exeMatch) {
293
+ return [path.resolve(cmdDir, exeMatch[1])];
294
+ }
288
295
  } else {
289
296
  try {
290
297
  let target = binPath;
@@ -424,7 +431,7 @@ class CursorAdapter extends BaseAdapter {
424
431
  try {
425
432
  const resolved = this._resolveToNodeCmd(cmd[0]);
426
433
  if (resolved) {
427
- cmd = [resolved[0], resolved[1], ...cmd.slice(1)];
434
+ cmd = [...resolved, ...cmd.slice(1)];
428
435
  } else if (IS_WINDOWS && cmd[0].toLowerCase().endsWith('.cmd')) {
429
436
  cmd = ['cmd.exe', '/c', ...cmd];
430
437
  }
@@ -122,6 +122,13 @@ class GeminiAdapter extends BaseAdapter {
122
122
  if (jsMatch) {
123
123
  return [nodeBin, path.resolve(cmdDir, jsMatch[1])];
124
124
  }
125
+ // .cmd shims that forward to a native .exe — resolve to the exe and spawn
126
+ // it directly. Wrapping such a .cmd in `cmd.exe /c` caps the command line
127
+ // at cmd.exe's 8191-char limit, truncating long args (e.g. system prompts).
128
+ const exeMatch = cmdContent.match(/%dp0%\\([^\s"*?]+\.exe)/i);
129
+ if (exeMatch) {
130
+ return [path.resolve(cmdDir, exeMatch[1])];
131
+ }
125
132
  } else {
126
133
  try {
127
134
  let target = binPath;
@@ -265,7 +272,7 @@ class GeminiAdapter extends BaseAdapter {
265
272
  try {
266
273
  const resolved = this._resolveToNodeCmd(cmd[0]);
267
274
  if (resolved) {
268
- cmd = [resolved[0], resolved[1], ...cmd.slice(1)];
275
+ cmd = [...resolved, ...cmd.slice(1)];
269
276
  } else if (IS_WINDOWS && cmd[0].toLowerCase().endsWith('.cmd')) {
270
277
  cmd = ['cmd.exe', '/c', ...cmd];
271
278
  }
package/src/daemon.js CHANGED
@@ -151,6 +151,14 @@ class Daemon {
151
151
  if (!this._adapters || !this._adapters[agentName]) break;
152
152
  await new Promise(r => setTimeout(r, 500));
153
153
  }
154
+ // Force-clear the adapter slot if it's still hanging. Without this,
155
+ // a hung adapter.run() promise prevents the slot from ever being
156
+ // released, and subsequent start/restart commands see "already running".
157
+ if (this._adapters && this._adapters[agentName]) {
158
+ this._log(`WARNING: ${agentName} adapter did not exit after stop — force-releasing slot`);
159
+ try { this._adapters[agentName].stop(); } catch {}
160
+ delete this._adapters[agentName];
161
+ }
154
162
  this._writeStatus();
155
163
  }
156
164
 
package/src/installer.js CHANGED
@@ -132,7 +132,12 @@ class Installer {
132
132
  : null;
133
133
  if (verifyCmd) {
134
134
  try {
135
- require('child_process').execSync(verifyCmd, { stdio: 'ignore', timeout: 5000 });
135
+ // Use the enhanced PATH so a freshly-installed CLI living in a dir the
136
+ // installer added (e.g. %LOCALAPPDATA%\cursor-agent) is found — otherwise
137
+ // `cursor-agent --version` exits "not recognized" and verify wrongly fails.
138
+ require('child_process').execSync(verifyCmd, {
139
+ stdio: 'ignore', timeout: 5000, env: getEnhancedEnv(), windowsHide: true,
140
+ });
136
141
  return true;
137
142
  } catch { return false; }
138
143
  }
@@ -140,9 +140,14 @@ function runUpdate(opts = {}) {
140
140
  const spawn = opts.spawn || spawnSync;
141
141
  const npmBin = opts.npmBin || findNpmBin({ platform });
142
142
  const prefix = opts.prefix !== undefined ? opts.prefix : detectInstallPrefix();
143
- const args = ['install', '--no-save', `${PKG_NAME}@latest`];
143
+ // Use a GLOBAL install (-g). A LOCAL install (`npm install <pkg> --prefix
144
+ // <dir>`) treats <dir> as a project root and prunes every node_modules entry
145
+ // not reachable from its package.json. When the prefix is the portable node
146
+ // dir, that deletes node's own bundled npm/corepack (and other agent runtimes),
147
+ // breaking every subsequent `agn` command. A global install only adds/updates
148
+ // the named package into <prefix>/node_modules and never prunes its siblings.
149
+ const args = ['install', '-g', `${PKG_NAME}@latest`];
144
150
  if (prefix) args.push('--prefix', prefix);
145
- else args.push('-g');
146
151
 
147
152
  // On Windows, npm.cmd is a batch file and must be invoked through cmd.exe.
148
153
  // Passing the args via cmd.exe with shell:false lets Node quote paths that