@openagents-org/agent-launcher 0.2.128 → 0.2.131

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.
@@ -11,6 +11,51 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ /**
15
+ * Strong directive forcing agents to use the workspace browser when the
16
+ * workspace has Browser Fabric enabled. Emitted high in the system prompt
17
+ * so it wins against any earlier guidance that suggests local browsing
18
+ * tools.
19
+ *
20
+ * Returns '' when the toggle is off; the caller can unconditionally
21
+ * concatenate the result.
22
+ *
23
+ * Mirrors `build_browser_directive` in
24
+ * sdk/src/openagents/adapters/workspace_prompt.py.
25
+ */
26
+ function buildBrowserDirective(browserEnabled) {
27
+ if (!browserEnabled) return '';
28
+ return (
29
+ '\n## Browser Use (MANDATORY)\n' +
30
+ 'This workspace has the **shared Browser Fabric session** enabled. ' +
31
+ 'All web browsing MUST go through it so the user can watch the ' +
32
+ 'session live in their right-side panel and so cookies / state ' +
33
+ 'persist across agents.\n\n' +
34
+ '**Use ONLY these tools for any web browsing:**\n' +
35
+ '- `mcp__openagents-workspace__workspace_browser_open`\n' +
36
+ '- `mcp__openagents-workspace__workspace_browser_navigate`\n' +
37
+ '- `mcp__openagents-workspace__workspace_browser_click`\n' +
38
+ '- `mcp__openagents-workspace__workspace_browser_type`\n' +
39
+ '- `mcp__openagents-workspace__workspace_browser_snapshot`\n' +
40
+ '- `mcp__openagents-workspace__workspace_browser_screenshot`\n' +
41
+ '- `mcp__openagents-workspace__workspace_browser_list_tabs`\n' +
42
+ '- `mcp__openagents-workspace__workspace_browser_close`\n' +
43
+ '\n' +
44
+ 'If you don\'t have these MCP tools, use `Bash` + `curl` against ' +
45
+ '`/v1/browser/tabs` (documented below in Shared Browser).\n\n' +
46
+ '**FORBIDDEN — do NOT call any of these:**\n' +
47
+ '- `mcp__browsermcp__*` (any local Browser MCP extension tool)\n' +
48
+ '- `mcp__playwright__*`, `mcp__puppeteer__*`, `mcp__chrome-devtools__*`, or any other local-browser MCP\n' +
49
+ '- `WebFetch`, `WebSearch`, `web_fetch`, `web_search`, or any built-in network/browser tool\n' +
50
+ '\n' +
51
+ 'If a local browser tool errors with "extension isn\'t connected" or ' +
52
+ '"connect your browser", do NOT ask the user to connect anything — ' +
53
+ 'the local extension is irrelevant here. Immediately switch to the ' +
54
+ 'workspace browser tools above. The Browser Fabric session is already ' +
55
+ 'running on the backend.\n'
56
+ );
57
+ }
58
+
14
59
  /**
15
60
  * Build the identity section common to all adapters.
16
61
  */
