@openhands/extensions 0.26.0 → 0.28.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/README.md +3 -2
- package/automations/README.md +7 -6
- 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/automations/index.d.ts +9 -1
- package/automations/interface.json +9 -0
- package/automations/interface.schema.json +37 -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 +3 -4
- 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/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 +23 -11
- 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 +3 -4
- 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/tests/test_interface_manifest.py +23 -1
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
"bundle": {
|
|
101
|
-
"version": "1.2.
|
|
101
|
+
"version": "1.2.2",
|
|
102
102
|
"entrypoint": "python3 worker.py",
|
|
103
103
|
"timeout": 300,
|
|
104
104
|
"files": {
|
|
@@ -117,5 +117,5 @@
|
|
|
117
117
|
},
|
|
118
118
|
"message": "Configure the repositories, labels, branch prefix, pull request mode, agent profile, and schedule."
|
|
119
119
|
},
|
|
120
|
-
"version": "1.2.
|
|
120
|
+
"version": "1.2.2"
|
|
121
121
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "github-issue-triage",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"name": "GitHub issue triage",
|
|
5
5
|
"category": "Project management",
|
|
6
6
|
"description": "Prioritize open issues and establish acceptance criteria before marking them ready for development.",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"bundle": {
|
|
57
|
-
"version": "1.3.
|
|
57
|
+
"version": "1.3.1",
|
|
58
58
|
"entrypoint": "python3 worker.py",
|
|
59
59
|
"timeout": 300,
|
|
60
60
|
"files": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "github-pr-reviewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"name": "GitHub code review",
|
|
5
5
|
"category": "Code review",
|
|
6
6
|
"description": "Review requested pull-request heads, resume an outstanding reviewer request on a schedule once its checks are green, classify all open non-draft pull requests that nobody requested and review eligible green heads, drain the backlog oldest-first under a small per-scan new-conversation bound, record exact-head decisions, stop early on out-of-scope changes, and optionally request a code-aware human maintainer after approval or a maintainer-decision scope stop.",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
137
|
"bundle": {
|
|
138
|
-
"version": "1.
|
|
138
|
+
"version": "1.9.2",
|
|
139
139
|
"entrypoint": "python3 worker.py",
|
|
140
140
|
"timeout": 300,
|
|
141
141
|
"files": {
|
package/automations/index.d.ts
CHANGED
|
@@ -309,6 +309,7 @@ export type AutomationStatusFilterValue =
|
|
|
309
309
|
| "failing"
|
|
310
310
|
| "disabled";
|
|
311
311
|
export type AutomationTriggerFilterValue = "all" | "schedule" | "event";
|
|
312
|
+
export type AutomationCreatedByFilterValue = "all" | "me" | "others";
|
|
312
313
|
|
|
313
314
|
/**
|
|
314
315
|
* A filter dropdown. Values name predicates the host implements; the manifest
|
|
@@ -327,9 +328,16 @@ export interface AutomationTriggerFilter {
|
|
|
327
328
|
options: { value: AutomationTriggerFilterValue; label: string }[];
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
export interface AutomationCreatedByFilter {
|
|
332
|
+
id: "created_by";
|
|
333
|
+
label: string;
|
|
334
|
+
options: { value: AutomationCreatedByFilterValue; label: string }[];
|
|
335
|
+
}
|
|
336
|
+
|
|
330
337
|
export type AutomationDashboardFilter =
|
|
331
338
|
| AutomationStatusFilter
|
|
332
|
-
| AutomationTriggerFilter
|
|
339
|
+
| AutomationTriggerFilter
|
|
340
|
+
| AutomationCreatedByFilter;
|
|
333
341
|
|
|
334
342
|
/** A comparator the host implements, named from a closed set. */
|
|
335
343
|
export type AutomationSortValue = "last-run" | "runs" | "name";
|
|
@@ -71,6 +71,15 @@
|
|
|
71
71
|
{ "value": "schedule", "label": "Scheduled" },
|
|
72
72
|
{ "value": "event", "label": "Event-driven" }
|
|
73
73
|
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "created_by",
|
|
77
|
+
"label": "Filter by creator",
|
|
78
|
+
"options": [
|
|
79
|
+
{ "value": "all", "label": "Anyone" },
|
|
80
|
+
{ "value": "me", "label": "Me" },
|
|
81
|
+
{ "value": "others", "label": "Others" }
|
|
82
|
+
]
|
|
74
83
|
}
|
|
75
84
|
],
|
|
76
85
|
"sort": {
|
|
@@ -86,12 +86,13 @@
|
|
|
86
86
|
"description": "The filter dropdowns of the list page, in render order. Each filter id may appear once; the host's admission checks that.",
|
|
87
87
|
"type": "array",
|
|
88
88
|
"minItems": 1,
|
|
89
|
-
"maxItems":
|
|
89
|
+
"maxItems": 3,
|
|
90
90
|
"uniqueItems": true,
|
|
91
91
|
"items": {
|
|
92
92
|
"oneOf": [
|
|
93
93
|
{ "$ref": "#/$defs/statusFilter" },
|
|
94
|
-
{ "$ref": "#/$defs/triggerFilter" }
|
|
94
|
+
{ "$ref": "#/$defs/triggerFilter" },
|
|
95
|
+
{ "$ref": "#/$defs/createdByFilter" }
|
|
95
96
|
]
|
|
96
97
|
}
|
|
97
98
|
},
|
|
@@ -425,6 +426,40 @@
|
|
|
425
426
|
}
|
|
426
427
|
}
|
|
427
428
|
},
|
|
429
|
+
"createdByFilter": {
|
|
430
|
+
"description": "The creator filter. Like the status filter, values name host-implemented predicates: me matches automations the signed-in user created, others every other automation. The host decides where the filter is shown and may hide it where every automation is the signed-in user's.",
|
|
431
|
+
"type": "object",
|
|
432
|
+
"additionalProperties": false,
|
|
433
|
+
"required": ["id", "label", "options"],
|
|
434
|
+
"properties": {
|
|
435
|
+
"id": { "const": "created_by" },
|
|
436
|
+
"label": {
|
|
437
|
+
"description": "The control's accessible name.",
|
|
438
|
+
"$ref": "#/$defs/copy"
|
|
439
|
+
},
|
|
440
|
+
"options": {
|
|
441
|
+
"description": "Each value may appear once; the host's admission checks that.",
|
|
442
|
+
"type": "array",
|
|
443
|
+
"minItems": 2,
|
|
444
|
+
"maxItems": 8,
|
|
445
|
+
"uniqueItems": true,
|
|
446
|
+
"items": {
|
|
447
|
+
"type": "object",
|
|
448
|
+
"additionalProperties": false,
|
|
449
|
+
"required": ["value", "label"],
|
|
450
|
+
"properties": {
|
|
451
|
+
"value": { "enum": ["all", "me", "others"] },
|
|
452
|
+
"label": { "$ref": "#/$defs/copy" }
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
"contains": {
|
|
456
|
+
"type": "object",
|
|
457
|
+
"required": ["value"],
|
|
458
|
+
"properties": { "value": { "const": "all" } }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
},
|
|
428
463
|
"sort": {
|
|
429
464
|
"description": "The sort dropdown. Values name comparators the host implements. default must be one of the declared option values; the host's admission checks that, as schemas cannot cheaply.",
|
|
430
465
|
"type": "object",
|
|
@@ -830,6 +830,15 @@
|
|
|
830
830
|
"technical-writing"
|
|
831
831
|
]
|
|
832
832
|
},
|
|
833
|
+
{
|
|
834
|
+
"name": "simplified-technical-english",
|
|
835
|
+
"source": "./skills/simplified-technical-english",
|
|
836
|
+
"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.",
|
|
837
|
+
"category": "writing",
|
|
838
|
+
"keywords": [
|
|
839
|
+
"simplified-technical-english"
|
|
840
|
+
]
|
|
841
|
+
},
|
|
833
842
|
{
|
|
834
843
|
"name": "plain-english-content",
|
|
835
844
|
"source": "./skills/plain-english-content",
|
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -121,6 +121,10 @@ The agent posts a PR comment with this structure:
|
|
|
121
121
|
### Functional Verification
|
|
122
122
|
[Commands run, outputs observed, screenshots, behavior verified]
|
|
123
123
|
|
|
124
|
+
### Visual Evidence (frontend PRs only)
|
|
125
|
+
[Screenshots of each affected state and a GIF of the key interaction, embedded
|
|
126
|
+
inline or saved to the run's `openhands-qa-changes-logs` artifact]
|
|
127
|
+
|
|
124
128
|
### Unable to Verify (if applicable)
|
|
125
129
|
[What could not be verified, what was attempted, suggested AGENTS.md guidance]
|
|
126
130
|
|
|
@@ -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.
|
|
@@ -11,6 +11,8 @@ This skill is activated by the following keywords:
|
|
|
11
11
|
|
|
12
12
|
## Details
|
|
13
13
|
|
|
14
|
+
Use authenticated GitHub 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 `GITHUB_TOKEN` and direct API flow when those tools are unavailable or raw API or `curl` access is explicitly needed.
|
|
15
|
+
|
|
14
16
|
You have access to an environment variable, `GITHUB_TOKEN`, which allows you to interact with
|
|
15
17
|
the GitHub API.
|
|
16
18
|
|
|
@@ -39,4 +41,6 @@ Here are some instructions for pushing, but ONLY do this if the user asks you to
|
|
|
39
41
|
```bash
|
|
40
42
|
git remote -v && git branch # to find the current org, repo and branch
|
|
41
43
|
git checkout -b create-widget && git add . && git commit -m "Create widget" && git push -u origin create-widget
|
|
42
|
-
```
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When asked to merge a pull request, the skill uses GitHub's async merge API (`PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge-async`, then poll the returned UUID). It supports stacked PRs and merge queues. Docs: https://docs.github.com/en/rest/pulls/pulls#merge-a-pull-request-asynchronously
|
|
@@ -5,6 +5,10 @@ triggers:
|
|
|
5
5
|
- github
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## Connection selection
|
|
9
|
+
|
|
10
|
+
Use authenticated GitHub 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 token and direct API instructions below only when GitHub MCP tools are unavailable or when raw API or `curl` access is explicitly needed.
|
|
11
|
+
|
|
8
12
|
You have access to an environment variable, `GITHUB_TOKEN`, which allows you to interact with
|
|
9
13
|
the GitHub API.
|
|
10
14
|
|
|
@@ -37,6 +41,29 @@ git remote -v && git branch # to find the current org, repo and branch
|
|
|
37
41
|
git checkout -b create-widget && git add . && git commit -m "Create widget" && git push -u origin create-widget
|
|
38
42
|
```
|
|
39
43
|
|
|
44
|
+
## Merging Pull Requests (async merge API)
|
|
45
|
+
|
|
46
|
+
IMPORTANT:
|
|
47
|
+
Only merge if the user asks you to, without ambiguity.
|
|
48
|
+
|
|
49
|
+
GitHub released an async merge API in October 2026; it is now the
|
|
50
|
+
recommended way to merge PRs programmatically, and the only merge API that supports stacked PRs. It can also add a PR to a merge queue.
|
|
51
|
+
Prefer it over `PUT .../pulls/{n}/merge` and the GraphQL `mergePullRequest` mutation.
|
|
52
|
+
A GitHub MCP merge tool may still use the older synchronous endpoint; for stacked PRs or merge queues, use the calls below.
|
|
53
|
+
Docs: https://docs.github.com/en/rest/pulls/pulls#merge-a-pull-request-asynchronously
|
|
54
|
+
|
|
55
|
+
1. Request the merge (`merge_action`: `default` uses the merge queue if the branch has one, else merges directly; or `direct_merge` / `merge_queue`). `merge_method`, `commit_title` and `commit_message` apply only to direct merges:
|
|
56
|
+
```bash
|
|
57
|
+
gh api -X PUT repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/merge-async -f merge_method=squash -f sha=<HEAD_SHA>
|
|
58
|
+
```
|
|
59
|
+
2. A `202` returns `details.uuid`; poll it every few seconds until `status` is no longer `pending`:
|
|
60
|
+
```bash
|
|
61
|
+
gh api repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/merge-async/<UUID>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Final `status` is `merged`, `enqueued` (added to the merge queue, NOT merged yet), or `failed` (see `details.message`).
|
|
65
|
+
A `200` on the PUT means the PR is already merged or queued; a `409` means a merge request is already pending (its UUID is returned).
|
|
66
|
+
|
|
40
67
|
## Handling Review Comments
|
|
41
68
|
|
|
42
69
|
- Critically evaluate each review comment before acting on it. Not all feedback is worth implementing:
|