@openhands/extensions 0.27.0 → 0.29.0

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.
Files changed (70) hide show
  1. package/.agents/skills/custom-codereview-guide.md +31 -0
  2. package/.claude-plugin/marketplace.json +9 -0
  3. package/.codex-plugin/marketplace.json +9 -0
  4. package/.github/workflows/build-vscode-app.yml +3 -3
  5. package/.github/workflows/deprecation-check.yml +1 -1
  6. package/.github/workflows/pypi-publish.yml +2 -2
  7. package/.github/workflows/tests.yml +1 -1
  8. package/.plugin/marketplace.json +9 -0
  9. package/.release-please-manifest.json +1 -1
  10. package/AGENTS.md +7 -2
  11. package/README.md +3 -2
  12. package/automations/bundle-index.js +5 -5
  13. package/automations/catalog/github-issue-to-pr/manifest.json +2 -2
  14. package/automations/catalog/github-issue-triage/manifest.json +2 -2
  15. package/automations/catalog/github-pr-reviewer/manifest.json +2 -2
  16. package/marketplaces/openhands-extensions.json +9 -0
  17. package/package.json +1 -1
  18. package/plugins/openhands/skills/openhands-automation/README.md +6 -4
  19. package/plugins/openhands/skills/openhands-automation/SKILL.md +103 -21
  20. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +79 -12
  21. package/plugins/openhands/skills/openhands-sdk/SKILL.md +2 -1
  22. package/plugins/qa-changes/README.md +4 -0
  23. package/plugins/qa-changes/skills/qa-changes/README.md +3 -1
  24. package/plugins/qa-changes/skills/qa-changes/SKILL.md +42 -0
  25. package/plugins/vulnerability-remediation/skills/github/README.md +5 -1
  26. package/plugins/vulnerability-remediation/skills/github/SKILL.md +27 -0
  27. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +51 -4
  28. package/pyproject.toml +1 -1
  29. package/python/openhands_extensions/_version.py +1 -1
  30. package/scripts/sync_openhands_sdk_skill.py +16 -13
  31. package/skills/azure-devops/README.md +3 -1
  32. package/skills/azure-devops/SKILL.md +4 -0
  33. package/skills/bitbucket/README.md +3 -1
  34. package/skills/bitbucket/SKILL.md +4 -2
  35. package/skills/bitbucket-cloud/README.md +2 -0
  36. package/skills/bitbucket-cloud/SKILL.md +2 -0
  37. package/skills/bitbucket-data-center/README.md +2 -0
  38. package/skills/bitbucket-data-center/SKILL.md +2 -0
  39. package/skills/canvas-extension-api/SKILL.md +4 -1
  40. package/skills/canvas-extension-api/references/native-design.md +250 -0
  41. package/skills/github/README.md +5 -1
  42. package/skills/github/SKILL.md +27 -0
  43. package/skills/github/scripts/agent_conversation.py +51 -4
  44. package/skills/github-issue-to-pr/scripts/agent_conversation.py +51 -4
  45. package/skills/github-issue-triage/scripts/agent_conversation.py +51 -4
  46. package/skills/github-pr-reviewer/README.md +5 -0
  47. package/skills/github-pr-reviewer/SKILL.md +12 -0
  48. package/skills/github-pr-reviewer/scripts/agent_conversation.py +51 -4
  49. package/skills/github-pr-reviewer/scripts/main.py +20 -6
  50. package/skills/github-pr-reviewer/scripts/worker.py +77 -15
  51. package/skills/github-pr-reviewer/tests/test_main.py +23 -0
  52. package/skills/gitlab/README.md +3 -1
  53. package/skills/gitlab/SKILL.md +4 -0
  54. package/skills/index.js +25 -13
  55. package/skills/linear/README.md +2 -0
  56. package/skills/linear/SKILL.md +6 -2
  57. package/skills/openhands-automation/README.md +6 -4
  58. package/skills/openhands-automation/SKILL.md +103 -21
  59. package/skills/openhands-automation/references/custom-automation.md +79 -12
  60. package/skills/openhands-sdk/SKILL.md +2 -1
  61. package/skills/qa-changes/README.md +3 -1
  62. package/skills/qa-changes/SKILL.md +42 -0
  63. package/skills/simplified-technical-english/.claude-plugin/plugin.json +15 -0
  64. package/skills/simplified-technical-english/.codex-plugin/plugin.json +15 -0
  65. package/skills/simplified-technical-english/.plugin/plugin.json +15 -0
  66. package/skills/simplified-technical-english/README.md +21 -0
  67. package/skills/simplified-technical-english/SKILL.md +65 -0
  68. package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
  69. package/tests/test_agent_conversation_dispatch.py +130 -0
  70. package/tests/test_github_reviewer_delivery.py +279 -2
@@ -12,6 +12,8 @@ This skill is activated by the following keywords:
12
12
 
13
13
  ## Details
14
14
 