@@ -78,11 +123,12 @@ function buildModePrompt(mode) {
78
123
  *
79
124
  * In plan mode, only read-only operations are documented.
80
125
  */
81
- function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode = 'execute' }) {
126
+ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode = 'execute', isWindows = process.platform === 'win32' }) {
82
127
  const disabled = disabledModules || new Set();
83
128
  const baseUrl = endpoint.replace(/\/+$/, '');
84
129
  const isPlan = mode === 'plan';
85
130
  const h = `X-Workspace-Token: ${token}`;
131
+ const curl = isWindows ? 'curl.exe' : 'curl';
86
132
 
87
133
  const sections = [];
88
134
 
@@ -115,34 +161,53 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
115
161
  let s = '\n### Shared Files\n\n';
116
162
 
117
163
  if (!isPlan) {
118
- s += (
119
- '**To upload a file**, exec this (replace filename/content):\n' +
120
- `CONTENT=$(echo -n 'YOUR_CONTENT' | base64) && ` +
121
- `curl -s -X POST ${baseUrl}/v1/files/base64 ` +
122
- `-H "${h}" ` +
123
- '-H "Content-Type: application/json" ' +
124
- `-d '{"filename":"report.md",` +
125
- `"content_base64":"'"$CONTENT"'",` +
126
- `"content_type":"text/markdown",` +
127
- `"network":"${workspaceId}",` +
128
- `"source":"openagents:${agentName}",` +
129
- `"channel_name":"${channelName}"}'\n\n`
130
- );
164
+ if (isWindows) {
165
+ s += (
166
+ '**To upload a file**, exec this (replace filename/content):\n' +
167
+ `$CONTENT = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('YOUR_CONTENT'))\n` +
168
+ `${curl} -s -X POST ${baseUrl}/v1/files/base64 ` +
169
+ `-H "${h}" ` +
170
+ '-H "Content-Type: application/json" ' +
171
+ `-d "{\\"filename\\":\\"report.md\\",` +
172
+ `\\"content_base64\\":\\"$CONTENT\\",` +
173
+ `\\"content_type\\":\\"text/markdown\\",` +
174
+ `\\"network\\":\\"${workspaceId}\\",` +
175
+ `\\"source\\":\\"openagents:${agentName}\\",` +
176
+ `\\"channel_name\\":\\"${channelName}\\"}"\n\n`
177
+ );
178
+ } else {
179
+ s += (
180
+ '**To upload a file**, exec this (replace filename/content):\n' +
181
+ `CONTENT=$(echo -n 'YOUR_CONTENT' | base64) && ` +
182
+ `${curl} -s -X POST ${baseUrl}/v1/files/base64 ` +
183
+ `-H "${h}" ` +
184
+ '-H "Content-Type: application/json" ' +
185
+ `-d '{"filename":"report.md",` +
186
+ `"content_base64":"'"$CONTENT"'",` +
187
+ `"content_type":"text/markdown",` +
188
+ `"network":"${workspaceId}",` +
189
+ `"source":"openagents:${agentName}",` +
190
+ `"channel_name":"${channelName}"}'\n\n`
191
+ );
192
+ }
131
193
  }
132
194
 
195
+ const tmpDir = isWindows ? '$env:TEMP' : '/tmp';
133
196
  s += (
134
197
  '**List files:**\n' +
135
- `\`curl -s -H "${h}" ${baseUrl}/v1/files?network=${workspaceId}\`\n\n` +
136
- '**Download file:**\n' +
137
- `\`curl -s -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n\n` +
198
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/files?network=${workspaceId}\`\n\n` +
199
+ '**Download file (text):**\n' +
200
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n\n` +
201
+ '**Download file (binary/images) — save to disk, then use Read tool to view:**\n' +
202
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id} -o ${tmpDir}/{filename}\`\n\n` +
138
203
  '**File info (metadata):**\n' +
139
- `\`curl -s -H "${h}" ${baseUrl}/v1/files/{file_id}/info\`\n`
204
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/files/{file_id}/info\`\n`
140
205
  );
141
206
 
142
207
  if (!isPlan) {
143
208
  s += (
144
209
  '\n**Delete file:**\n' +
145
- `\`curl -s -X DELETE -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n`
210
+ `\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/files/{file_id}\`\n`
146
211
  );
147
212
  }
148
213
 
@@ -157,48 +222,48 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
157
222
  s += (
158
223
  '**To browse a website**, exec these steps (use exec for each):\n' +
159
224
  `Step 1 — open tab: ` +
160
- `curl -s -X POST ${baseUrl}/v1/browser/tabs ` +
225
+ `${curl} -s -X POST ${baseUrl}/v1/browser/tabs ` +
161
226
  `-H "${h}" -H "Content-Type: application/json" ` +
162
227
  `-d '{"url":"https://example.com","network":"${workspaceId}",` +
163
228
  `"source":"openagents:${agentName}"}'\n` +
164
229
  `Step 2 — read content: ` +
165
- `curl -s -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID/snapshot\n` +
230
+ `${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID/snapshot\n` +
166
231
  `Step 3 — close tab: ` +
167
- `curl -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID\n` +
232
+ `${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID\n` +
168
233
  '(Replace TAB_ID with the id from step 1 response)\n\n'
169
234
  );
170
235
  }
