@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 +1 -1
- package/src/adapters/base.js +4 -1
- package/src/adapters/claude.js +33 -8
- package/src/adapters/cursor.js +8 -1
- package/src/adapters/gemini.js +8 -1
- package/src/daemon.js +8 -0
- package/src/installer.js +6 -1
- package/src/update-check.js +7 -2
package/package.json
CHANGED
package/src/adapters/base.js
CHANGED
|
@@ -93,7 +93,10 @@ class BaseAdapter {
|
|
|
93
93
|
|
|
94
94
|
// Sync workspace-managed skills into disabledModules
|
|
95
95
|
try {
|
|
96
|
-
const agents = await
|
|
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');
|
package/src/adapters/claude.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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];
|
package/src/adapters/cursor.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/adapters/gemini.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
}
|
package/src/update-check.js
CHANGED
|
@@ -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
|
-
|
|
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
|