@openagents-org/agent-launcher 0.2.128 → 0.2.130
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/registry.json +18 -23
- package/src/adapters/base.js +111 -2
- package/src/adapters/claude.js +1 -1
- package/src/adapters/cursor.js +599 -9
- package/src/adapters/utils.js +34 -10
- package/src/adapters/workspace-prompt.js +199 -66
- package/src/mcp-server.js +8 -1
- package/src/workspace-client.js +27 -0
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -209,49 +209,44 @@
|
|
|
209
209
|
"order": 4,
|
|
210
210
|
"builtin": true,
|
|
211
211
|
"install": {
|
|
212
|
-
"binary": "
|
|
213
|
-
"verify": "
|
|
214
|
-
"verify_win": "
|
|
215
|
-
"requires": [
|
|
216
|
-
null
|
|
217
|
-
],
|
|
212
|
+
"binary": "agent",
|
|
213
|
+
"verify": "agent --version 2>/dev/null | head -1",
|
|
214
|
+
"verify_win": "agent --version 2>nul",
|
|
215
|
+
"requires": [],
|
|
218
216
|
"macos": "curl https://cursor.com/install -fsSL | bash",
|
|
219
217
|
"linux": "curl https://cursor.com/install -fsSL | bash",
|
|
220
|
-
"windows": "
|
|
218
|
+
"windows": "irm 'https://cursor.com/install?win32=true' | iex",
|
|
219
|
+
"npm": "npm install -g @cursor/cli"
|
|
221
220
|
},
|
|
222
221
|
"adapter": {
|
|
223
222
|
"module": "openagents.adapters.cursor",
|
|
224
223
|
"class": "CursorAdapter"
|
|
225
224
|
},
|
|
226
225
|
"launch": {
|
|
227
|
-
"args": [
|
|
228
|
-
null
|
|
229
|
-
]
|
|
226
|
+
"args": []
|
|
230
227
|
},
|
|
231
228
|
"env_config": [
|
|
232
229
|
{
|
|
233
|
-
"name": "
|
|
234
|
-
"description": "API key for
|
|
235
|
-
"required":
|
|
230
|
+
"name": "CURSOR_API_KEY",
|
|
231
|
+
"description": "Cursor API key for CLI authentication",
|
|
232
|
+
"required": false,
|
|
236
233
|
"password": true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"name": "CURSOR_MODEL",
|
|
237
|
+
"description": "Model to use (e.g. claude-sonnet-4-6, gpt-4o)",
|
|
238
|
+
"required": false
|
|
237
239
|
}
|
|
238
240
|
],
|
|
239
241
|
"check_ready": {
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
],
|
|
243
|
-
"saved_env_key": "OPENAI_API_KEY",
|
|
244
|
-
"not_ready_message": "No API key \u2014 press e to configure"
|
|
242
|
+
"binary": "agent",
|
|
243
|
+
"not_ready_message": "Cursor CLI not found \u2014 install with: curl https://cursor.com/install -fsSL | bash"
|
|
245
244
|
},
|
|
246
245
|
"resolve_env": {
|
|
247
246
|
"rules": [
|
|
248
247
|
{
|
|
249
248
|
"from": "LLM_API_KEY",
|
|
250
|
-
"to": "
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
"from": "LLM_BASE_URL",
|
|
254
|
-
"to": "OPENAI_BASE_URL"
|
|
249
|
+
"to": "CURSOR_API_KEY"
|
|
255
250
|
},
|
|
256
251
|
{
|
|
257
252
|
"from": "LLM_MODEL",
|
package/src/adapters/base.js
CHANGED
|
@@ -42,6 +42,7 @@ class BaseAdapter {
|
|
|
42
42
|
this.workingDir = workingDir || undefined;
|
|
43
43
|
this.client = new WorkspaceClient(this.endpoint);
|
|
44
44
|
this._lastEventId = null;
|
|
45
|
+
this._lastToolResultId = null;
|
|
45
46
|
this._running = false;
|
|
46
47
|
this._sessionId = null; // issued by server on /v1/join; used to prove liveness
|
|
47
48
|
this._processedIds = new Set();
|
|
@@ -358,6 +359,31 @@ class BaseAdapter {
|
|
|
358
359
|
idleCount++;
|
|
359
360
|
}
|
|
360
361
|
|
|
362
|
+
// Sidecar poll: A2UI tool_result events. These are the user's response
|
|
363
|
+
// to a UI spec this agent (or any agent in the network) emitted. We
|
|
364
|
+
// surface each one as a synthetic user message so the LLM sees it as
|
|
365
|
+
// the next turn and can react. Failures here don't break the main
|
|
366
|
+
// message poll.
|
|
367
|
+
try {
|
|
368
|
+
const toolResult = await this.client.pollToolResults(
|
|
369
|
+
this.workspaceId, this.token,
|
|
370
|
+
{ after: this._lastToolResultId }
|
|
371
|
+
);
|
|
372
|
+
if (toolResult.cursor) this._lastToolResultId = toolResult.cursor;
|
|
373
|
+
for (const event of toolResult.events || []) {
|
|
374
|
+
const msgId = event.id;
|
|
375
|
+
if (msgId && this._processedIds.has(msgId)) continue;
|
|
376
|
+
if (msgId) this._processedIds.add(msgId);
|
|
377
|
+
const synth = synthesizeToolResultMessage(event);
|
|
378
|
+
if (synth) await this._dispatchMessage(synth);
|
|
379
|
+
}
|
|
380
|
+
} catch (e) {
|
|
381
|
+
// Non-fatal — log once per poll if it fails
|
|
382
|
+
if (pollCount <= 3 || pollCount % 20 === 0) {
|
|
383
|
+
this._log(`tool_result poll #${pollCount} failed: ${e.message}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
361
387
|
// Adaptive polling: 2s active, up to 15s idle.
|
|
362
388
|
// Each connected agent runs this loop, so faster rates multiply across
|
|
363
389
|
// every workspace member — keep this conservative and tune separately
|
|
@@ -421,6 +447,9 @@ class BaseAdapter {
|
|
|
421
447
|
const queue = this._channelQueues[channel];
|
|
422
448
|
if (!queue || queue.length === 0) break;
|
|
423
449
|
const nextMsg = queue.shift();
|
|
450
|
+
if (nextMsg._queueId) {
|
|
451
|
+
try { await this.sendStatus(channel, 'processing queued message', { queue_id: nextMsg._queueId, queue_status: 'processed' }); } catch {}
|
|
452
|
+
}
|
|
424
453
|
try {
|
|
425
454
|
await this._handleMessage(nextMsg);
|
|
426
455
|
} catch (e) {
|
|
@@ -473,8 +502,15 @@ class BaseAdapter {
|
|
|
473
502
|
}
|
|
474
503
|
|
|
475
504
|
async sendThinking(channel, content) {
|
|
505
|
+
// Strip ```a2ui blocks if they leak into Claude's intermediate thinking
|
|
506
|
+
// trace — the real spec gets emitted via sendResponse with proper
|
|
507
|
+
// payload.spec extraction, so showing the raw block here is just noise
|
|
508
|
+
// (and a duplicate). If stripping leaves the thinking message empty,
|
|
509
|
+
// skip it entirely.
|
|
510
|
+
const { cleanContent } = extractA2UISpec(content);
|
|
511
|
+
if (!cleanContent || !cleanContent.trim()) return;
|
|
476
512
|
try {
|
|
477
|
-
await this.client.sendMessage(this.workspaceId, channel, this.token,
|
|
513
|
+
await this.client.sendMessage(this.workspaceId, channel, this.token, cleanContent, {
|
|
478
514
|
senderType: 'agent',
|
|
479
515
|
senderName: this.agentName,
|
|
480
516
|
messageType: 'thinking',
|
|
@@ -487,11 +523,14 @@ class BaseAdapter {
|
|
|
487
523
|
}
|
|
488
524
|
|
|
489
525
|
async sendResponse(channel, content) {
|
|
526
|
+
const { cleanContent, spec, specToolCallId } = extractA2UISpec(content);
|
|
490
527
|
try {
|
|
491
|
-
await this.client.sendMessage(this.workspaceId, channel, this.token,
|
|
528
|
+
await this.client.sendMessage(this.workspaceId, channel, this.token, cleanContent, {
|
|
492
529
|
senderType: 'agent',
|
|
493
530
|
senderName: this.agentName,
|
|
494
531
|
sessionId: this._sessionId,
|
|
532
|
+
spec,
|
|
533
|
+
specToolCallId,
|
|
495
534
|
});
|
|
496
535
|
} catch (e) {
|
|
497
536
|
if (e instanceof SessionRevokedError) {
|
|
@@ -599,4 +638,74 @@ class BaseAdapter {
|
|
|
599
638
|
}
|
|
600
639
|
}
|
|
601
640
|
|
|
641
|
+
// ------------------------------------------------------------------
|
|
642
|
+
// A2UI helpers
|
|
643
|
+
// ------------------------------------------------------------------
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Pull the first ```a2ui ... ``` fenced block out of LLM-produced content.
|
|
647
|
+
* Returns the content with the block stripped, the parsed spec, and a
|
|
648
|
+
* tool-call id derived from `spec.tool_call_id` (if present) or a new one.
|
|
649
|
+
* If no block is present or parsing fails, returns the content unchanged
|
|
650
|
+
* with null spec — the message still goes out as plain markdown.
|
|
651
|
+
*/
|
|
652
|
+
/**
|
|
653
|
+
* Convert a workspace.tool_result event into a synthetic user-message
|
|
654
|
+
* shape that the agent's _handleMessage can dispatch. The LLM sees this
|
|
655
|
+
* as the next user turn — the content is a short, machine-readable line
|
|
656
|
+
* the LLM can parse without ambiguity. The original spec it emitted is
|
|
657
|
+
* already in the LLM's conversation history; the tool_call_id lets the
|
|
658
|
+
* LLM correlate this back.
|
|
659
|
+
*/
|
|
660
|
+
function synthesizeToolResultMessage(event) {
|
|
661
|
+
if (!event || !event.payload) return null;
|
|
662
|
+
const p = event.payload;
|
|
663
|
+
const actionId = p.action_id || '';
|
|
664
|
+
const toolCallId = p.tool_call_id || '';
|
|
665
|
+
let valueStr = '';
|
|
666
|
+
if (p.value !== undefined && p.value !== null) {
|
|
667
|
+
try { valueStr = JSON.stringify(p.value); } catch (_) { valueStr = String(p.value); }
|
|
668
|
+
}
|
|
669
|
+
const lines = [
|
|
670
|
+
'[ui_action]',
|
|
671
|
+
`action=${actionId}`,
|
|
672
|
+
toolCallId ? `tool_call_id=${toolCallId}` : null,
|
|
673
|
+
valueStr ? `value=${valueStr}` : null,
|
|
674
|
+
].filter(Boolean);
|
|
675
|
+
const content = lines.join(' ');
|
|
676
|
+
const target = event.target || '';
|
|
677
|
+
return {
|
|
678
|
+
messageId: event.id || '',
|
|
679
|
+
sessionId: target.startsWith('channel/') ? target.replace('channel/', '') : target,
|
|
680
|
+
senderType: 'human',
|
|
681
|
+
senderName: 'user',
|
|
682
|
+
content,
|
|
683
|
+
mentions: [],
|
|
684
|
+
messageType: 'chat',
|
|
685
|
+
metadata: event.metadata || {},
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function extractA2UISpec(content) {
|
|
690
|
+
if (!content || typeof content !== 'string') {
|
|
691
|
+
return { cleanContent: content, spec: null, specToolCallId: null };
|
|
692
|
+
}
|
|
693
|
+
const match = content.match(/```a2ui\s*\n([\s\S]*?)\n```/);
|
|
694
|
+
if (!match) return { cleanContent: content, spec: null, specToolCallId: null };
|
|
695
|
+
|
|
696
|
+
let spec;
|
|
697
|
+
try {
|
|
698
|
+
spec = JSON.parse(match[1]);
|
|
699
|
+
} catch (_) {
|
|
700
|
+
return { cleanContent: content, spec: null, specToolCallId: null };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const specToolCallId = (spec && spec.tool_call_id) || `tc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
704
|
+
if (spec && spec.tool_call_id) delete spec.tool_call_id;
|
|
705
|
+
|
|
706
|
+
const cleanContent = content.replace(match[0], '').trim();
|
|
707
|
+
return { cleanContent, spec, specToolCallId };
|
|
708
|
+
}
|
|
709
|
+
|
|
602
710
|
module.exports = BaseAdapter;
|
|
711
|
+
module.exports.extractA2UISpec = extractA2UISpec;
|
package/src/adapters/claude.js
CHANGED
|
@@ -569,7 +569,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
569
569
|
let content = (msg.content || '').trim();
|
|
570
570
|
const attachments = msg.attachments || [];
|
|
571
571
|
|
|
572
|
-
const attText = formatAttachmentsForPrompt(attachments);
|
|
572
|
+
const attText = formatAttachmentsForPrompt(attachments, this.toolMode);
|
|
573
573
|
if (attText) {
|
|
574
574
|
content = content ? content + attText : attText.trim();
|
|
575
575
|
}
|