15
+ Use authenticated Linear MCP tools first when they are available in the agent's environment. Detect the connection through tool availability rather than a particular server name. Fall back to the documented `LINEAR_API_KEY` and direct GraphQL flow when those tools are unavailable or raw API or `curl` access is explicitly needed.
16
+
15
17
  You need access to an environment variable, `LINEAR_API_KEY`, which allows you to interact with the Linear API.
16
18
 
17
19
  <IMPORTANT>
@@ -9,16 +9,20 @@ triggers:
9
9
 
10
10
  # Linear
11
11
 
12
+ ## Connection selection
13
+
14
+ Use authenticated Linear MCP tools first when they are available in the agent's environment. Detect this by tool availability, without depending on a particular MCP server name. Use the API key and direct GraphQL instructions below only when Linear MCP tools are unavailable or when raw API or `curl` access is explicitly needed.
15
+
12
16
  Windows PowerShell equivalents for the repeated Linear GraphQL `curl` and environment-variable snippets are in `references/windows.md`.
13
17
 
14
18
  <IMPORTANT>
15
- Before performing any Linear operations, check if the required environment variable is set:
19
+ Before using the direct API fallback, check if the required environment variable is set:
16
20
 
17
21
  ```bash
18
22
  [ -n "$LINEAR_API_KEY" ] && echo "LINEAR_API_KEY is set" || echo "LINEAR_API_KEY is NOT set"
19
23
  ```
20
24
 
21
- If LINEAR_API_KEY is missing, ask the user to provide it before proceeding.
25
+ If LINEAR_API_KEY is missing and authenticated Linear MCP tools are unavailable, ask the user to provide it before proceeding.
22
26
  </IMPORTANT>
23
27
 
24
28
  ## Understanding Linear Identifiers
@@ -16,13 +16,13 @@ This skill is activated by keywords:
16
16
 
17
17
  ## Features
18
18
 
19
- - **Prompt-based creation**: Create automations from a natural language prompt (recommended)
19
+ - **Prompt-based creation**: Create automations from a natural language prompt, for tasks that benefit from agent reasoning
20
+ - **Custom scripts**: Run your own code, with or without an LLM — the cheaper, more reliable fit for deterministic tasks (fixed data, scheduled HTTP calls, templated messages), and for custom dependencies or full control (see [references/custom-automation.md](references/custom-automation.md))
20
21
  - **Event-triggered automations**: Trigger on GitHub events (PR opened, issue commented, push, etc.)
21
22
  - **Custom webhooks**: Register webhooks for any service (Stripe, Slack, Linear, etc.)
22
23
  - **JMESPath filters**: Match events based on payload content (labels, mentions, repos)
23
24
  - **Automation management**: List, update, enable/disable, and delete automations
24
25
  - **Manual dispatch**: Trigger automation runs on-demand
25
- - **Custom automations**: For advanced users who need full control (see [references/custom-automation.md](references/custom-automation.md))
26
26
 
27
27
  ## API Base URL
28
28
 
@@ -30,6 +30,8 @@ Use an explicitly provided host first. When a local Agent Canvas automation serv
30
30
 
31
31
  ## Quick Start
32
32
 
33
+ The examples below use the prompt preset, which suits tasks that need reasoning or judgment. For a deterministic task that needs no LLM, use a custom script instead — see the *Custom Script Example (No LLM)* in [SKILL.md](SKILL.md).
34
+
33
35
  ### Cron-Triggered Automation
34
36
 
35
37
  ```bash
@@ -81,9 +83,9 @@ curl -X POST "https://app.all-hands.dev/api/automation/v1/preset/prompt" \
81
83
  }'
82
84
  ```
83
85
 
84
- The service handles SDK code generation, tarball packaging, upload, and automation creation automatically.
86
+ For preset automations, the service handles SDK code generation, tarball packaging, upload, and automation creation automatically.
85
87
 
86
88
  ## See Also
87
89
 
88
90
  - [SKILL.md](SKILL.md) — Full API reference, agent behavior rules, event keys, filters, and examples
