@openagents-org/agent-launcher 0.2.140 → 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 +68 -10
- 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' +
|
|
@@ -305,6 +306,15 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
305
306
|
if (jsMatch) {
|
|
306
307
|
return [nodeBin, path.resolve(cmdDir, jsMatch[1])];
|
|
307
308
|
}
|
|
309
|
+
// .cmd shims that forward to a native .exe (e.g. Claude Code's
|
|
310
|
+
// claude.cmd → @anthropic-ai/claude-code/bin/claude.exe). Resolve to the
|
|
311
|
+
// exe and spawn it directly. Wrapping such a .cmd in `cmd.exe /c` caps the
|
|
312
|
+
// command line at cmd.exe's 8191-char limit, which truncates the ~14KB
|
|
313
|
+
// --append-system-prompt and makes the agent hang ("command line too long").
|
|
314
|
+
const exeMatch = cmdContent.match(/%dp0%\\([^\s"*?]+\.exe)/i);
|
|
315
|
+
if (exeMatch) {
|
|
316
|
+
return [path.resolve(cmdDir, exeMatch[1])];
|
|
317
|
+
}
|
|
308
318
|
} else {
|
|
309
319
|
// Unix: symlink → resolve to actual .js file
|
|
310
320
|
try {
|
|
@@ -564,7 +574,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
564
574
|
const resolved = this._resolveToNodeCmd(oaBin);
|
|
565
575
|
if (resolved) {
|
|
566
576
|
mcpCommand = resolved[0];
|
|
567
|
-
mcpFinalArgs = [resolved
|
|
577
|
+
mcpFinalArgs = [...resolved.slice(1), ...mcpArgs];
|
|
568
578
|
} else {
|
|
569
579
|
mcpCommand = oaBin;
|
|
570
580
|
}
|
|
@@ -689,7 +699,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
689
699
|
const resolved = this._resolveToNodeCmd(filteredCmd[0]);
|
|
690
700
|
let finalCmd = filteredCmd;
|
|
691
701
|
if (resolved) {
|
|
692
|
-
finalCmd = [resolved
|
|
702
|
+
finalCmd = [...resolved, ...filteredCmd.slice(1)];
|
|
693
703
|
} else if (IS_WINDOWS && filteredCmd[0].toLowerCase().endsWith('.cmd')) {
|
|
694
704
|
finalCmd = ['cmd.exe', '/c', ...filteredCmd];
|
|
695
705
|
}
|
|
@@ -719,6 +729,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
719
729
|
lastStdoutTime: Date.now(),
|
|
720
730
|
watchdogTimer: null,
|
|
721
731
|
awaitingToolResult: false,
|
|
732
|
+
userStopped: false,
|
|
722
733
|
};
|
|
723
734
|
|
|
724
735
|
if (proc.stderr) {
|
|
@@ -974,6 +985,12 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
974
985
|
}
|
|
975
986
|
return;
|
|
976
987
|
}
|
|
988
|
+
if (existingPP.userStopped) {
|
|
989
|
+
if (!existingPP.everPostedAnything) {
|
|
990
|
+
try { await this.sendResponse(msgChannel, 'Execution stopped by user.'); } catch {}
|
|
991
|
+
}
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
977
994
|
// Process died mid-message — fall through to spawn a fresh one
|
|
978
995
|
this._log(`Persistent process died, falling back to fresh spawn for ${msgChannel}`);
|
|
979
996
|
}
|
|
@@ -999,13 +1016,48 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
999
1016
|
}
|
|
1000
1017
|
}
|
|
1001
1018
|
|
|
1019
|
+
// Third-party Anthropic-compatible relays (the common reason a custom
|
|
1020
|
+
// ANTHROPIC_BASE_URL is set) authenticate via `Authorization: Bearer`, which
|
|
1021
|
+
// the Claude CLI only sends when ANTHROPIC_AUTH_TOKEN is set. With just
|
|
1022
|
+
// ANTHROPIC_API_KEY the CLI sends `x-api-key`, which most relays ignore — the
|
|
1023
|
+
// relay then rejects every request as 401 "invalid token / 无效的令牌". When a
|
|
1024
|
+
// non-official base URL is configured and no auth token was provided, mirror
|
|
1025
|
+
// the API key into ANTHROPIC_AUTH_TOKEN (it outranks the API key in Claude
|
|
1026
|
+
// Code's auth precedence) so the CLI uses Bearer auth. The launcher normally
|
|
1027
|
+
// sets this when saving env; this is the runtime backstop for envs saved by
|
|
1028
|
+
// an older launcher or coming from any other source. The official
|
|
1029
|
+
// api.anthropic.com endpoint keeps x-api-key, so it is left untouched.
|
|
1030
|
+
const anthropicBase = (cleanEnv.ANTHROPIC_BASE_URL || '').trim();
|
|
1031
|
+
const anthropicKey = (cleanEnv.ANTHROPIC_API_KEY || '').trim();
|
|
1032
|
+
if (anthropicKey && anthropicBase && !(cleanEnv.ANTHROPIC_AUTH_TOKEN || '').trim()) {
|
|
1033
|
+
let officialAnthropic = false;
|
|
1034
|
+
try {
|
|
1035
|
+
const host = new URL(anthropicBase).hostname.toLowerCase();
|
|
1036
|
+
officialAnthropic = host === 'anthropic.com' || host.endsWith('.anthropic.com');
|
|
1037
|
+
} catch { officialAnthropic = false; }
|
|
1038
|
+
if (!officialAnthropic) {
|
|
1039
|
+
cleanEnv.ANTHROPIC_AUTH_TOKEN = anthropicKey;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1002
1043
|
// Spawn a persistent process and send the first message via stdin
|
|
1003
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
|
+
|
|
1004
1055
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1005
1056
|
if (mcpConfigFile) { try { fs.unlinkSync(mcpConfigFile); } catch {} mcpConfigFile = null; }
|
|
1006
1057
|
|
|
1007
1058
|
if (attempt > 0) {
|
|
1008
1059
|
this._killPersistentProc(msgChannel);
|
|
1060
|
+
// Rebuild recap for retry without resume
|
|
1009
1061
|
try {
|
|
1010
1062
|
const recap = await this._buildChannelRecap(msgChannel, content);
|
|
1011
1063
|
if (recap) effectiveContent = `${recap}\n\n---\n\n${content}`;
|
|
@@ -1032,7 +1084,13 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
1032
1084
|
const result = await this._sendToPersistentProc(pp, effectiveContent);
|
|
1033
1085
|
|
|
1034
1086
|
if (result.exited) {
|
|
1035
|
-
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
|
+
}
|
|
1036
1094
|
if (attempt === 0 && this._channelSessions[msgChannel]) {
|
|
1037
1095
|
this._log(`Stale session detected, retrying without resume`);
|
|
1038
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
|