171
236
 
172
237
  s += (
173
238
  '**List open tabs:**\n' +
174
- `\`curl -s -H "${h}" ${baseUrl}/v1/browser/tabs?network=${workspaceId}\`\n\n` +
239
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs?network=${workspaceId}\`\n\n` +
175
240
  '**Get page content (text):**\n' +
176
- `\`curl -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/snapshot\`\n\n` +
241
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/snapshot\`\n\n` +
177
242
  '**Get screenshot (PNG):**\n' +
178
- `\`curl -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/screenshot\`\n`
243
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}/screenshot\`\n`
179
244
  );
180
245
 
181
246
  if (!isPlan) {
182
247
  s += (
183
248
  '\n**Open tab:**\n' +
184
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json"` +
249
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
185
250
  ` ${baseUrl}/v1/browser/tabs` +
186
251
  ` -d '{"url":"URL","network":"${workspaceId}",` +
187
252
  `"source":"openagents:${agentName}"}'\`\n\n` +
188
253
  '**Navigate:**\n' +
189
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json"` +
254
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
190
255
  ` ${baseUrl}/v1/browser/tabs/{tab_id}/navigate` +
191
256
  ` -d '{"url":"URL"}'\`\n\n` +
192
257
  '**Click element:**\n' +
193
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json"` +
258
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
194
259
  ` ${baseUrl}/v1/browser/tabs/{tab_id}/click` +
195
260
  ` -d '{"selector":"CSS_SELECTOR"}'\`\n\n` +
196
261
  '**Type text:**\n' +
197
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json"` +
262
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json"` +
198
263
  ` ${baseUrl}/v1/browser/tabs/{tab_id}/type` +
199
264
  ` -d '{"selector":"CSS_SELECTOR","text":"TEXT"}'\`\n\n` +
200
265
  '**Close tab:**\n' +
201
- `\`curl -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}\`\n`
266
+ `\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}\`\n`
202
267
  );
203
268
  }
204
269
 
@@ -209,9 +274,9 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
209
274
  sections.push(
210
275
  '\n### Message History\n\n' +
211
276
  '**Get recent messages in the current channel:**\n' +
212
- `\`curl -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=${channelName}&type=workspace.message&sort=desc&limit=20"\`\n\n` +
277
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=${channelName}&type=workspace.message&sort=desc&limit=20"\`\n\n` +
213
278
  '**Get messages from a specific channel:**\n' +
214
- `\`curl -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=CHANNEL_NAME&type=workspace.message&sort=desc&limit=20"\`\n`
279
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/events?network=${workspaceId}&channel=CHANNEL_NAME&type=workspace.message&sort=desc&limit=20"\`\n`
215
280
  );
216
281
 
217
282
  // Post status update