89
- - [references/custom-automation.md](references/custom-automation.md) — Reference for custom automations with user-provided SDK scripts
91
+ - [references/custom-automation.md](references/custom-automation.md) — Reference for custom automations with user-provided scripts, SDK-based or deterministic (no LLM)
@@ -64,14 +64,14 @@ The agent server typically runs inside a **sandbox** (a Docker or Kubernetes con
64
64
  > 1. **For LLM-appropriate work, default to preset endpoints.** They handle all SDK boilerplate, tarball packaging, and upload automatically:
65
65
  > - **Prompt preset** (`POST /v1/preset/prompt`) — for tasks expressed as a natural language prompt that benefit from agent reasoning
66
66
  > - **Plugin preset** (`POST /v1/preset/plugin`) — when plugins with skills, MCP configs, or commands are needed
67
- > 2. **Do not silently create custom scripts.** Do not generate Python code, `setup.sh` files, or tarball uploads without user consent. But *do* proactively recommend the custom path (per rule 0) when the task is deterministic or high-frequency — surface the option and let the user choose.
68
- > 3. **If neither preset is the right fit**, do NOT silently fall back to custom automation. Instead, explain the available options to the user:
69
- > - **Prompt preset** — natural language prompt execution (LLM-driven)
70
- > - **Plugin preset** — load plugins with extended capabilities (skills, MCP, hooks, commands)
71
- > - **Custom script** — full control over code, with or without LLM; point them to `references/custom-automation.md`
72
- > - Let the user choose which approach to use.
73
- > 4. **Only create custom scripts after the user agrees to that path.** Refer to `references/custom-automation.md` for the full reference.
74
- > 5. **Before suggesting event-triggered (webhook) automations, check whether the deployment is publicly reachable.** Check `RUNTIME_URL`. Webhooks require an internet-accessible URL so that external services (GitHub, Slack, Linear, etc.) can deliver events to the automation service. If `RUNTIME_URL` is unset, empty, or resolves to a local or private address (`localhost`, `127.0.0.1`, `0.0.0.0`, or any RFC 1918 range: `10.x.x.x`, `192.168.x.x`, `172.16–31.x.x`), the service cannot receive inbound webhook traffic from the public internet. In that case:
67
+ > 2. **Present the options, then build the one the user picks.** Whenever rule 0 applies, or neither preset is a clean fit (custom Python dependencies, a non-Python entrypoint, a multi-file project, direct SDK lifecycle control), lay the choices out side by side with a one-line note on cost and reliability for each, and let the user choose:
68
+ > - **Prompt preset** - natural language prompt execution (LLM-driven)
69
+ > - **Plugin preset** - load plugins with extended capabilities (skills, MCP, hooks, commands)
70
+ > - **Custom script** - full control over code, with or without an LLM; see `references/custom-automation.md`
71
+ >
72
+ > Do not silently fall back to either side. The "ready to deploy?" confirmation in the creation process applies to every path equally; a custom script needs no extra permission beyond it, and a preset gets no pass on it.
73
+ > 3. **Building a custom script:** follow `references/custom-automation.md` for tarball packaging, validation, and upload. The *Custom Script Example (No LLM)* below shows the whole flow end to end.
74
+ > 4. **Before suggesting event-triggered (webhook) automations, check whether the deployment is publicly reachable.** Check `RUNTIME_URL`. Webhooks require an internet-accessible URL so that external services (GitHub, Slack, Linear, etc.) can deliver events to the automation service. If `RUNTIME_URL` is unset, empty, or resolves to a local or private address (`localhost`, `127.0.0.1`, `0.0.0.0`, or any RFC 1918 range: `10.x.x.x`, `192.168.x.x`, `172.16–31.x.x`), the service cannot receive inbound webhook traffic from the public internet. In that case:
75
75
  > - **Recommend a cron-based polling automation instead.** Have the automation run on a schedule and call the external service's API (e.g., the GitHub REST API) to check for new events since the last run.
76
76
  > - Explain the limitation clearly to the user: "Because this is a local deployment, external services can't reach the webhook endpoint. I'll set up a polling automation using a cron schedule instead."
77
77
 
@@ -255,8 +255,10 @@ curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
255
255
  Write the prompt as an instruction to an AI agent. The prompt executes inside a sandbox with full tool access (bash, file editing, etc.), the user's configured LLM, stored secrets, and MCP server integrations. Examples:
256
256
 
257
257
  - `"Generate a weekly status report summarizing the team's GitHub activity and post it to Slack"`
258
- - `"Check the production API health endpoint every hour and alert if it returns non-200"`
259
- - `"Pull the latest data from our analytics API and update the dashboard spreadsheet"`
258
+ - `"Read yesterday's new error reports in Sentry, group them by likely root cause, and open one GitHub issue per new failure pattern with a proposed fix"`
259
+ - `"Go through support tickets tagged 'bug' since the last run, try to reproduce each against main, and comment on the ticket with what you found"`
260
+
261
+ Each of these needs judgment: deciding what counts as a pattern, whether a reproduction succeeded, what is worth reporting. A task whose output could be written as a fixed template right now belongs in a custom script instead (see *Custom Script Example (No LLM)* below).
260
262
 
261
263
  #### Cron Schedule
262
264
 
@@ -296,24 +298,105 @@ curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
296
298
  }'
297
299
  ```
298
300
 
299
- **Weekly cleanup:**
301
+ **Weekly dependency review:**
300
302
  ```bash
301
303
  curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
302
304
  -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
303
305
  -H "Content-Type: application/json" \
304
306
  -d '{
305
- "name": "Weekly Cleanup",
306
- "prompt": "Clean up temporary files older than 7 days and send a summary of what was removed",
307
+ "name": "Weekly Dependency Review",
308
+ "prompt": "Check the repository dependencies for new releases. Read each changelog, open one PR with the upgrades that look safe, and list any release with breaking changes in the PR description so a human can decide on it",
307
309
  "trigger": {"type": "cron", "schedule": "0 2 * * 0", "timezone": "UTC"},
308
- "timeout": 300
310
+ "timeout": 1800
309
311
  }'
