@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.
- package/.agents/skills/custom-codereview-guide.md +31 -0
- package/.claude-plugin/marketplace.json +9 -0
- package/.codex-plugin/marketplace.json +9 -0
- package/.github/workflows/build-vscode-app.yml +3 -3
- package/.github/workflows/deprecation-check.yml +1 -1
- package/.github/workflows/pypi-publish.yml +2 -2
- package/.github/workflows/tests.yml +1 -1
- package/.plugin/marketplace.json +9 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +7 -2
- package/README.md +3 -2
- package/automations/bundle-index.js +5 -5
- package/automations/catalog/github-issue-to-pr/manifest.json +2 -2
- package/automations/catalog/github-issue-triage/manifest.json +2 -2
- package/automations/catalog/github-pr-reviewer/manifest.json +2 -2
- package/marketplaces/openhands-extensions.json +9 -0
- package/package.json +1 -1
- package/plugins/openhands/skills/openhands-automation/README.md +6 -4
- package/plugins/openhands/skills/openhands-automation/SKILL.md +103 -21
- package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +79 -12
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +2 -1
- package/plugins/qa-changes/README.md +4 -0
- package/plugins/qa-changes/skills/qa-changes/README.md +3 -1
- package/plugins/qa-changes/skills/qa-changes/SKILL.md +42 -0
- package/plugins/vulnerability-remediation/skills/github/README.md +5 -1
- package/plugins/vulnerability-remediation/skills/github/SKILL.md +27 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +51 -4
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/scripts/sync_openhands_sdk_skill.py +16 -13
- package/skills/azure-devops/README.md +3 -1
- package/skills/azure-devops/SKILL.md +4 -0
- package/skills/bitbucket/README.md +3 -1
- package/skills/bitbucket/SKILL.md +4 -2
- package/skills/bitbucket-cloud/README.md +2 -0
- package/skills/bitbucket-cloud/SKILL.md +2 -0
- package/skills/bitbucket-data-center/README.md +2 -0
- package/skills/bitbucket-data-center/SKILL.md +2 -0
- package/skills/canvas-extension-api/SKILL.md +4 -1
- package/skills/canvas-extension-api/references/native-design.md +250 -0
- package/skills/github/README.md +5 -1
- package/skills/github/SKILL.md +27 -0
- package/skills/github/scripts/agent_conversation.py +51 -4
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +51 -4
- package/skills/github-issue-triage/scripts/agent_conversation.py +51 -4
- package/skills/github-pr-reviewer/README.md +5 -0
- package/skills/github-pr-reviewer/SKILL.md +12 -0
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +51 -4
- package/skills/github-pr-reviewer/scripts/main.py +20 -6
- package/skills/github-pr-reviewer/scripts/worker.py +77 -15
- package/skills/github-pr-reviewer/tests/test_main.py +23 -0
- package/skills/gitlab/README.md +3 -1
- package/skills/gitlab/SKILL.md +4 -0
- package/skills/index.js +25 -13
- package/skills/linear/README.md +2 -0
- package/skills/linear/SKILL.md +6 -2
- package/skills/openhands-automation/README.md +6 -4
- package/skills/openhands-automation/SKILL.md +103 -21
- package/skills/openhands-automation/references/custom-automation.md +79 -12
- package/skills/openhands-sdk/SKILL.md +2 -1
- package/skills/qa-changes/README.md +3 -1
- package/skills/qa-changes/SKILL.md +42 -0
- package/skills/simplified-technical-english/.claude-plugin/plugin.json +15 -0
- package/skills/simplified-technical-english/.codex-plugin/plugin.json +15 -0
- package/skills/simplified-technical-english/.plugin/plugin.json +15 -0
- package/skills/simplified-technical-english/README.md +21 -0
- package/skills/simplified-technical-english/SKILL.md +65 -0
- package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
- package/tests/test_agent_conversation_dispatch.py +130 -0
- package/tests/test_github_reviewer_delivery.py +279 -2
package/skills/linear/README.md
CHANGED
|
@@ -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>
|
package/skills/linear/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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. **
|
|
68
|
-
>
|
|
69
|
-
> - **
|
|
70
|
-
> - **
|
|
71
|
-
>
|
|
72
|
-
>
|
|
73
|
-
>
|
|
74
|
-
>
|
|
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
|
-
- `"
|
|
259
|
-
- `"
|
|
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
|
|
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
|
|
306
|
-
"prompt": "
|
|
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":
|
|
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
|
|
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:
|
|
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": "
|
|
453
|
-
"prompt": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|