@@ -219,7 +284,7 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
219
284
  sections.push(
220
285
  '\n### Post Status Update\n\n' +
221
286
  'Post a status/thinking message (visible in the workspace UI as an intermediate step):\n' +
222
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
287
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
223
288
  `${baseUrl}/v1/events -d '{"type":"workspace.message.posted",` +
224
289
  `"source":"openagents:${agentName}","target":"channel/${channelName}",` +
225
290
  `"payload":{"content":"YOUR_STATUS","message_type":"status"}}'\`\n`
@@ -234,14 +299,14 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
234
299
  'is replaced each time (send the full list with current statuses).\n\n' +
235
300
  '**Status values:** `pending`, `in_progress`, `completed`\n\n' +
236
301
  '**Update your to-do list:**\n' +
237
- `\`curl -s -X PUT -H "${h}" -H "Content-Type: application/json" ` +
302
+ `\`${curl} -s -X PUT -H "${h}" -H "Content-Type: application/json" ` +
238
303
  `${baseUrl}/v1/todos -d '{"todos":[` +
239
304
  `{"content":"First task","status":"in_progress"},` +
240
305
  `{"content":"Second task","status":"pending"}` +
241
306
  `],"network":"${workspaceId}","channel":"${channelName}",` +
242
307
  `"source":"openagents:${agentName}"}'\`\n\n` +
243
308
  '**Get your to-do list:**\n' +
244
- `\`curl -s -H "${h}" "${baseUrl}/v1/todos?network=${workspaceId}&channel=${channelName}"\`\n\n` +
309
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/todos?network=${workspaceId}&channel=${channelName}"\`\n\n` +
245
310
  '**IMPORTANT:** When you receive a task with multiple steps or a list of things to do, ' +
246
311
  'ALWAYS create a to-do list first before starting work. This lets the user see your ' +
247
312
  'progress in real time. Update statuses as you work through each item.\n' +
@@ -259,14 +324,14 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
259
324
  'Use cases: check back on a deploy, retry after a rate limit, remind ' +
260
325
  'yourself to follow up.\n\n' +
261
326
  '**Create a timer:**\n' +
262
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
327
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
263
328
  `${baseUrl}/v1/timers -d '{"delay":300,"message":"Check the build",` +
264
329
  `"network":"${workspaceId}","channel":"${channelName}",` +
265
330
  `"source":"openagents:${agentName}"}'\`\n\n` +
266
331
  '**List active timers:**\n' +
267
- `\`curl -s -H "${h}" "${baseUrl}/v1/timers?network=${workspaceId}&channel=${channelName}"\`\n\n` +
332
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/timers?network=${workspaceId}&channel=${channelName}"\`\n\n` +
268
333
  '**Cancel a timer:**\n' +
269
- `\`curl -s -X DELETE -H "${h}" ${baseUrl}/v1/timers/TIMER_ID\`\n`
334
+ `\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/timers/TIMER_ID\`\n`
270
335
  );
271
336
  }
272
337
 
@@ -274,27 +339,30 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
274
339
  if (!isPlan) {
275
340
  sections.push(
276
341
  '\n### Routines (Recurring Tasks)\n\n' +
277
- 'Create a recurring routine that fires on a schedule and posts a message ' +
278
- 'to the channel, waking you to do the work. Great for daily standups, ' +
279
- 'periodic reviews, scheduled checks.\n\n' +
280
- '**Schedule:** Specify `hour` (0-23 UTC), `minute` (0-59), and optional ' +
281
- '`days` array (0=Mon, 6=Sun). Omit `days` for every day.\n\n' +
282
- '**Create a routine:**\n' +
283
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
342
+ 'Create a recurring routine that fires on a schedule. Each routine gets ' +
343
+ '**its own dedicated thread** (`routine:<id>`) so different routines never ' +
344
+ 'interfere, and the full context is preserved.\n\n' +
345
+ '**`context` is required** — provide a thorough description of what the ' +
346
+ 'routine should do, any background info, and relevant details from the ' +
347
+ 'current conversation. This context is posted at the start of the routine\'s ' +
348
+ 'thread every time it fires, so you have full background.\n\n' +
349
+ '**Two schedule modes:**\n' +
350
+ '- **Daily**: `hour` (0-23 UTC) + `minute` (0-59), optional `days` ' +
351
+ 'array (0=Mon, 6=Sun). Omit `days` for every day.\n' +
352
+ '- **Interval**: `interval_minutes` (1-1440). Fires every N minutes. ' +
353
+ 'Mutually exclusive with `hour`/`minute`.\n\n' +
354
+ '**Create a daily routine:**\n' +
355
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
284
356
  `${baseUrl}/v1/routines -d '{"name":"Daily PR Review","message":"Review open PRs",` +
357
+ `"context":"Review all open pull requests on the main repo. Check for merge conflicts, ` +
358
+ `CI failures, and stale PRs older than 3 days. Post a summary to the workspace.",` +
285
359
  `"hour":8,"minute":0,` +
286
- `"network":"${workspaceId}","channel":"${channelName}",` +
287
- `"source":"openagents:${agentName}"}'\`\n\n` +
288
- '**Create a weekday-only routine (Mon-Fri):**\n' +
289
- `\`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
290
- `${baseUrl}/v1/routines -d '{"name":"Morning Standup","message":"Post standup summary",` +
291
- `"hour":9,"minute":0,"days":[0,1,2,3,4],` +
292
- `"network":"${workspaceId}","channel":"${channelName}",` +
360
+ `"network":"${workspaceId}",` +
293
361
  `"source":"openagents:${agentName}"}'\`\n\n` +
294
362
  '**List active routines:**\n' +
295
- `\`curl -s -H "${h}" "${baseUrl}/v1/routines?network=${workspaceId}&channel=${channelName}"\`\n\n` +
363
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/routines?network=${workspaceId}"\`\n\n` +
296
364
  '**Cancel a routine:**\n' +
297
- `\`curl -s -X DELETE -H "${h}" ${baseUrl}/v1/routines/ROUTINE_ID\`\n`
365
+ `\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/routines/ROUTINE_ID\`\n`
298
366
  );
299
367
  }
