@openagents-org/agent-launcher 0.2.127 → 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 +137 -5
- 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
|
@@ -78,11 +78,12 @@ function buildModePrompt(mode) {
|
|
|
78
78
|
*
|
|
79
79
|
* In plan mode, only read-only operations are documented.
|
|
80
80
|
*/
|
|
81
|
-
function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode = 'execute' }) {
|
|
81
|
+
function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode = 'execute', isWindows = process.platform === 'win32' }) {
|
|
82
82
|
const disabled = disabledModules || new Set();
|
|
83
83
|
const baseUrl = endpoint.replace(/\/+$/, '');
|
|
84
84
|
const isPlan = mode === 'plan';
|
|
85
85
|
const h = `X-Workspace-Token: ${token}`;
|
|
86
|
+
const curl = isWindows ? 'curl.exe' : 'curl';
|
|
86
87
|
|
|
87
88
|
const sections = [];
|
|
88
89
|
|
|
@@ -115,34 +116,53 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
115
116
|
let s = '\n### Shared Files\n\n';
|
|
116
117
|
|
|
117
118
|
if (!isPlan) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
if (isWindows) {
|
|
120
|
+
s += (
|
|
121
|
+
'**To upload a file**, exec this (replace filename/content):\n' +
|
|
122
|
+
`$CONTENT = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('YOUR_CONTENT'))\n` +
|
|
123
|
+
`${curl} -s -X POST ${baseUrl}/v1/files/base64 ` +
|
|
124
|
+
`-H "${h}" ` +
|
|
125
|
+
'-H "Content-Type: application/json" ' +
|
|
126
|
+
`-d "{\\"filename\\":\\"report.md\\",` +
|
|
127
|
+
`\\"content_base64\\":\\"$CONTENT\\",` +
|
|
128
|
+
`\\"content_type\\":\\"text/markdown\\",` +
|
|
129
|
+
`\\"network\\":\\"${workspaceId}\\",` +
|
|
130
|
+
`\\"source\\":\\"openagents:${agentName}\\",` +
|
|
131
|
+
`\\"channel_name\\":\\"${channelName}\\"}"\n\n`
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
s += (
|
|
135
|
+
'**To upload a file**, exec this (replace filename/content):\n' +
|
|
136
|
+
`CONTENT=$(echo -n 'YOUR_CONTENT' | base64) && ` +
|
|
137
|
+
`${curl} -s -X POST ${baseUrl}/v1/files/base64 ` +
|
|
138
|
+
`-H "${h}" ` +
|
|
139
|
+
'-H "Content-Type: application/json" ' +
|
|
140
|
+
`-d '{"filename":"report.md",` +
|
|
141
|
+
`"content_base64":"'"$CONTENT"'",` +
|
|
142
|
+
`"content_type":"text/markdown",` +
|
|
143
|
+
`"network":"${workspaceId}",` +
|
|
144
|
+
`"source":"openagents:${agentName}",` +
|
|
145
|
+
`"channel_name":"${channelName}"}'\n\n`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
149
|
|
|
150
|
+
const tmpDir = isWindows ? '$env:TEMP' : '/tmp';
|
|
133
151
|
s += (
|
|
134
152
|
'**List files:**\n' +
|
|
135
|
-
|
|
136
|
-
'**Download file:**\n' +
|
|
137
|
-
|
|
153
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/files?network=${workspaceId}\`\n\n` +
|
|
154
|
+
'**Download file (text):**\n' +
|
|
155
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n\n` +
|
|
156
|
+
'**Download file (binary/images) — save to disk, then use Read tool to view:**\n' +
|
|
157
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id} -o ${tmpDir}/{filename}\`\n\n` +
|
|
138
158
|
'**File info (metadata):**\n' +
|
|
139
|
-
|
|
159
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id}/info\`\n`
|
|
140
160
|
);
|
|
141
161
|
|
|
142
162
|
if (!isPlan) {
|
|
143
163
|
s += (
|
|
144
164
|
'\n**Delete file:**\n' +
|
|
145
|
-
|
|
165
|
+
`\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n`
|
|
146
166
|
);
|
|
147
167
|
}
|
|
148
168
|
|
|
@@ -157,48 +177,48 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
157
177
|
s += (
|
|
158
178
|
'**To browse a website**, exec these steps (use exec for each):\n' +
|
|
159
179
|
`Step 1 — open tab: ` +
|
|
160
|
-
|
|
180
|
+
`${curl} -s -X POST ${baseUrl}/v1/browser/tabs ` +
|
|
161
181
|
`-H "${h}" -H "Content-Type: application/json" ` +
|
|
162
182
|
`-d '{"url":"https://example.com","network":"${workspaceId}",` +
|
|
163
183
|
`"source":"openagents:${agentName}"}'\n` +
|
|
164
184
|
`Step 2 — read content: ` +
|
|
165
|
-
|
|
185
|
+
`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID/snapshot\n` +
|
|
166
186
|
`Step 3 — close tab: ` +
|
|
167
|
-
|
|
187
|
+
`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID\n` +
|
|
168
188
|
'(Replace TAB_ID with the id from step 1 response)\n\n'
|
|
169
189
|
);
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
s += (
|
|
173
193
|
'**List open tabs:**\n' +
|
|
174
|
-
|
|
194
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs?network=${workspaceId}\`\n\n` +
|
|
175
195
|
'**Get page content (text):**\n' +
|
|
176
|
-
|
|
196
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/snapshot\`\n\n` +
|
|
177
197
|
'**Get screenshot (PNG):**\n' +
|
|
178
|
-
|
|
198
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/screenshot\`\n`
|
|
179
199
|
);
|
|
180
200
|
|
|
181
201
|
if (!isPlan) {
|
|
182
202
|
s += (
|
|
183
203
|
'\n**Open tab:**\n' +
|
|
184
|
-
|
|
204
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
|
|
185
205
|
` ${baseUrl}/v1/browser/tabs` +
|
|
186
206
|
` -d '{"url":"URL","network":"${workspaceId}",` +
|
|
187
207
|
`"source":"openagents:${agentName}"}'\`\n\n` +
|
|
188
208
|
'**Navigate:**\n' +
|
|
189
|
-
|
|
209
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
|
|
190
210
|
` ${baseUrl}/v1/browser/tabs/{tab_id}/navigate` +
|
|
191
211
|
` -d '{"url":"URL"}'\`\n\n` +
|
|
192
212
|
'**Click element:**\n' +
|
|
193
|
-
|
|
213
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
|
|
194
214
|
` ${baseUrl}/v1/browser/tabs/{tab_id}/click` +
|
|
195
215
|
` -d '{"selector":"CSS_SELECTOR"}'\`\n\n` +
|
|
196
216
|
'**Type text:**\n' +
|
|
197
|
-
|
|
217
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
|
|
198
218
|
` ${baseUrl}/v1/browser/tabs/{tab_id}/type` +
|
|
199
219
|
` -d '{"selector":"CSS_SELECTOR","text":"TEXT"}'\`\n\n` +
|
|
200
220
|
'**Close tab:**\n' +
|
|
201
|
-
|
|
221
|
+
`\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}\`\n`
|
|
202
222
|
);
|
|
203
223
|
}
|
|
204
224
|
|
|
@@ -209,9 +229,9 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
209
229
|
sections.push(
|
|
210
230
|
'\n### Message History\n\n' +
|
|
211
231
|
'**Get recent messages in the current channel:**\n' +
|
|
212
|
-
|
|
232
|
+
`\`${curl} -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=${channelName}&type=workspace.message&sort=desc&limit=20"\`\n\n` +
|
|
213
233
|
'**Get messages from a specific channel:**\n' +
|
|
214
|
-
|
|
234
|
+
`\`${curl} -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=CHANNEL_NAME&type=workspace.message&sort=desc&limit=20"\`\n`
|
|
215
235
|
);
|
|
216
236
|
|
|
217
237
|
// Post status update
|
|
@@ -219,7 +239,7 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
219
239
|
sections.push(
|
|
220
240
|
'\n### Post Status Update\n\n' +
|
|
221
241
|
'Post a status/thinking message (visible in the workspace UI as an intermediate step):\n' +
|
|
222
|
-
|
|
242
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
|
|
223
243
|
`${baseUrl}/v1/events -d '{"type":"workspace.message.posted",` +
|
|
224
244
|
`"source":"openagents:${agentName}","target":"channel/${channelName}",` +
|
|
225
245
|
`"payload":{"content":"YOUR_STATUS","message_type":"status"}}'\`\n`
|
|
@@ -234,14 +254,14 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
234
254
|
'is replaced each time (send the full list with current statuses).\n\n' +
|
|
235
255
|
'**Status values:** `pending`, `in_progress`, `completed`\n\n' +
|
|
236
256
|
'**Update your to-do list:**\n' +
|
|
237
|
-
|
|
257
|
+
`\`${curl} -s -X PUT -H "${h}" -H "Content-Type: application/json" ` +
|
|
238
258
|
`${baseUrl}/v1/todos -d '{"todos":[` +
|
|
239
259
|
`{"content":"First task","status":"in_progress"},` +
|
|
240
260
|
`{"content":"Second task","status":"pending"}` +
|
|
241
261
|
`],"network":"${workspaceId}","channel":"${channelName}",` +
|
|
242
262
|
`"source":"openagents:${agentName}"}'\`\n\n` +
|
|
243
263
|
'**Get your to-do list:**\n' +
|
|
244
|
-
|
|
264
|
+
`\`${curl} -s -H "${h}" "${baseUrl}/v1/todos?network=${workspaceId}&channel=${channelName}"\`\n\n` +
|
|
245
265
|
'**IMPORTANT:** When you receive a task with multiple steps or a list of things to do, ' +
|
|
246
266
|
'ALWAYS create a to-do list first before starting work. This lets the user see your ' +
|
|
247
267
|
'progress in real time. Update statuses as you work through each item.\n' +
|
|
@@ -259,14 +279,14 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
259
279
|
'Use cases: check back on a deploy, retry after a rate limit, remind ' +
|
|
260
280
|
'yourself to follow up.\n\n' +
|
|
261
281
|
'**Create a timer:**\n' +
|
|
262
|
-
|
|
282
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
|
|
263
283
|
`${baseUrl}/v1/timers -d '{"delay":300,"message":"Check the build",` +
|
|
264
284
|
`"network":"${workspaceId}","channel":"${channelName}",` +
|
|
265
285
|
`"source":"openagents:${agentName}"}'\`\n\n` +
|
|
266
286
|
'**List active timers:**\n' +
|
|
267
|
-
|
|
287
|
+
`\`${curl} -s -H "${h}" "${baseUrl}/v1/timers?network=${workspaceId}&channel=${channelName}"\`\n\n` +
|
|
268
288
|
'**Cancel a timer:**\n' +
|
|
269
|
-
|
|
289
|
+
`\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/timers/TIMER_ID\`\n`
|
|
270
290
|
);
|
|
271
291
|
}
|
|
272
292
|
|
|
@@ -280,21 +300,21 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
280
300
|
'**Schedule:** Specify `hour` (0-23 UTC), `minute` (0-59), and optional ' +
|
|
281
301
|
'`days` array (0=Mon, 6=Sun). Omit `days` for every day.\n\n' +
|
|
282
302
|
'**Create a routine:**\n' +
|
|
283
|
-
|
|
303
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
|
|
284
304
|
`${baseUrl}/v1/routines -d '{"name":"Daily PR Review","message":"Review open PRs",` +
|
|
285
305
|
`"hour":8,"minute":0,` +
|
|
286
306
|
`"network":"${workspaceId}","channel":"${channelName}",` +
|
|
287
307
|
`"source":"openagents:${agentName}"}'\`\n\n` +
|
|
288
308
|
'**Create a weekday-only routine (Mon-Fri):**\n' +
|
|
289
|
-
|
|
309
|
+
`\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
|
|
290
310
|
`${baseUrl}/v1/routines -d '{"name":"Morning Standup","message":"Post standup summary",` +
|
|
291
311
|
`"hour":9,"minute":0,"days":[0,1,2,3,4],` +
|
|
292
312
|
`"network":"${workspaceId}","channel":"${channelName}",` +
|
|
293
313
|
`"source":"openagents:${agentName}"}'\`\n\n` +
|
|
294
314
|
'**List active routines:**\n' +
|
|
295
|
-
|
|
315
|
+
`\`${curl} -s -H "${h}" "${baseUrl}/v1/routines?network=${workspaceId}&channel=${channelName}"\`\n\n` +
|
|
296
316
|
'**Cancel a routine:**\n' +
|
|
297
|
-
|
|
317
|
+
`\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/routines/ROUTINE_ID\`\n`
|
|
298
318
|
);
|
|
299
319
|
}
|
|
300
320
|
|
|
@@ -302,12 +322,32 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
|
|
|
302
322
|
sections.push(
|
|
303
323
|
'\n### Discover Agents\n\n' +
|
|
304
324
|
'**List all agents in the workspace (with status and role):**\n' +
|
|
305
|
-
|
|
325
|
+
`\`${curl} -s -H "${h}" ${baseUrl}/v1/discover?network=${workspaceId}\`\n`
|
|
306
326
|
);
|
|
307
327
|
|
|
308
328
|
return sections.join('\n');
|
|
309
329
|
}
|
|
310
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Guardrails shared across all adapter prompt builders.
|
|
333
|
+
*/
|
|
334
|
+
function buildGuardrails() {
|
|
335
|
+
return (
|
|
336
|
+
'\nIMPORTANT: Never use AskUserQuestion. ' +
|
|
337
|
+
'AskUserQuestion blocks the subprocess and will hang the thread. ' +
|
|
338
|
+
'If you need to ask the user something, just write the question ' +
|
|
339
|
+
'as your text response.\n' +
|
|
340
|
+
'\nIMPORTANT: When the user gives you a numbered list, bulleted list, or ' +
|
|
341
|
+
'multiple tasks in a single message, you MUST create a to-do list BEFORE ' +
|
|
342
|
+
'doing any work. This is mandatory — no exceptions, even for simple tasks. ' +
|
|
343
|
+
'The to-do list lets the user track your progress in real time.\n' +
|
|
344
|
+
'\nIMPORTANT: Do NOT use built-in scheduling tools (CronCreate, CronDelete, ' +
|
|
345
|
+
'CronList, ScheduleWakeup). For timers, routines, and recurring tasks, ' +
|
|
346
|
+
'ALWAYS use the workspace REST API (curl commands in your skill instructions). ' +
|
|
347
|
+
'Built-in scheduling is local-only and won\'t appear in the workspace.\n'
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
311
351
|
/**
|
|
312
352
|
* Build the system prompt for Claude adapter (MCP-based).
|
|
313
353
|
* Claude gets identity + collaboration instructions but NOT API skills.
|
|
@@ -323,6 +363,7 @@ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = '
|
|
|
323
363
|
'Use workspace_create_routine to set up recurring scheduled tasks (e.g. daily reviews).\n'
|
|
324
364
|
);
|
|
325
365
|
parts.push(buildCollaborationPrompt());
|
|
366
|
+
parts.push(buildA2UIPrompt());
|
|
326
367
|
|
|
327
368
|
if (mode === 'plan') {
|
|
328
369
|
parts.push(
|
|
@@ -331,28 +372,86 @@ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = '
|
|
|
331
372
|
);
|
|
332
373
|
}
|
|
333
374
|
|
|
334
|
-
parts.push(
|
|
335
|
-
'\nIMPORTANT: Never use AskUserQuestion. ' +
|
|
336
|
-
'AskUserQuestion blocks the subprocess and will hang the thread. ' +
|
|
337
|
-
'If you need to ask the user something, just write the question ' +
|
|
338
|
-
'as your text response.\n'
|
|
339
|
-
);
|
|
375
|
+
parts.push(buildGuardrails());
|
|
340
376
|
|
|
341
|
-
parts.
|
|
342
|
-
|
|
343
|
-
'multiple tasks in a single message, you MUST create a to-do list BEFORE ' +
|
|
344
|
-
'doing any work. This is mandatory — no exceptions, even for simple tasks. ' +
|
|
345
|
-
'The to-do list lets the user track your progress in real time.\n'
|
|
346
|
-
);
|
|
377
|
+
return parts.join('\n');
|
|
378
|
+
}
|
|
347
379
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Teach the LLM how to emit interactive UI alongside its text response.
|
|
382
|
+
* The frontend (OpenAgents Go) renders any A2UI-shaped JSON spec inline
|
|
383
|
+
* in the chat bubble; the agent-connector strips the fenced block before
|
|
384
|
+
* posting and ferries the spec via payload.spec.
|
|
385
|
+
*/
|
|
386
|
+
function buildA2UIPrompt() {
|
|
387
|
+
return (
|
|
388
|
+
'\n## Rendering interactive UI\n' +
|
|
389
|
+
'When you want the user to interact with structured UI (a button choice, ' +
|
|
390
|
+
'a form, a chart, a table, a confirmation dialog) instead of just reading ' +
|
|
391
|
+
'text, emit a fenced code block tagged `a2ui` containing a JSON spec. The ' +
|
|
392
|
+
"frontend renders it inline below any markdown narration you write.\n\n" +
|
|
393
|
+
"The spec is a tree of `{ type, props, children?, action? }` nodes. " +
|
|
394
|
+
'Supported component types include `Stack`, `Heading`, `Text`, `Image`, ' +
|
|
395
|
+
'`Icon`, `Button`, `ChoiceList`, `ConfirmDialog`, `AmountInput`, `Card`, ' +
|
|
396
|
+
'`Divider`, `Spacer`, `Alert`, `LineChart`, `PieChart`, `AssetPrice`, ' +
|
|
397
|
+
'`BalanceCard`, `TransactionList`, `TransactionRow`. Unknown types render ' +
|
|
398
|
+
'as a placeholder chip, so feel free to compose freely.\n\n' +
|
|
399
|
+
'Interactive components attach an `action` object with a `name` field you ' +
|
|
400
|
+
'choose. Example:\n\n' +
|
|
401
|
+
'```a2ui\n' +
|
|
402
|
+
'{\n' +
|
|
403
|
+
' "type": "Stack",\n' +
|
|
404
|
+
' "props": { "direction": "vertical", "spacing": 12 },\n' +
|
|
405
|
+
' "children": [\n' +
|
|
406
|
+
' { "type": "Heading", "props": { "text": "Pick a time", "level": 2 } },\n' +
|
|
407
|
+
' { "type": "Button", "props": { "label": "Morning", "action": { "name": "pick_morning" } } },\n' +
|
|
408
|
+
' { "type": "Button", "props": { "label": "Evening", "action": { "name": "pick_evening" } } }\n' +
|
|
409
|
+
' ]\n' +
|
|
410
|
+
'}\n' +
|
|
411
|
+
'```\n\n' +
|
|
412
|
+
'### Exact prop names (these are checked verbatim — guessing will render "No data")\n\n' +
|
|
413
|
+
'Layout / content:\n' +
|
|
414
|
+
'- `Stack` — props: `direction` ("vertical"|"horizontal"), `spacing`, `alignment`\n' +
|
|
415
|
+
'- `Card` — props: `title`, `padding`, `cornerRadius`\n' +
|
|
416
|
+
'- `Divider`, `Spacer` — props: `size`, `orientation`\n' +
|
|
417
|
+
'- `Heading` — props: `text`, `level` (1–4)\n' +
|
|
418
|
+
'- `Text` — props: `content` **(NOT `text`)**, `style`, `weight`, `color`\n' +
|
|
419
|
+
'- `Image` — props: `url`, `name`, `contentMode`, `width`, `height`\n' +
|
|
420
|
+
'- `Icon` — props: `name`, `size`, `color`\n\n' +
|
|
421
|
+
'Interactive:\n' +
|
|
422
|
+
'- `Button` — props: `label`, `style` ("primary"|"secondary"|"destructive"), `icon`, `disabled`, `action: {name, params?}`\n' +
|
|
423
|
+
'- `ConfirmDialog` — props: `title`, `message`, `confirmLabel`, `cancelLabel`, `triggerLabel`, `action`\n' +
|
|
424
|
+
'- `ChoiceList` — props: `question`, `options: [{id, label}]`, `action`\n' +
|
|
425
|
+
'- `AmountInput` — props: `label`, `placeholder`, `currency`, `action`\n' +
|
|
426
|
+
'- `input` (lowercase) — props: `inputType` ("text"|"choice"|"number"|"date"), `id`, `label`, `placeholder`, `options`\n\n' +
|
|
427
|
+
'Feedback:\n' +
|
|
428
|
+
'- `Alert` — props: `title`, `message`, `severity` ("info"|"success"|"warning"|"error"), `dismissible`, `action`\n\n' +
|
|
429
|
+
'Charts (Swift Charts under the hood — needs **non-empty** data with the right key):\n' +
|
|
430
|
+
'- `LineChart` — props: `title`, `color`, `points: [{x, y}]` **(NOT `data`)**\n' +
|
|
431
|
+
'- `PieChart` — props: `title`, `segments: [{label, value, color?}]` **(NOT `slices`)**, `showLegend`\n' +
|
|
432
|
+
'- `chart` (lowercase) — simple sparkline/bar: props: `style` ("sparkline"|"bar"), `data: [number, ...]`, `labels`, `color`\n\n' +
|
|
433
|
+
'Data display:\n' +
|
|
434
|
+
'- `metric` (lowercase) — props: `label`, `value`, `caption`, `captionColor`, `icon`\n' +
|
|
435
|
+
'- `list` (lowercase) — props: `items: [{title, subtitle, trailing, icon}]`, `maxVisible`, `expandLabel`\n' +
|
|
436
|
+
'- `table` (lowercase) — **key-value rows only, not multi-column**: props: `rows: [{label, value}]`, `maxVisible`\n\n' +
|
|
437
|
+
'There is no multi-column data table in the catalog. For tabular data with ' +
|
|
438
|
+
'columns you have two choices: (a) use `list` with title/subtitle/trailing ' +
|
|
439
|
+
'mapped to your columns, or (b) compose a Stack of horizontal Stacks of Text ' +
|
|
440
|
+
'manually. Pick whichever reads better.\n\n' +
|
|
441
|
+
'If your data is empty or you can\'t fit it into the available primitives, ' +
|
|
442
|
+
'fall back to plain markdown — don\'t emit a spec just to wrap text in a card.\n\n' +
|
|
443
|
+
'When the user interacts with a rendered component, you will receive a ' +
|
|
444
|
+
"user message shaped like `[ui_action] action=pick_morning tool_call_id=... value=...`. " +
|
|
445
|
+
"Use the `action` name (which you chose) to decide what to do next; the " +
|
|
446
|
+
"spec you emitted earlier is in your conversation history for context.\n\n" +
|
|
447
|
+
'Use a spec when:\n' +
|
|
448
|
+
'- The user needs to make a discrete choice (offer buttons rather than ' +
|
|
449
|
+
'asking them to type a free-form answer).\n' +
|
|
450
|
+
'- Structured data is easier to scan as a chart or table than as prose.\n' +
|
|
451
|
+
'- You need explicit confirmation before a destructive action.\n' +
|
|
452
|
+
"- A form would gather several fields faster than back-and-forth chat.\n\n" +
|
|
453
|
+
"Don't emit a spec for ordinary prose answers — narration alone is fine.\n"
|
|
353
454
|
);
|
|
354
|
-
|
|
355
|
-
return parts.join('\n');
|
|
356
455
|
}
|
|
357
456
|
|
|
358
457
|
/**
|
|
@@ -362,10 +461,12 @@ function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoi
|
|
|
362
461
|
const parts = [];
|
|
363
462
|
parts.push(buildWorkspaceIdentity(agentName, workspaceId, channelName, mode));
|
|
364
463
|
parts.push(buildCollaborationPrompt());
|
|
464
|
+
parts.push(buildA2UIPrompt());
|
|
365
465
|
parts.push(buildModePrompt(mode));
|
|
366
466
|
parts.push(buildApiSkillsPrompt({
|
|
367
467
|
endpoint, workspaceId, token, agentName, channelName, disabledModules, mode,
|
|
368
468
|
}));
|
|
469
|
+
parts.push(buildGuardrails());
|
|
369
470
|
return parts.join('\n');
|
|
370
471
|
}
|
|
371
472
|
|
|
@@ -394,7 +495,7 @@ function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channel
|
|
|
394
495
|
'---\n\n'
|
|
395
496
|
);
|
|
396
497
|
|
|
397
|
-
return frontmatter + identity + '\n' + collab + '\n' + body;
|
|
498
|
+
return frontmatter + identity + '\n' + collab + '\n' + body + '\n' + buildGuardrails();
|
|
398
499
|
}
|
|
399
500
|
|
|
400
501
|
/**
|
|
@@ -405,7 +506,7 @@ function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoi
|
|
|
405
506
|
const collab = buildCollaborationPrompt();
|
|
406
507
|
const modePrompt = buildModePrompt(mode);
|
|
407
508
|
const api = buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode });
|
|
408
|
-
return identity + '\n' + collab + '\n' + modePrompt + '\n' + api;
|
|
509
|
+
return identity + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
|
|
409
510
|
}
|
|
410
511
|
|
|
411
512
|
/**
|
|
@@ -429,7 +530,7 @@ function buildOpenCodeSkillMd({ endpoint, workspaceId, token, agentName, channel
|
|
|
429
530
|
`You are agent '${agentName}' connected to OpenAgents workspace ${workspaceId}.\n` +
|
|
430
531
|
'Use these APIs via bash + curl to interact with the workspace.\n\n';
|
|
431
532
|
|
|
432
|
-
return frontmatter + identity + api;
|
|
533
|
+
return frontmatter + identity + api + '\n' + buildGuardrails();
|
|
433
534
|
}
|
|
434
535
|
|
|
435
536
|
/**
|
|
@@ -460,13 +561,44 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
|
|
|
460
561
|
' or collaborating with other agents via @mentions.\n' +
|
|
461
562
|
'---\n\n';
|
|
462
563
|
|
|
463
|
-
return frontmatter + identity + '\n' + collab + '\n' + api;
|
|
564
|
+
return frontmatter + identity + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Build a SKILL.md file for Cursor CLI's skill auto-discovery.
|
|
569
|
+
*
|
|
570
|
+
* Written to .cursor/skills/openagents-workspace.md before each CLI spawn.
|
|
571
|
+
* Cursor discovers skills from the .cursor/skills/ directory automatically.
|
|
572
|
+
*/
|
|
573
|
+
function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
|
|
574
|
+
const api = buildApiSkillsPrompt({
|
|
575
|
+
endpoint, workspaceId, token, agentName,
|
|
576
|
+
channelName: channelName || 'general',
|
|
577
|
+
disabledModules,
|
|
578
|
+
mode: 'execute',
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
|
|
582
|
+
const collab = buildCollaborationPrompt();
|
|
583
|
+
|
|
584
|
+
const frontmatter =
|
|
585
|
+
'---\n' +
|
|
586
|
+
'name: openagents-workspace\n' +
|
|
587
|
+
'description: |\n' +
|
|
588
|
+
' OpenAgents Workspace collaboration tools — shared files, browser,\n' +
|
|
589
|
+
' and multi-agent coordination. Use when: sharing files or reports,\n' +
|
|
590
|
+
' browsing websites, reading shared files, checking workspace agents,\n' +
|
|
591
|
+
' or collaborating with other agents via @mentions.\n' +
|
|
592
|
+
'---\n\n';
|
|
593
|
+
|
|
594
|
+
return frontmatter + identity + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
|
|
464
595
|
}
|
|
465
596
|
|
|
466
597
|
module.exports = {
|
|
467
598
|
buildWorkspaceIdentity,
|
|
468
599
|
buildCollaborationPrompt,
|
|
469
600
|
buildModePrompt,
|
|
601
|
+
buildGuardrails,
|
|
470
602
|
buildApiSkillsPrompt,
|
|
471
603
|
buildClaudeSystemPrompt,
|
|
472
604
|
buildOpenclawSystemPrompt,
|
|
@@ -474,4 +606,5 @@ module.exports = {
|
|
|
474
606
|
buildOpenCodeSystemPrompt,
|
|
475
607
|
buildOpenCodeSkillMd,
|
|
476
608
|
buildClaudeSkillMd,
|
|
609
|
+
buildCursorSkillMd,
|
|
477
610
|
};
|
package/src/mcp-server.js
CHANGED
|
@@ -451,7 +451,14 @@ class McpServer {
|
|
|
451
451
|
if (mt === 'status') return null;
|
|
452
452
|
const sender = m.senderName || m.senderType || '';
|
|
453
453
|
const content = m.content || '';
|
|
454
|
-
|
|
454
|
+
let line = `[${sender}] ${content}`;
|
|
455
|
+
if (m.attachments && m.attachments.length > 0) {
|
|
456
|
+
const atts = m.attachments.map((a) =>
|
|
457
|
+
`[Attached: ${a.filename || 'file'} (file_id: ${a.fileId || '?'})]`
|
|
458
|
+
).join(' ');
|
|
459
|
+
line += `\n ${atts}`;
|
|
460
|
+
}
|
|
461
|
+
return line;
|
|
455
462
|
}).filter(Boolean);
|
|
456
463
|
if (!lines.length) return text('No messages yet.');
|
|
457
464
|
return text(lines.join('\n'));
|
package/src/workspace-client.js
CHANGED
|
@@ -156,12 +156,15 @@ class WorkspaceClient {
|
|
|
156
156
|
*/
|
|
157
157
|
async sendMessage(workspaceId, channelName, token, content, {
|
|
158
158
|
senderType = 'agent', senderName, messageType = 'chat', metadata, attachments, sessionId,
|
|
159
|
+
spec, specToolCallId,
|
|
159
160
|
} = {}) {
|
|
160
161
|
const sourcePrefix = senderType === 'agent' ? 'openagents' : 'human';
|
|
161
162
|
const source = senderName ? `${sourcePrefix}:${senderName}` : `${sourcePrefix}:unknown`;
|
|
162
163
|
|
|
163
164
|
const payload = { content, message_type: messageType };
|
|
164
165
|
if (attachments && attachments.length) payload.attachments = attachments;
|
|
166
|
+
if (spec) payload.spec = spec;
|
|
167
|
+
if (specToolCallId) payload.spec_tool_call_id = specToolCallId;
|
|
165
168
|
|
|
166
169
|
return this.sendEvent(workspaceId, {
|
|
167
170
|
type: 'workspace.message.posted',
|
|
@@ -172,6 +175,30 @@ class WorkspaceClient {
|
|
|
172
175
|
}, token, sessionId);
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Poll workspace.tool_result events — the client's response to an agent's
|
|
180
|
+
* render_ui invocation. Returns events directly (no message normalization)
|
|
181
|
+
* because the caller treats them differently than chat messages.
|
|
182
|
+
*/
|
|
183
|
+
async pollToolResults(workspaceId, token, { after, limit = 50 } = {}) {
|
|
184
|
+
const params = new URLSearchParams({
|
|
185
|
+
network: workspaceId,
|
|
186
|
+
type: 'workspace.tool_result',
|
|
187
|
+
limit: String(limit),
|
|
188
|
+
});
|
|
189
|
+
if (after) params.set('after', after);
|
|
190
|
+
|
|
191
|
+
const data = await this._get(`/v1/events?${params}`, this._wsHeaders(token));
|
|
192
|
+
const result = data.data || data;
|
|
193
|
+
const events = (result && result.events) || [];
|
|
194
|
+
|
|
195
|
+
let cursor = null;
|
|
196
|
+
if (events.length > 0) {
|
|
197
|
+
cursor = events[events.length - 1].id || null;
|
|
198
|
+
}
|
|
199
|
+
return { events, cursor };
|
|
200
|
+
}
|
|
201
|
+
|
|
175
202
|
/**
|
|
176
203
|
* Poll messages in a channel via GET /v1/events.
|
|
177
204
|
* @returns {Array} message-compatible objects
|