310
312
  ```
311
313
 
314
+ ### Custom Script Example (No LLM)
315
+
316
+ The same shape as the prompt preset, for a task that needs no reasoning: check a URL on a schedule and post to Slack only when it fails. Every run takes a moment and costs no tokens. This is the complete flow; `get_secret` and `fire_callback` are the helpers from *No-LLM Script Helpers* above.
317
+
318
+ ```bash
319
+ mkdir healthcheck && cd healthcheck
320
+
321
+ cat > main.py << 'PYEOF'
322
+ """Hourly health check: alert Slack on a non-200 response. No LLM, no SDK."""
323
+ import json, os, sys, urllib.request
324
+
325
+ URL = "https://api.example.com/health"
326
+ CHANNEL = "C12345678"
327
+
328
+ def get_secret(name):
329
+ url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
330
+ key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
331
+ if url: # local Agent Canvas
332
+ url = f"{url}/api/settings/secrets/{name}"
333
+ else: # OpenHands Cloud / Enterprise
334
+ api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
335
+ url = f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}"
336
+ req = urllib.request.Request(url,
337
+ headers={"X-Session-API-Key": key})
338
+ with urllib.request.urlopen(req) as r:
339
+ return r.read().decode().strip()
340
+
341
+ def fire_callback(status="COMPLETED", error=None):
342
+ url = os.environ.get("AUTOMATION_CALLBACK_URL", "")
343
+ if not url: return
344
+ body = {"status": status, "run_id": os.environ.get("AUTOMATION_RUN_ID", "")}
345
+ if error: body["error"] = error
346
+ req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={
347
+ "Content-Type": "application/json",
348
+ "Authorization": f"Bearer {os.environ.get('AUTOMATION_CALLBACK_API_KEY', '')}",
349
+ })
350
+ try: urllib.request.urlopen(req)
351
+ except Exception as e: print(f"Callback error: {e}")
352
+
353
+ def slack(text):
354
+ token = get_secret("SLACK_BOT_TOKEN")
355
+ req = urllib.request.Request("https://slack.com/api/chat.postMessage",
356
+ data=json.dumps({"channel": CHANNEL, "text": text}).encode(),
357
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"})
358
+ urllib.request.urlopen(req)
359
+
360
+ try:
361
+ try:
362
+ status = urllib.request.urlopen(URL, timeout=10).status
363
+ except Exception as e:
364
+ status = f"error: {e}"
365
+ if status != 200:
366
+ slack(f":rotating_light: {URL} returned {status}")
367
+ print(f"{URL} -> {status}")
368
+ fire_callback("COMPLETED")
369
+ except Exception as e:
370
+ print(f"ERROR: {e}", file=sys.stderr)
371
+ fire_callback("FAILED", str(e))
372
+ sys.exit(1)
373
+ PYEOF
374
+
375
+ tar -czf ../healthcheck.tar.gz .
376
+
377
+ TARBALL_PATH=$(curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/uploads?name=healthcheck" \
378
+ -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
379
+ -H "Content-Type: application/gzip" \
380
+ --data-binary @../healthcheck.tar.gz | jq -r '.tarball_path')
381
+
382
+ curl -X POST "${OPENHANDS_HOST}/api/automation/v1" \
383
+ -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
384
+ -H "Content-Type: application/json" \
385
+ -d "{
386
+ \"name\": \"Hourly Health Check\",
387
+ \"trigger\": {\"type\": \"cron\", \"schedule\": \"0 * * * *\", \"timezone\": \"UTC\"},
388
+ \"tarball_path\": \"$TARBALL_PATH\",
389
+ \"entrypoint\": \"python3 main.py\"
390
+ }"
391
+ ```
392
+
393
+ For state between runs (last alert time, last seen event), use the KV store helpers in `references/custom-automation.md#state-persistence-kv-store`.
394
+
312
395
  ---
313
396
 
314
397
  ## Polling as a Webhook Alternative
315
398
 
316
- When the deployment cannot receive inbound webhook traffic (see rule 5), use a cron-triggered automation that calls the external service’s API on a schedule to check for new events.
399
+ When the deployment cannot receive inbound webhook traffic (see rule 4), use a cron-triggered automation that calls the external service’s API on a schedule to check for new events.
317
400
 
318
401
  ### Polling vs. Webhooks at a Glance
319
402
 
@@ -442,15 +525,15 @@ curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
442
525
  }'
443
526
  ```
444
527
 
445
- #### GitHub: Run tests on push to main
528
+ #### GitHub: Flag risky changes pushed to main
446
529
 
447
530
  ```bash
448
531
  curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
449
532
  -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
450
533
  -H "Content-Type: application/json" \
451
534
  -d '{
