@openhands/extensions 0.3.0 → 0.4.1
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/.release-please-manifest.json +1 -1
- package/README.md +3 -2
- package/automations/catalog/github-pr-reviewer.json +1 -1
- package/automations/catalog/github-repo-monitor.json +1 -1
- package/marketplaces/openhands-extensions.json +13 -0
- package/package.json +1 -1
- package/plugins/issue-duplicate-checker/action.yml +2 -2
- package/plugins/pr-review/README.md +1 -1
- package/plugins/qa-changes/action.yml +4 -2
- package/plugins/release-notes/action.yml +3 -2
- package/plugins/release-notes/workflows/release-notes.yml +10 -4
- package/plugins/vulnerability-remediation/README.md +4 -2
- package/plugins/vulnerability-remediation/action.yml +8 -4
- package/pyproject.toml +1 -1
- package/scripts/sync_extensions.py +2 -1
- package/skills/github-actions/.plugin/plugin.json +19 -0
- package/skills/github-actions/README.md +190 -0
- package/skills/github-actions/SKILL.md +44 -0
- package/skills/github-pr-reviewer/SKILL.md +183 -40
- package/skills/github-pr-reviewer/references/state-schema.md +98 -0
- package/skills/github-pr-reviewer/scripts/main.py +625 -0
- package/skills/github-repo-monitor/README.md +1 -1
- package/skills/github-repo-monitor/SKILL.md +9 -9
- package/skills/github-repo-monitor/references/github-api.md +3 -3
- package/skills/github-repo-monitor/scripts/main.py +11 -11
- package/skills/index.js +16 -4
- package/skills/openhands-sdk/SKILL.md +3 -0
- package/skills/slack-channel-monitor/README.md +2 -1
- package/skills/slack-channel-monitor/SKILL.md +22 -4
- package/tests/test_sync_extensions.py +14 -0
- /package/plugins/issue-duplicate-checker/scripts/{post_duplicate_notice.js → post_duplicate_notice.cjs} +0 -0
- /package/plugins/issue-duplicate-checker/scripts/{remove_duplicate_candidate_label.js → remove_duplicate_candidate_label.cjs} +0 -0
- /package/skills/github-pr-reviewer/commands/{pr-reviewer:setup.md → pr-reviewer-setup.md} +0 -0
- /package/skills/github-repo-monitor/commands/{github-monitor:poll.md → github-monitor-poll.md} +0 -0
- /package/skills/incident-retrospective/commands/{incident-retro:setup.md → incident-retro-setup.md} +0 -0
- /package/skills/linear-triage/commands/{linear-triage:setup.md → linear-triage-setup.md} +0 -0
- /package/skills/openhands-automation/commands/{automation:create.md → automation-create.md} +0 -0
- /package/skills/research-brief/commands/{research-brief:setup.md → research-brief-setup.md} +0 -0
- /package/skills/slack-channel-monitor/commands/{slack-monitor:poll.md → slack-monitor-poll.md} +0 -0
- /package/skills/slack-standup-digest/commands/{standup-digest:setup.md → standup-digest-setup.md} +0 -0
|
@@ -11,79 +11,222 @@ triggers:
|
|
|
11
11
|
|
|
12
12
|
# GitHub PR Reviewer Automation
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Create a cron automation that polls a GitHub repository, reviews each open
|
|
15
|
+
pull request exactly once, and posts the AI review as a GitHub comment.
|
|
16
|
+
|
|
17
|
+
The automation script is fully deterministic: PR discovery, state tracking,
|
|
18
|
+
and deduplication are handled in Python. The LLM is only invoked to write
|
|
19
|
+
the review text for PRs not yet seen, never for orchestration.
|
|
15
20
|
|
|
16
21
|
---
|
|
17
22
|
|
|
18
23
|
## Prerequisites
|
|
19
24
|
|
|
20
|
-
### Required
|
|
25
|
+
### Required secret
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
Verify that the following secret is set in **OpenHands Settings → Secrets**:
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
| Secret name | Token type | Minimum permissions |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` (private) or `public_repo` (public) |
|
|
32
|
+
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Pull requests: Read and Write |
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
Check with:
|
|
35
|
+
```bash
|
|
36
|
+
curl -s https://api.github.com/user \
|
|
37
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
38
|
+
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))"
|
|
39
|
+
```
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
2. **Trigger type** — event-based (reacts to `pull_request.opened` / `pull_request.synchronize`) or cron-based polling
|
|
30
|
-
3. **Review tone** — thorough, concise, or friendly
|
|
31
|
-
4. **Auto-post or draft** — should reviews be posted as GitHub comments automatically, or saved as draft artifacts for human approval first?
|
|
32
|
-
5. **Filters** — any label, branch, or author filters (e.g. skip drafts, only review PRs with `needs-review` label)
|
|
41
|
+
If the token is missing or invalid, inform the user and stop.
|
|
33
42
|
|
|
34
43
|
---
|
|
35
44
|
|
|
36
45
|
## Setup Workflow
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
Follow these steps in order.
|
|
48
|
+
|
|
49
|
+
### Step 1 — Verify GITHUB_PERSONAL_ACCESS_TOKEN
|
|
50
|
+
|
|
51
|
+
Run the `curl` check above.
|
|
52
|
+
|
|
53
|
+
- If absent: *"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in
|
|
54
|
+
OpenHands Settings → Secrets."* Stop.
|
|
55
|
+
- If the API returns `{"message": "Bad credentials"}`: tell the user the
|
|
56
|
+
token is invalid and ask them to update it. Stop.
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
### Step 2 — Collect repository
|
|
59
|
+
|
|
60
|
+
Ask: *"Which GitHub repository should be monitored?
|
|
61
|
+
(Format: `owner/repo`, e.g. `myorg/backend`)"*
|
|
62
|
+
|
|
63
|
+
Validate access:
|
|
64
|
+
```bash
|
|
65
|
+
curl -s "https://api.github.com/repos/{owner}/{repo}" \
|
|
66
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
67
|
+
| python3 -c "
|
|
68
|
+
import json, sys
|
|
69
|
+
d = json.load(sys.stdin)
|
|
70
|
+
if 'message' in d:
|
|
71
|
+
print('ERROR:', d['message'])
|
|
72
|
+
else:
|
|
73
|
+
print(f\"Accessible. Private: {d.get('private')}. Permissions: {d.get('permissions')}\")
|
|
74
|
+
"
|
|
41
75
|
```
|
|
42
|
-
|
|
76
|
+
|
|
77
|
+
Record `REPO = "{owner}/{repo}"`.
|
|
78
|
+
|
|
79
|
+
### Step 3 — Collect review tone
|
|
80
|
+
|
|
81
|
+
Ask: *"What review tone should the reviewer use?
|
|
82
|
+
1. Thorough (default) — comprehensive coverage of correctness, security, tests, style
|
|
83
|
+
2. Concise — high-signal only, skips minor style feedback
|
|
84
|
+
3. Friendly — constructive and encouraging
|
|
85
|
+
(Press Enter for Thorough, or type your choice or any custom style description)"*
|
|
86
|
+
|
|
87
|
+
Map the choice to `REVIEW_TONE`:
|
|
88
|
+
|
|
89
|
+
| Answer | `REVIEW_TONE` | `REVIEW_STYLE_INSTRUCTIONS` |
|
|
90
|
+
|--------|--------------|------------------------------|
|
|
91
|
+
| 1 / Enter | `"thorough"` | `""` |
|
|
92
|
+
| 2 | `"concise"` | `""` |
|
|
93
|
+
| 3 | `"friendly"` | `""` |
|
|
94
|
+
| Custom text (e.g. "hostile pirate") | `"thorough"` | the custom text verbatim |
|
|
95
|
+
|
|
96
|
+
### Step 4 — Collect cron schedule
|
|
97
|
+
|
|
98
|
+
Ask: *"How often should the automation poll for new PRs?
|
|
99
|
+
(Press Enter for the default: every 5 minutes.
|
|
100
|
+
Use a cron expression for a different interval, e.g. `0 * * * *` = hourly)"*
|
|
101
|
+
|
|
102
|
+
Default: `*/5 * * * *`.
|
|
103
|
+
|
|
104
|
+
Record as `CRON_SCHEDULE`.
|
|
105
|
+
|
|
106
|
+
### Step 5 — Generate the automation script
|
|
107
|
+
|
|
108
|
+
Read `scripts/main.py` from this skill's directory. Apply exactly four
|
|
109
|
+
constant substitutions near the top of the file:
|
|
110
|
+
|
|
111
|
+
| Placeholder | Replace with |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `REPO = "owner/repo"` | `REPO = "{owner_repo}"` |
|
|
114
|
+
| `REVIEW_TONE = "thorough"` | `REVIEW_TONE = "{review_tone}"` |
|
|
115
|
+
| `REVIEW_STYLE_INSTRUCTIONS = ""` | `REVIEW_STYLE_INSTRUCTIONS = "{style_instructions}"` |
|
|
116
|
+
| `DEFAULT_OPENHANDS_URL = "http://localhost:8000"` | leave unchanged unless the user has a preference |
|
|
117
|
+
|
|
118
|
+
Write the customised script to a temporary build directory:
|
|
119
|
+
```bash
|
|
120
|
+
mkdir -p /tmp/pr-reviewer-build
|
|
121
|
+
# write the customised main.py to /tmp/pr-reviewer-build/main.py
|
|
43
122
|
```
|
|
44
123
|
|
|
45
|
-
|
|
124
|
+
Validate syntax before packaging:
|
|
125
|
+
```bash
|
|
126
|
+
python3 -m py_compile /tmp/pr-reviewer-build/main.py && echo "Syntax OK"
|
|
127
|
+
```
|
|
46
128
|
|
|
47
|
-
|
|
129
|
+
Fix any syntax errors before proceeding.
|
|
48
130
|
|
|
49
|
-
|
|
50
|
-
Check `<RUNTIME_SERVICES>` for deployment reachability. If the deployment has a public URL, recommend an event trigger on `pull_request.opened` and `pull_request.synchronize`.
|
|
131
|
+
### Step 6 — Package and upload
|
|
51
132
|
|
|
52
|
-
|
|
53
|
-
|
|
133
|
+
Determine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`
|
|
134
|
+
block in your system context:
|
|
135
|
+
- **OPENHANDS_HOST**: the Automation backend `url_from_agent`
|
|
136
|
+
- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`
|
|
54
137
|
|
|
55
|
-
|
|
138
|
+
```bash
|
|
139
|
+
tar -czf /tmp/pr-reviewer.tar.gz -C /tmp/pr-reviewer-build .
|
|
56
140
|
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
141
|
+
TARBALL_PATH=$(curl -s -X POST \
|
|
142
|
+
"${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-pr-reviewer" \
|
|
143
|
+
-H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
|
|
144
|
+
-H "Content-Type: application/gzip" \
|
|
145
|
+
--data-binary @/tmp/pr-reviewer.tar.gz \
|
|
146
|
+
| python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])")
|
|
62
147
|
|
|
63
|
-
|
|
148
|
+
echo "Uploaded: $TARBALL_PATH"
|
|
149
|
+
```
|
|
64
150
|
|
|
65
|
-
|
|
66
|
-
- Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`
|
|
67
|
-
- Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`
|
|
151
|
+
### Step 7 — Register the automation
|
|
68
152
|
|
|
69
|
-
Use the **prompt preset** endpoint:
|
|
70
153
|
```bash
|
|
71
|
-
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1
|
|
154
|
+
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \
|
|
72
155
|
-H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
|
|
73
156
|
-H "Content-Type: application/json" \
|
|
74
|
-
-d
|
|
75
|
-
"name": "GitHub PR Reviewer",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
|
|
157
|
+
-d "{
|
|
158
|
+
\"name\": \"GitHub PR Reviewer: {owner}/{repo}\",
|
|
159
|
+
\"trigger\": {\"type\": \"cron\", \"schedule\": \"{cron_schedule}\"},
|
|
160
|
+
\"tarball_path\": \"$TARBALL_PATH\",
|
|
161
|
+
\"entrypoint\": \"python3 main.py\",
|
|
162
|
+
\"timeout\": 300
|
|
163
|
+
}" | python3 -m json.tool
|
|
79
164
|
```
|
|
80
165
|
|
|
81
|
-
|
|
166
|
+
Record the returned `id`.
|
|
167
|
+
|
|
168
|
+
### Step 8 — Confirm
|
|
82
169
|
|
|
83
170
|
Tell the user:
|
|
171
|
+
|
|
84
172
|
> ✅ **GitHub PR Reviewer** is running!
|
|
85
173
|
>
|
|
86
174
|
> - Automation ID: `{id}`
|
|
87
|
-
> -
|
|
88
|
-
> -
|
|
89
|
-
> -
|
|
175
|
+
> - Repository: `{owner}/{repo}`
|
|
176
|
+
> - Review tone: `{tone}`
|
|
177
|
+
> - Polling schedule: `{cron_schedule}`
|
|
178
|
+
> - State file: `~/.openhands/workspaces/automation-state/github_pr_reviewer_{id}.json`
|
|
179
|
+
>
|
|
180
|
+
> The next cron run will discover all currently open PRs and queue reviews.
|
|
181
|
+
> Each PR is reviewed exactly once; state is stored in the JSON file above.
|
|
182
|
+
> To force a re-review of all PRs, delete the state file.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Runtime Behaviour (per poll)
|
|
187
|
+
|
|
188
|
+
Each cron run executes `main.py`, which:
|
|
189
|
+
|
|
190
|
+
1. **Loads state** from the JSON file (see `references/state-schema.md`).
|
|
191
|
+
2. **Resolves and validates `GITHUB_PERSONAL_ACCESS_TOKEN`** — aborts
|
|
192
|
+
immediately if absent or invalid.
|
|
193
|
+
3. **Lists all open PRs** in the configured repository.
|
|
194
|
+
4. **For each PR not yet in state**:
|
|
195
|
+
- Fetches the unified diff via the GitHub API.
|
|
196
|
+
- Skips PRs whose diff exceeds `MAX_DIFF_LINES_SKIP` (default 5000 lines)
|
|
197
|
+
and posts an explanatory comment.
|
|
198
|
+
- Truncates diffs larger than `MAX_DIFF_LINES` (default 500 lines) and
|
|
199
|
+
notes this in the prompt.
|
|
200
|
+
- Creates an OpenHands conversation with the PR metadata, diff, and
|
|
201
|
+
configured tone instructions as the initial message.
|
|
202
|
+
- Posts an acknowledgement comment on the PR with a link to the conversation.
|
|
203
|
+
- Records the PR in state with `status: "active"`.
|
|
204
|
+
5. **For each active conversation**:
|
|
205
|
+
- Skips PRs that have been closed or merged (marks them closed silently).
|
|
206
|
+
- Checks the conversation's `execution_status`.
|
|
207
|
+
- When it reaches `idle`, `finished`, `error`, or `stuck`:
|
|
208
|
+
posts the agent's final response as a GitHub comment and marks the
|
|
209
|
+
conversation `closed`.
|
|
210
|
+
6. **Saves updated state** and fires the completion callback.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Additional Resources
|
|
215
|
+
|
|
216
|
+
- **`references/state-schema.md`** — State JSON schema, field definitions,
|
|
217
|
+
and conversation lifecycle diagram.
|
|
218
|
+
- **`scripts/main.py`** — The complete automation script. Customise the
|
|
219
|
+
four constants at the top before packaging.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Troubleshooting
|
|
224
|
+
|
|
225
|
+
| Symptom | Likely cause | Fix |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| Bot never posts reviews | `GITHUB_PERSONAL_ACCESS_TOKEN` missing or wrong scopes | Verify token; check Step 1 |
|
|
228
|
+
| "Bad credentials" in run logs | Token expired | Rotate and update the secret |
|
|
229
|
+
| 404 on repo access | Repo name wrong or no access | Re-check `owner/repo` and token permissions |
|
|
230
|
+
| Same PR reviewed twice | State file deleted or corrupted | Check that the state file path is stable across runs |
|
|
231
|
+
| Review never posted | Conversation stuck in `running` | Open the conversation in the OpenHands UI |
|
|
232
|
+
| PR skipped silently | Diff too large | Raise `MAX_DIFF_LINES_SKIP` in the script or split the PR |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# State File Schema
|
|
2
|
+
|
|
3
|
+
The automation maintains a JSON state file that persists across polling runs.
|
|
4
|
+
It is the source of truth for which PRs have been reviewed and which
|
|
5
|
+
conversations are still active.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## File Location
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
{WORKSPACE_BASE_ROOT}/automation-state/github_pr_reviewer_{automation_id}.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`WORKSPACE_BASE_ROOT` is derived by going two levels up from the `WORKSPACE_BASE`
|
|
16
|
+
environment variable (stripping `automation-runs/{run_id}`).
|
|
17
|
+
|
|
18
|
+
Example on a local install:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
~/.openhands/workspaces/automation-state/github_pr_reviewer_abc12345-….json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `automation_id` is read from the `AUTOMATION_EVENT_PAYLOAD` environment
|
|
25
|
+
variable (field `automation_id`).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Top-Level Schema
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"version": 1, // schema version (integer)
|
|
34
|
+
"repo": "owner/repo", // the monitored repository
|
|
35
|
+
"conversations": { } // see ConversationRecord below
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## `conversations` Map
|
|
42
|
+
|
|
43
|
+
Key: `"{pr_number}"` (string) — uniquely identifies a PR in the repo.
|
|
44
|
+
|
|
45
|
+
Value: **ConversationRecord**
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
{
|
|
49
|
+
// Always present
|
|
50
|
+
"pr_number": 42, // GitHub PR number (integer)
|
|
51
|
+
"html_url": "https://github.com/owner/repo/pull/42", // PR URL
|
|
52
|
+
"status": "active", // "active" | "closed" | "skipped"
|
|
53
|
+
"last_activity": 1717200000.0, // Unix timestamp of last state change
|
|
54
|
+
|
|
55
|
+
// Present when status is "active" or "closed"
|
|
56
|
+
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
57
|
+
// OpenHands conversation UUID
|
|
58
|
+
|
|
59
|
+
// Present when status is "skipped"
|
|
60
|
+
"reason": "diff too large (6000 lines)"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Conversation Lifecycle
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
PR opened on GitHub
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
[active] ── conversation created, acknowledgement comment posted
|
|
73
|
+
│
|
|
74
|
+
▼ (on a later cron run, when conversation reaches terminal status)
|
|
75
|
+
[closed] ── review posted to PR as GitHub comment
|
|
76
|
+
│
|
|
77
|
+
▼ (if PR is merged/closed before review is ready)
|
|
78
|
+
[closed] ── no comment posted (silently closed)
|
|
79
|
+
|
|
80
|
+
── or ──
|
|
81
|
+
|
|
82
|
+
PR diff too large
|
|
83
|
+
│
|
|
84
|
+
▼
|
|
85
|
+
[skipped] ── explanatory comment posted, PR never re-queued
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Resetting State
|
|
91
|
+
|
|
92
|
+
To force a re-review of all PRs (e.g. after changing the review tone):
|
|
93
|
+
|
|
94
|
+
1. Delete the state file:
|
|
95
|
+
```bash
|
|
96
|
+
rm ~/.openhands/workspaces/automation-state/github_pr_reviewer_<id>.json
|
|
97
|
+
```
|
|
98
|
+
2. The next cron run will treat all open PRs as new and queue reviews for each.
|