300
368
 
@@ -302,17 +370,37 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
302
370
  sections.push(
303
371
  '\n### Discover Agents\n\n' +
304
372
  '**List all agents in the workspace (with status and role):**\n' +
305
- `\`curl -s -H "${h}" ${baseUrl}/v1/discover?network=${workspaceId}\`\n`
373
+ `\`${curl} -s -H "${h}" ${baseUrl}/v1/discover?network=${workspaceId}\`\n`
306
374
  );
307
375
 
308
376
  return sections.join('\n');
309
377
  }
310
378
 
379
+ /**
380
+ * Guardrails shared across all adapter prompt builders.
381
+ */
382
+ function buildGuardrails() {
383
+ return (
384
+ '\nIMPORTANT: Never use AskUserQuestion. ' +
385
+ 'AskUserQuestion blocks the subprocess and will hang the thread. ' +
386
+ 'If you need to ask the user something, just write the question ' +
387
+ 'as your text response.\n' +
388
+ '\nIMPORTANT: When the user gives you a numbered list, bulleted list, or ' +
389
+ 'multiple tasks in a single message, you MUST create a to-do list BEFORE ' +
390
+ 'doing any work. This is mandatory — no exceptions, even for simple tasks. ' +
391
+ 'The to-do list lets the user track your progress in real time.\n' +
392
+ '\nIMPORTANT: Do NOT use built-in scheduling tools (CronCreate, CronDelete, ' +
393
+ 'CronList, ScheduleWakeup). For timers, routines, and recurring tasks, ' +
394
+ 'ALWAYS use the workspace REST API (curl commands in your skill instructions). ' +
395
+ 'Built-in scheduling is local-only and won\'t appear in the workspace.\n'
396
+ );
397
+ }
398
+
311
399
  /**
312
400
  * Build the system prompt for Claude adapter (MCP-based).
313
401
  * Claude gets identity + collaboration instructions but NOT API skills.
314
402
  */
315
- function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = 'execute' }) {
403
+ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = 'execute', browserEnabled = false }) {
316
404
  const parts = [];
317
405
  parts.push(buildWorkspaceIdentity(agentName, workspaceId, channelName, mode));
318
406
  parts.push(
@@ -322,7 +410,9 @@ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = '
322
410
  'Use workspace_create_timer to set a reminder that wakes you up later.\n' +
323
411
  'Use workspace_create_routine to set up recurring scheduled tasks (e.g. daily reviews).\n'
324
412
  );
413
+ parts.push(buildBrowserDirective(browserEnabled));
325
414
  parts.push(buildCollaborationPrompt());
415
+ parts.push(buildA2UIPrompt());
326
416
 
327
417
  if (mode === 'plan') {
328
418
  parts.push(
@@ -331,53 +421,115 @@ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = '
331
421
  );
332
422
  }
333
423
 
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
- );
424
+ parts.push(buildGuardrails());
340
425
 
341
- parts.push(
342
- '\nIMPORTANT: When the user gives you a numbered list, bulleted list, or ' +
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
- );
426
+ return parts.join('\n');
427
+ }
347
428
 