452
- "name": "Run Tests on Main",
453
- "prompt": "Clone the repository and run the test suite. Report any failures.",
535
+ "name": "Risky Change Watch",
536
+ "prompt": "Review the diff of this push. If it touches authentication, billing, data migrations, or anything that changes behavior for existing users, open an issue that explains the risk in plain terms and mentions the code owners. Otherwise do nothing.",
454
537
  "trigger": {
455
538
  "type": "event",
456
539
  "source": "github",
@@ -942,7 +1025,7 @@ The **prompt preset** is the right default for genuinely agent-shaped work — a
942
1025
 
943
1026
  **Watch for deterministic, high-frequency patterns.** Requests like "send a daily standup reminder", "ping a healthcheck URL every minute", "post a random quote every 5 minutes", or "rotate a fact-of-the-day message" do not need an LLM. Surface this to the user explicitly with a rough cost framing (e.g. "this schedule will invoke your LLM ~288 times/day") before defaulting to a preset. As a rule of thumb, any cron tighter than hourly deserves a deliberate "should this really be agent-driven?" check.
944
1027
 
945
- **When neither preset is the right fit** (deterministic task, custom Python dependencies, non-Python entrypoint, multi-file project structure, direct SDK lifecycle control), explain the options to the user and let them decide. Do not attempt custom automation without explicit user agreement. If they choose the custom route, refer to `references/custom-automation.md`.
1028
+ **When neither preset is the right fit** (deterministic task, custom Python dependencies, non-Python entrypoint, multi-file project structure, direct SDK lifecycle control), explain the options and let the user decide, exactly as for any other automation. If they choose the custom route, follow `references/custom-automation.md` and the *Custom Script Example (No LLM)* above.
946
1029
 
947
1030
  ## Security Considerations
948
1031
 
@@ -955,7 +1038,6 @@ See `references/security.md` — also covers narrowing triggers and sender-level
955
1038
 
956
1039
  ## Reference Files
957
1040
 
958
- - **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), state persistence via the KV store, environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate or recommend the custom path (including for deterministic / cost-sensitive tasks per rule 0). Only *implement* a custom automation after the user agrees to that path.
1041
+ - **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), state persistence via the KV store, environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate, recommend, or build the custom path (including for deterministic / cost-sensitive tasks per rule 0).
959
1042
  - **`references/ab-testing.md`** — A/B testing for plugin automations: defining variants with weights, experiment configuration, variant selection logic, observability via conversation tags, and complete examples. Consult this when a user wants to compare plugin versions or configurations.
960
- - **`references/security.md`** — Trust boundaries: untrusted content vs. verified sender, least-privilege secrets, trigger scoping, sender authorization, pre-deploy verification. Consult whenever an automation handles external input or forwards secrets to a spawned conversation.
961
1043
  - **`references/security.md`** — Trust boundaries for automations: untrusted event content vs. verified sender, least-privilege secret scoping for spawned conversations, narrowing triggers, sender-level authorization, and verifying a script actually runs before deploying it. Consult this whenever an automation handles external/untrusted input (GitHub issues/PRs, Slack messages, any public-facing webhook) or forwards secrets to a spawned conversation.
@@ -1,13 +1,12 @@
1
1
  # Custom Automation Reference
2
2
 
3
- > **⚠️ Do NOT use this reference unless the user has explicitly requested a custom automation.** Always use the preset/prompt endpoint from the main SKILL.md first. If the preset approach cannot satisfy the requirement, explain the options to the user and let them decide.
4
-
5
3
  This file contains detailed documentation for creating custom automations with user-provided code, uploads, and entrypoints.
6
4
 
7
- **When to use custom automation (only if the user explicitly chooses this):**
5
+ **When to use a custom automation:**
6
+ - The task is deterministic and needs no LLM (fixed data, scheduled HTTP calls, templated messages), especially on a frequent schedule
8
7
  - Full control over the automation code structure is needed
9
8
  - Custom dependencies or a specific runtime are required
10
- - The user has confirmed that the prompt preset does not meet their requirements
9
+ - The user picked this path after seeing the options side by side (see the agent behavior rules in `SKILL.md`)
11
10
 
12
11
  ## Table of Contents
13
12
 
@@ -250,6 +249,7 @@ uv pip install -q openhands-sdk openhands-workspace openhands-tools
250
249
  import os
251
250
 
252
251
  from openhands.sdk import Conversation
252
+ from openhands.sdk.automation import automation_conversation_kwargs
253
253
  from openhands.tools.preset.default import get_default_agent
254
254
  from openhands.workspace import OpenHandsCloudWorkspace
255
255
 
@@ -267,13 +267,48 @@ with OpenHandsCloudWorkspace(
267
267
  ) as workspace:
268
268
  llm = workspace.get_llm()
269
269
  agent = get_default_agent(llm=llm, cli_mode=True)
270
- conversation = Conversation(agent=agent, workspace=workspace)
270
+ conversation = Conversation(
271
+ agent=agent,
272
+ workspace=workspace,
273
+ **automation_conversation_kwargs(),
274
+ )
271
275
  conversation.send_message("Your automation prompt here")
272
276
  conversation.run()
273
277
  conversation.close()
274
278
  # OpenHandsCloudWorkspace.__exit__ fires the completion callback here.
