@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.
Files changed (40) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/README.md +3 -2
  3. package/automations/catalog/github-pr-reviewer.json +1 -1
  4. package/automations/catalog/github-repo-monitor.json +1 -1
  5. package/marketplaces/openhands-extensions.json +13 -0
  6. package/package.json +1 -1
  7. package/plugins/issue-duplicate-checker/action.yml +2 -2
  8. package/plugins/pr-review/README.md +1 -1
  9. package/plugins/qa-changes/action.yml +4 -2
  10. package/plugins/release-notes/action.yml +3 -2
  11. package/plugins/release-notes/workflows/release-notes.yml +10 -4
  12. package/plugins/vulnerability-remediation/README.md +4 -2
  13. package/plugins/vulnerability-remediation/action.yml +8 -4
  14. package/pyproject.toml +1 -1
  15. package/scripts/sync_extensions.py +2 -1
  16. package/skills/github-actions/.plugin/plugin.json +19 -0
  17. package/skills/github-actions/README.md +190 -0
  18. package/skills/github-actions/SKILL.md +44 -0
  19. package/skills/github-pr-reviewer/SKILL.md +183 -40
  20. package/skills/github-pr-reviewer/references/state-schema.md +98 -0
  21. package/skills/github-pr-reviewer/scripts/main.py +625 -0
  22. package/skills/github-repo-monitor/README.md +1 -1
  23. package/skills/github-repo-monitor/SKILL.md +9 -9
  24. package/skills/github-repo-monitor/references/github-api.md +3 -3
  25. package/skills/github-repo-monitor/scripts/main.py +11 -11
  26. package/skills/index.js +16 -4
  27. package/skills/openhands-sdk/SKILL.md +3 -0
  28. package/skills/slack-channel-monitor/README.md +2 -1
  29. package/skills/slack-channel-monitor/SKILL.md +22 -4
  30. package/tests/test_sync_extensions.py +14 -0
  31. /package/plugins/issue-duplicate-checker/scripts/{post_duplicate_notice.js → post_duplicate_notice.cjs} +0 -0
  32. /package/plugins/issue-duplicate-checker/scripts/{remove_duplicate_candidate_label.js → remove_duplicate_candidate_label.cjs} +0 -0
  33. /package/skills/github-pr-reviewer/commands/{pr-reviewer:setup.md → pr-reviewer-setup.md} +0 -0
  34. /package/skills/github-repo-monitor/commands/{github-monitor:poll.md → github-monitor-poll.md} +0 -0
  35. /package/skills/incident-retrospective/commands/{incident-retro:setup.md → incident-retro-setup.md} +0 -0
  36. /package/skills/linear-triage/commands/{linear-triage:setup.md → linear-triage-setup.md} +0 -0
  37. /package/skills/openhands-automation/commands/{automation:create.md → automation-create.md} +0 -0
  38. /package/skills/research-brief/commands/{research-brief:setup.md → research-brief-setup.md} +0 -0
  39. /package/skills/slack-channel-monitor/commands/{slack-monitor:poll.md → slack-monitor-poll.md} +0 -0
  40. /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
- Set up a cron or event-triggered automation that reviews GitHub pull requests.
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 integration
25
+ ### Required secret
21
26
 
22
- - **GitHub MCP** must be installed in Settings → MCP.
27
+ Verify that the following secret is set in **OpenHands Settings → Secrets**:
23
28
 
24
- ### Information to collect
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
- Ask the user for:
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
- 1. **Repositories** — which repos should be watched (e.g. `myorg/backend`, `myorg/frontend`)
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
- ### Step 1 — Verify GitHub MCP access
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
- Confirm the GitHub MCP integration is working:
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
- Use the GitHub MCP to list recent pull requests in one of the target repositories.
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
- If it fails, tell the user to install the GitHub MCP integration first.
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
- ### Step 2 — Determine trigger type
129
+ Fix any syntax errors before proceeding.
48
130
 
49
- **Event-based (recommended if publicly reachable):**
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
- **Cron-based (local/private deployments):**
53
- If the deployment is local, set up a cron schedule (e.g. every 5 minutes) that polls for recently updated PRs.
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
- ### Step 3 — Build the review prompt
138
+ ```bash
139
+ tar -czf /tmp/pr-reviewer.tar.gz -C /tmp/pr-reviewer-build .
56
140
 
57
- Construct a prompt for the automation that includes:
58
- - The user's chosen repositories
59
- - Review focus areas (correctness, security, tests, maintainability)
60
- - Review tone preference
61
- - Whether to auto-post or draft
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
- ### Step 4 — Create the automation
148
+ echo "Uploaded: $TARBALL_PATH"
149
+ ```
64
150
 
65
- Read the Automation backend URL and auth from `<RUNTIME_SERVICES>`:
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/preset/prompt" \
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
- "prompt": "<constructed review prompt>",
77
- "trigger": <trigger config from step 2>
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
- ### Step 5 — Confirm
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
- > - Repositories: `{repo list}`
88
- > - Trigger: `{trigger description}`
89
- > - Review style: `{tone}`, `{auto-post or draft}`
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.