348
- parts.push(
349
- '\nIMPORTANT: Do NOT use built-in scheduling tools (CronCreate, CronDelete, ' +
350
- 'CronList, ScheduleWakeup). For timers, routines, and recurring tasks, ' +
351
- 'ALWAYS use the workspace REST API (curl commands in your skill instructions). ' +
352
- 'Built-in scheduling is local-only and won\'t appear in the workspace.\n'
429
+ /**
430
+ * Teach the LLM how to emit interactive UI alongside its text response.
431
+ * The frontend (OpenAgents Go) renders any A2UI-shaped JSON spec inline
432
+ * in the chat bubble; the agent-connector strips the fenced block before
433
+ * posting and ferries the spec via payload.spec.
434
+ */
435
+ function buildA2UIPrompt() {
436
+ return (
437
+ '\n## Rendering interactive UI\n' +
438
+ 'When you want the user to interact with structured UI (a button choice, ' +
439
+ 'a form, a chart, a table, a confirmation dialog) instead of just reading ' +
440
+ 'text, emit a fenced code block tagged `a2ui` containing a JSON spec. The ' +
441
+ "frontend renders it inline below any markdown narration you write.\n\n" +
442
+ "The spec is a tree of `{ type, props, children?, action? }` nodes. " +
443
+ 'Supported component types include `Stack`, `Heading`, `Text`, `Image`, ' +
444
+ '`Icon`, `Button`, `ChoiceList`, `ConfirmDialog`, `AmountInput`, `Card`, ' +
445
+ '`Divider`, `Spacer`, `Alert`, `LineChart`, `PieChart`, `AssetPrice`, ' +
446
+ '`BalanceCard`, `TransactionList`, `TransactionRow`. Unknown types render ' +
447
+ 'as a placeholder chip, so feel free to compose freely.\n\n' +
448
+ 'Interactive components attach an `action` object with a `name` field you ' +
449
+ 'choose. Example:\n\n' +
450
+ '```a2ui\n' +
451
+ '{\n' +
452
+ ' "type": "Stack",\n' +
453
+ ' "props": { "direction": "vertical", "spacing": 12 },\n' +
454
+ ' "children": [\n' +
455
+ ' { "type": "Heading", "props": { "text": "Pick a time", "level": 2 } },\n' +
456
+ ' { "type": "Button", "props": { "label": "Morning", "action": { "name": "pick_morning" } } },\n' +
457
+ ' { "type": "Button", "props": { "label": "Evening", "action": { "name": "pick_evening" } } }\n' +
458
+ ' ]\n' +
459
+ '}\n' +
460
+ '```\n\n' +
461
+ '### Exact prop names (these are checked verbatim — guessing will render "No data")\n\n' +
462
+ 'Layout / content:\n' +
463
+ '- `Stack` — props: `direction` ("vertical"|"horizontal"), `spacing`, `alignment`\n' +
464
+ '- `Card` — props: `title`, `padding`, `cornerRadius`\n' +
465
+ '- `Divider`, `Spacer` — props: `size`, `orientation`\n' +
466
+ '- `Heading` — props: `text`, `level` (1–4)\n' +
467
+ '- `Text` — props: `content` **(NOT `text`)**, `style`, `weight`, `color`\n' +
468
+ '- `Image` — props: `url`, `name`, `contentMode`, `width`, `height`\n' +
469
+ '- `Icon` — props: `name`, `size`, `color`\n\n' +
470
+ 'Interactive:\n' +
471
+ '- `Button` — props: `label`, `style` ("primary"|"secondary"|"destructive"), `icon`, `disabled`, `action: {name, params?}`\n' +
472
+ '- `ConfirmDialog` — props: `title`, `message`, `confirmLabel`, `cancelLabel`, `triggerLabel`, `action`\n' +
473
+ '- `ChoiceList` — props: `question`, `options: [{id, label}]`, `action`\n' +
474
+ '- `AmountInput` — props: `label`, `placeholder`, `currency`, `action`\n' +
475
+ '- `input` (lowercase) — props: `inputType` ("text"|"choice"|"number"|"date"), `id`, `label`, `placeholder`, `options`\n\n' +
476
+ 'Feedback:\n' +
477
+ '- `Alert` — props: `title`, `message`, `severity` ("info"|"success"|"warning"|"error"), `dismissible`, `action`\n\n' +
478
+ 'Charts (Swift Charts under the hood — needs **non-empty** data with the right key):\n' +
479
+ '- `LineChart` — props: `title`, `color`, `points: [{x, y}]` **(NOT `data`)**\n' +
480
+ '- `PieChart` — props: `title`, `segments: [{label, value, color?}]` **(NOT `slices`)**, `showLegend`\n' +
481
+ '- `chart` (lowercase) — simple sparkline/bar: props: `style` ("sparkline"|"bar"), `data: [number, ...]`, `labels`, `color`\n\n' +
482
+ 'Data display:\n' +
483
+ '- `metric` (lowercase) — props: `label`, `value`, `caption`, `captionColor`, `icon`\n' +
484
+ '- `list` (lowercase) — props: `items: [{title, subtitle, trailing, icon}]`, `maxVisible`, `expandLabel`\n' +
485
+ '- `table` (lowercase) — **key-value rows only, not multi-column**: props: `rows: [{label, value}]`, `maxVisible`\n\n' +
486
+ 'There is no multi-column data table in the catalog. For tabular data with ' +
487
+ 'columns you have two choices: (a) use `list` with title/subtitle/trailing ' +
488
+ 'mapped to your columns, or (b) compose a Stack of horizontal Stacks of Text ' +
489
+ 'manually. Pick whichever reads better.\n\n' +
490
+ 'If your data is empty or you can\'t fit it into the available primitives, ' +
491
+ 'fall back to plain markdown — don\'t emit a spec just to wrap text in a card.\n\n' +
492
+ 'When the user interacts with a rendered component, you will receive a ' +
493
+ "user message shaped like `[ui_action] action=pick_morning tool_call_id=... value=...`. " +
494
+ "Use the `action` name (which you chose) to decide what to do next; the " +
495
+ "spec you emitted earlier is in your conversation history for context.\n\n" +
496
+ 'Use a spec when:\n' +
497
+ '- The user needs to make a discrete choice (offer buttons rather than ' +
498
+ 'asking them to type a free-form answer).\n' +
499
+ '- Structured data is easier to scan as a chart or table than as prose.\n' +
500
+ '- You need explicit confirmation before a destructive action.\n' +
501
+ "- A form would gather several fields faster than back-and-forth chat.\n\n" +
502
+ "Don't emit a spec for ordinary prose answers — narration alone is fine.\n"
353
503
  );
354
-
355
- return parts.join('\n');
356
504
  }
357
505
 
358
506
  /**
359
507
  * Build the full system prompt for OpenClaw/non-MCP agents.
360
508
  */
361
- function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules }) {
509
+ function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
362
510
  const parts = [];
363
511
  parts.push(buildWorkspaceIdentity(agentName, workspaceId, channelName, mode));
512
+ parts.push(buildBrowserDirective(browserEnabled));
364
513
  parts.push(buildCollaborationPrompt());
514
+ parts.push(buildA2UIPrompt());
365
515
  parts.push(buildModePrompt(mode));
366
516
  parts.push(buildApiSkillsPrompt({
367
517
  endpoint, workspaceId, token, agentName, channelName, disabledModules, mode,
368
518
  }));
519
+ parts.push(buildGuardrails());
369
520
  return parts.join('\n');
370
521
  }