275
279
  ```
276
280
 
281
+ ### Link automation conversations to run telemetry
282
+
283
+ When an automation script starts a conversation, preserve the trace context that the automation service injected into the run. This links the conversation span to the triggering webhook, scheduler dispatch, and run record.
284
+
285
+ - SDK conversations: pass `**automation_conversation_kwargs()` into every `Conversation(...)` call.
286
+ - Direct agent-server API calls: merge `automation_observability_headers()` into the headers for each `POST /api/conversations` request.
287
+ - If you add your own conversation tags, pass them through `conversation_tags={...}` instead of replacing the helper output.
288
+
289
+ ```python
290
+ from openhands.sdk import Conversation
291
+ from openhands.sdk.automation import (
292
+ automation_conversation_kwargs,
293
+ automation_observability_headers,
294
+ )
295
+
296
+ conversation = Conversation(
297
+ agent=agent,
298
+ workspace=workspace,
299
+ **automation_conversation_kwargs(),
300
+ )
301
+
302
+ headers = {
303
+ "X-Session-API-Key": api_key,
304
+ **automation_observability_headers(span_name="automation.conversation"),
305
+ }
306
+ # Use these headers when creating a backend conversation directly.
307
+ # httpx.post(f"{agent_url}/api/conversations", headers=headers, json=payload)
308
+ ```
309
+
310
+ The built-in prompt and plugin presets use this helper. Custom scripts should do the same for every new conversation so traces, observability metadata, and automation conversation tags stay attached.
311
+
277
312
  ### Selecting an LLM profile
278
313
 
279
314
  The automation service injects an `AUTOMATION_MODEL` environment variable holding the **LLM profile name** the automation should run with. This is the profile a user selects when editing the automation, or — when none is selected — a snapshot of the user's active profile taken when the automation was created. To honor it on every run, read `AUTOMATION_MODEL` and pass it to `workspace.get_llm(profile_name=...)`:
@@ -300,13 +335,27 @@ Conversations started during a run remain accessible in the OpenHands UI after t
300
335
 
301
336
  ```python
302
337
  # Default: conversation persists after close (users can view/continue it)
303
- conversation = Conversation(agent=agent, workspace=workspace)
338
+ conversation = Conversation(
339
+ agent=agent,
340
+ workspace=workspace,
341
+ **automation_conversation_kwargs(),
342
+ )
304
343
 
305
344
  # Explicitly persist (same as default)
306
- conversation = Conversation(agent=agent, workspace=workspace, delete_on_close=False)
345
+ conversation = Conversation(
346
+ agent=agent,
347
+ workspace=workspace,
348
+ delete_on_close=False,
349
+ **automation_conversation_kwargs(),
350
+ )
307
351
 
308
352
  # Delete conversation resources on close