371
522
 
372
523
  /**
373
524
  * Build a SKILL.md file for OpenClaw's skill auto-discovery.
374
525
  */
375
- function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
526
+ function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
376
527
  const body = buildApiSkillsPrompt({
377
528
  endpoint, workspaceId, token, agentName, channelName, disabledModules, mode: 'execute',
378
529
  });
379
530
 
380
531
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
532
+ const directive = buildBrowserDirective(browserEnabled);
381
533
  const collab = buildCollaborationPrompt();
382
534
 
383
535
  const frontmatter = (
@@ -394,18 +546,19 @@ function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channel
394
546
  '---\n\n'
395
547
  );
396
548
 
397
- return frontmatter + identity + '\n' + collab + '\n' + body;
549
+ return frontmatter + identity + directive + '\n' + collab + '\n' + body + '\n' + buildGuardrails();
398
550
  }
399
551
 
400
552
  /**
401
553
  * Build system prompt for OpenCode adapter.
402
554
  */
403
- function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules }) {
555
+ function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
404
556
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, mode);
557
+ const directive = buildBrowserDirective(browserEnabled);
405
558
  const collab = buildCollaborationPrompt();
406
559
  const modePrompt = buildModePrompt(mode);
407
560
  const api = buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode });
408
- return identity + '\n' + collab + '\n' + modePrompt + '\n' + api;
561
+ return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
409
562
  }
410
563
 
411
564
  /**
@@ -429,7 +582,7 @@ function buildOpenCodeSkillMd({ endpoint, workspaceId, token, agentName, channel
429
582
  `You are agent '${agentName}' connected to OpenAgents workspace ${workspaceId}.\n` +
430
583
  'Use these APIs via bash + curl to interact with the workspace.\n\n';
431
584
 
432
- return frontmatter + identity + api;
585
+ return frontmatter + identity + api + '\n' + buildGuardrails();
433
586
  }
434
587
 
435
588
  /**
@@ -439,7 +592,38 @@ function buildOpenCodeSkillMd({ endpoint, workspaceId, token, agentName, channel
439
592
  * of spawning an MCP server. Claude Code discovers the skill via its
440
593
  * .claude/skills/ directory and uses Bash + curl to call workspace APIs.
441
594
  */
442
- function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
595
+ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
596
+ const api = buildApiSkillsPrompt({
597
+ endpoint, workspaceId, token, agentName,
598
+ channelName: channelName || 'general',
599
+ disabledModules,
600
+ mode: 'execute',
601
+ });
602
+
603
+ const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
604
+ const directive = buildBrowserDirective(browserEnabled);
605
+ const collab = buildCollaborationPrompt();
606
+
607
+ const frontmatter =
608
+ '---\n' +
609
+ 'name: openagents-workspace\n' +
610
+ 'description: |\n' +
611
+ ' OpenAgents Workspace collaboration tools — shared files, browser,\n' +
612
+ ' and multi-agent coordination. Use when: sharing files or reports,\n' +
613
+ ' browsing websites, reading shared files, checking workspace agents,\n' +
614
+ ' or collaborating with other agents via @mentions.\n' +
615
+ '---\n\n';
616
+
617
+ return frontmatter + identity + directive + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
618
+ }
619
+
620
+ /**
621
+ * Build a SKILL.md file for Cursor CLI's skill auto-discovery.
622
+ *
623
+ * Written to .cursor/skills/openagents-workspace.md before each CLI spawn.
624
+ * Cursor discovers skills from the .cursor/skills/ directory automatically.
625
+ */
626
+ function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
443
627
  const api = buildApiSkillsPrompt({
444
628
  endpoint, workspaceId, token, agentName,
445
629
  channelName: channelName || 'general',
@@ -448,6 +632,7 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
448
632
  });
449
633
 
450
634
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
635
+ const directive = buildBrowserDirective(browserEnabled);
451
636
  const collab = buildCollaborationPrompt();
452
637
 
453
638
  const frontmatter =
@@ -460,13 +645,15 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
460
645
  ' or collaborating with other agents via @mentions.\n' +
461
646
  '---\n\n';
462
647
 
463
- return frontmatter + identity + '\n' + collab + '\n' + api;
648
+ return frontmatter + identity + directive + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
464
649
  }
465
650
 
466
651
  module.exports = {
467
652
  buildWorkspaceIdentity,
653
+ buildBrowserDirective,
468
654
  buildCollaborationPrompt,
469
655
  buildModePrompt,
656
+ buildGuardrails,
470
657
  buildApiSkillsPrompt,
471
658
  buildClaudeSystemPrompt,
472
659
  buildOpenclawSystemPrompt,
@@ -474,4 +661,5 @@ module.exports = {
474
661
  buildOpenCodeSystemPrompt,
475
662
  buildOpenCodeSkillMd,
476
663
  buildClaudeSkillMd,
664
+ buildCursorSkillMd,
477
665
  };