309
- conversation = Conversation(agent=agent, workspace=workspace, delete_on_close=True)
353
+ conversation = Conversation(
354
+ agent=agent,
355
+ workspace=workspace,
356
+ delete_on_close=True,
357
+ **automation_conversation_kwargs(),
358
+ )
310
359
  ```
311
360
 
312
361
  The agent server itself persists until it times out or is manually deleted; this is managed by the automation service, not by the workspace.
@@ -340,7 +389,11 @@ else:
340
389
  with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
341
390
  llm = workspace.get_llm()
342
391
  agent = get_default_agent(llm=llm, cli_mode=True)
343
- conversation = Conversation(agent=agent, workspace=workspace)
392
+ conversation = Conversation(
393
+ agent=agent,
394
+ workspace=workspace,
395
+ **automation_conversation_kwargs(),
396
+ )
344
397
  conversation.send_message(f"Investigate these alerts and open GitHub issues: {alerts}")
345
398
  conversation.run()
346
399
  conversation.close()
@@ -359,7 +412,11 @@ from openhands.sdk.conversation.state import ConversationExecutionStatus
359
412
  with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
360
413
  llm = workspace.get_llm()
361
414
  agent = get_default_agent(llm=llm, cli_mode=True)
362
- conversation = Conversation(agent=agent, workspace=workspace)
415
+ conversation = Conversation(
416
+ agent=agent,
417
+ workspace=workspace,
418
+ **automation_conversation_kwargs(),
419
+ )
363
420
  conversation.send_message("Run a long analysis task")
364
421
  # Conversation is now running asynchronously in the agent server.
365
422
 
@@ -405,7 +462,12 @@ stop_hook = HookConfig(
405
462
  with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
406
463
  llm = workspace.get_llm()
407
464
  agent = get_default_agent(llm=llm, cli_mode=True)
408
- conversation = Conversation(agent=agent, workspace=workspace, hook_config=stop_hook)
465
+ conversation = Conversation(
466
+ agent=agent,
467
+ workspace=workspace,
468
+ hook_config=stop_hook,
469
+ **automation_conversation_kwargs(),
470
+ )
409
471
  conversation.send_message("Do some long-running work")
410
472
  # Don't call run() — the conversation runs asynchronously.
411
473
  # When the agent stops, the stop hook will fire the callback.
@@ -766,6 +828,7 @@ import os
766
828
  import json
767
829
 
768
830
  from openhands.sdk import Conversation
831
+ from openhands.sdk.automation import automation_conversation_kwargs
769
832
  from openhands.tools.preset.default import get_default_agent
770
833
  from openhands.workspace import OpenHandsCloudWorkspace
771
834
 
@@ -782,7 +845,11 @@ with OpenHandsCloudWorkspace(
782
845
  ) as workspace:
783
846
  llm = workspace.get_llm()
784
847
  agent = get_default_agent(llm=llm, cli_mode=True)
785
- conversation = Conversation(agent=agent, workspace=workspace)
848
+ conversation = Conversation(
849
+ agent=agent,
850
+ workspace=workspace,
851
+ **automation_conversation_kwargs(),
852
+ )
786
853
  conversation.send_message("Generate a weekly status report")
787
854
  conversation.run()
788
855
  conversation.close()
@@ -131,10 +131,11 @@ print("All done!")
131
131
  - [Secret Registry](https://docs.openhands.dev/sdk/guides/secrets.md): Provide environment variables and secrets to agent workspace securely.
132
132
  - [Security & Action Confirmation](https://docs.openhands.dev/sdk/guides/security.md): Control agent action execution through confirmation policy and security analyzer.
133
133
  - [Send Message While Running](https://docs.openhands.dev/sdk/guides/convo-send-message-while-running.md): Interrupt running agents to provide additional context or corrections.
134
- - [Software Agent SDK](https://docs.openhands.dev/sdk.md): Build AI agents that write software. A clean, modular SDK with production-ready tools.
134
+ - [Software Agent SDK](https://docs.openhands.dev/sdk/index.md): Build AI agents that write software. A clean, modular SDK with production-ready tools.
135
135
  - [Structured Output](https://docs.openhands.dev/sdk/guides/structured-output.md): Attach a schema to any tool so the LLM returns typed, validated fields alongside the tool's own arguments.
136
136
  - [Stuck Detector](https://docs.openhands.dev/sdk/guides/agent-stuck-detector.md): Detect and handle stuck agents automatically with timeout mechanisms.
137
137
  - [Task Tool Set](https://docs.openhands.dev/sdk/guides/task-tool-set.md): Delegate complex work to specialized sub-agents that run synchronously and return results to the parent agent.
138
+ - [Task-Based Model Routing](https://docs.openhands.dev/sdk/guides/llm-task-routing.md): Let the agent pick the best saved LLM profile for the task at hand with the built-in route_task_to_model tool.
138
139
  - [Theory of Mind (TOM) Agent](https://docs.openhands.dev/sdk/guides/agent-tom-agent.md): Enable your agent to understand user intent and preferences through Theory of Mind capabilities, providing personalized guidance based on user modeling.
139
140
  - [TODO Management](https://docs.openhands.dev/sdk/guides/github-workflows/todo-management.md): Implement TODOs using OpenHands Agent
140
141
 
@@ -14,5 +14,7 @@ The skill guides the agent through four phases:
14
14
 
15
15
  1. **Understand** — Classify changes and identify entry points
16
16
  2. **Setup** — Install dependencies, build, check CI status
17
- 3. **Exercise** — Run the actual code as a user would (browser, CLI, API requests)
17
+ 3. **Exercise** — Run the actual code as a user would (browser, CLI, API requests).
18
+ For frontend changes, capture screenshots of each affected state and a GIF of
19
+ the key interaction (before/after where behavior changes)
18
20
  4. **Report** — Post a structured QA report with evidence and a verdict
@@ -75,6 +75,37 @@ This is the most important phase. **Actually use the software** the way a real u
75
75
  - Verify the visual change renders correctly. Take screenshots as evidence.
76
76
  - Test user interactions (clicks, form submissions, navigation).
77
77
  - Try at least one edge case (empty state, long text, missing data).
78
+ - Then capture visual evidence and attach it to the PR (see next step).
79
+
80
+ **Capture visual evidence (frontend PRs only):**
81
+
82
+ If the PR has frontend work, run the affected screens from the branch's final
83
+ state and record what a reviewer would otherwise have to imagine from the diff.
84
+ The goal: the change can be reviewed **by observation, not by reading code.**
85
+
86
+ 1. **Screenshot each relevant state** of every affected screen - whichever
87
+ apply: *empty*, *loading*, *error*, and *populated*. Skip states the screen
88
+ genuinely cannot reach.
89
+ 2. **Record the key interaction end to end** - the main flow the PR changes,
90
+ driven the way a user would. Prefer a GIF: GitHub renders a GIF inline from
91
+ an image link, but only links a video file.
92
+ 3. **Where behavior changes, capture before/after** - the same state or
93
+ interaction on the base branch and on the PR branch, so the delta is
94
+ visible.
95
+ 4. **Attach it to the report.** Embed the images and GIF in the QA report so
96
+ they render inline, and label each one (screen and state, or before/after).
97
+ Do not push commits to the PR branch to host media: that changes the head
98
+ you are verifying, and the shipped QA workflow has read-only access to
99
+ repository contents. If you cannot upload media where the report is posted,
100
+ save the files where the run keeps its artifacts, say where to find them,
101
+ and describe in text what each capture shows. In the `qa-changes` GitHub
102
+ Action that is `$GITHUB_WORKSPACE/output/`, the parent of the PR checkout
103
+ you work in, which the action uploads as the `openhands-qa-changes-logs`
104
+ artifact; a relative `output/` inside the checkout is not uploaded.
105
+
106
+ Keep it proportional: capture the screens the PR actually touches, not the whole
107
+ app. If you cannot render a screen (missing data, an unreachable state, no
108
+ browser), say so in the report rather than faking it.
78
109
 
79
110
  **For CLI changes:**
80
111
  - Run the CLI command with realistic arguments. Capture stdout and stderr.
@@ -181,6 +212,16 @@ prior state rather than reproducing a failure.}
181
212
 
182
213
  </details>
183
214
 
215
+ <details><summary>Visual Evidence</summary>
216
+
217
+ {Frontend PRs only. Embed the screenshots (per screen and state: empty,
218
+ loading, error, populated) and the GIF of the key interaction so they
219
+ render inline, or say where the captures are stored if they could not be
220
+ uploaded. Where behavior changed, show before/after. Label each clearly.
221
+ Omit this section entirely for non-frontend PRs.}
222
+
223
+ </details>
224
+
184
225
  <details><summary>Unable to Verify</summary>
185
226
 
186
227
  {What could not be verified, what was attempted, and suggested
@@ -221,6 +262,7 @@ was verified.}
221
262
  - **Run the code, not the tests.** Execute the actual software — start servers, run CLI commands, make HTTP requests, open browsers. Do not run `pytest`, `npm test`, or equivalent test suites. That is CI's job.
222
263
  - **Do not analyze code.** Reading files and commenting on style, structure, or logic is code review's job. Your job is to exercise behavior, not read source files.
223
264
  - **Set a high bar.** If the change affects a UI, open it in a real browser. If it affects a CLI, run the actual CLI with real inputs. If it affects an API, make real HTTP requests.
265
+ - **Make frontend changes reviewable by observation.** For UI PRs, attach screenshots of each relevant state and a GIF of the key interaction (before/after where behavior changes) so a reviewer can see the change, not infer it from the diff.
224
266
  - **Test what the PR claims.** The PR description is the specification. Verify the claim, not hypothetical scenarios.
225
267
  - **Leave CI to CI.** Do not re-run tests, linters, formatters, or type checkers. Note CI status, then focus entirely on functional verification that CI cannot do.
226
268
  - **Report evidence, not opinions.** Include exact commands, outputs, and error messages — inside collapsible blocks.
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "simplified-technical-english",
3
+ "version": "1.0.0",
4
+ "description": "Write explanations and chat in the style of ASD-STE100 Simplified Technical English: short active sentences, one idea each, plain consistent words. Applies to prose only, never to code.",
5
+ "author": {
6
+ "name": "OpenHands",
7
+ "email": "contact@all-hands.dev"
8
+ },
9
+ "homepage": "https://github.com/OpenHands/extensions",
10
+ "repository": "https://github.com/OpenHands/extensions",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "simplified-technical-english"
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "simplified-technical-english",
3
+ "version": "1.0.0",
4
+ "description": "Write explanations and chat in the style of ASD-STE100 Simplified Technical English: short active sentences, one idea each, plain consistent words. Applies to prose only, never to code.",
5
+ "author": {
6
+ "name": "OpenHands",
7
+ "email": "contact@all-hands.dev"
8
+ },
9
+ "homepage": "https://github.com/OpenHands/extensions",
10
+ "repository": "https://github.com/OpenHands/extensions",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "simplified-technical-english"
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "simplified-technical-english",
3
+ "version": "1.0.0",
4
+ "description": "Write explanations and chat in the style of ASD-STE100 Simplified Technical English: short active sentences, one idea each, plain consistent words. Applies to prose only, never to code.",
5
+ "author": {
6
+ "name": "OpenHands",
7
+ "email": "contact@all-hands.dev"
8
+ },
9
+ "homepage": "https://github.com/OpenHands/extensions",
10
+ "repository": "https://github.com/OpenHands/extensions",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "simplified-technical-english"
14
+ ]
15
+ }
@@ -0,0 +1,21 @@
1
+ # Simplified Technical English skill
2
+
3
+ This skill helps OpenHands write **prose** in the style of ASD-STE100 Simplified
4
+ Technical English: short active sentences, one idea each, plain and consistent
5
+ words. The aim is text that is easy to read the first time. This matters most
6
+ for readers whose first language is not English, and for instructions where a
7
+ misread is costly.
8
+
9
+ Use it for:
10
+
11
+ - chat replies, explanations, summaries, and step lists that need maximum clarity
12
+ - ops, deploy, or security instructions where a misread is costly
13
+ - briefing another agent to produce plain, scannable prose
14
+
15
+ **The hard boundary is prose only.** The skill must never change code, identifiers,
16
+ CLI flags, API fields, commit messages, or reference/API docs. A controlled
17
+ English vocabulary must not bleed into code, where exact domain terms win.
18
+
19
+ It complements `plain-english-content` (GOV.UK-style content design) and
20
+ `technical-writing` (conversational engineering prose); this one adds the
21
+ ASD-STE100 register and the explicit "do not touch code" guardrail.