@openhands/extensions 0.2.0 → 0.4.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/.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/index.d.ts +2 -0
- package/index.js +1 -0
- package/marketplaces/openhands-extensions.json +13 -0
- package/package.json +9 -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/build-skills-catalog.mjs +100 -0
- 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-review/SKILL.md +40 -2
- 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.d.ts +11 -0
- package/skills/index.js +477 -0
- package/skills/openhands-sdk/SKILL.md +1 -0
- package/skills/slack-channel-monitor/README.md +2 -1
- package/skills/slack-channel-monitor/SKILL.md +22 -4
- package/tests/test_github_pr_review_skill.py +114 -0
- package/tests/test_skills_catalog.py +336 -0
- 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
|
@@ -76,7 +76,7 @@ For comments spanning multiple lines, add `start_line` to specify the range:
|
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
**`start_line`/`line` define the range that will be REPLACED.** The suggestion block may have any number of lines — it does **not** have to match the range size. See the next section for the exact semantics; getting this wrong is how suggestions silently delete or duplicate code.
|
|
80
80
|
|
|
81
81
|
## Priority Labels
|
|
82
82
|
|
|
@@ -114,6 +114,44 @@ Use suggestions for: renaming, typos, small refactors (1-5 lines), type hints, d
|
|
|
114
114
|
|
|
115
115
|
Avoid for: large refactors, architectural changes, ambiguous improvements.
|
|
116
116
|
|
|
117
|
+
### How Suggestions Actually Work (READ THIS BEFORE WRITING ONE)
|
|
118
|
+
|
|
119
|
+
A suggestion block **replaces** the targeted range with its contents. The replaced range is:
|
|
120
|
+
|
|
121
|
+
- `line` only → the single line `line` (replaces 1 line)
|
|
122
|
+
- `start_line` + `line` → the inclusive range `start_line..line` (replaces `line - start_line + 1` lines)
|
|
123
|
+
|
|
124
|
+
The suggestion content can be **any number of lines** — 0 (deletion), 1, or many. It does not have to match the range size. Whatever is between the ` ```suggestion ` and closing ` ``` ` fences becomes the new content of those lines.
|
|
125
|
+
|
|
126
|
+
Writing the wrong combination of `start_line`/`line` and suggestion body is what causes accepted suggestions to **duplicate** or **delete** code. Use the table below as your contract:
|
|
127
|
+
|
|
128
|
+
| Intent | `start_line` | `line` | Suggestion body must contain |
|
|
129
|
+
|--------|--------------|--------|-------------------------------|
|
|
130
|
+
| Change line N | omit | N | the new content for line N |
|
|
131
|
+
| Change lines N..M | N | M | the new content for the whole block |
|
|
132
|
+
| **Add** a line **after** line N (keep line N) | omit | N | line N's exact current text, then the new line(s) |
|
|
133
|
+
| **Add** a line **before** line N (keep line N) | omit | N | the new line(s), then line N's exact current text |
|
|
134
|
+
| **Insert** lines inside range N..M (keep N..M) | N | M | every original line in N..M plus the new lines, in the final desired order |
|
|
135
|
+
| **Delete** line N | omit | N | empty body (just an empty ` ```suggestion ``` ` block) |
|
|
136
|
+
| **Delete** lines N..M | N | M | empty body |
|
|
137
|
+
|
|
138
|
+
### Common Mistakes That Break Code
|
|
139
|
+
|
|
140
|
+
1. **Duplicated lines.** You copy a neighboring line (N-1 or N+1) into the suggestion body as context — that line is still present in the file outside the replaced range, so accepting the suggestion inserts a second copy of it. Fix: only include lines that fall within the targeted range, plus any genuinely new content.
|
|
141
|
+
2. **Disappearing lines.** You target `start_line=10, line=12` to comment on a 3-line block, but your suggestion body only contains 1 line because you "only want to change line 11". Accepting that suggestion deletes lines 10 and 12. Fix: either narrow the range to just line 11, or include lines 10 and 12 verbatim in the body.
|
|
142
|
+
3. **Description does not match the suggestion.** The prose says "rename this variable" but the suggestion replaces an entire function. Or the prose says "add a None check" but the suggestion only contains the check (deleting the original code). Fix: after writing the suggestion, re-read the prose and confirm the resulting file would match it line-for-line.
|
|
143
|
+
|
|
144
|
+
### Mandatory Verification Before Posting
|
|
145
|
+
|
|
146
|
+
For every comment that contains a ` ```suggestion ``` ` block, do this check before adding it to the review JSON:
|
|
147
|
+
|
|
148
|
+
1. Read the actual file lines that will be replaced: `sed -n '<start_line>,<line>p' <path>` (or `sed -n '<line>p' <path>` for a single-line target).
|
|
149
|
+
2. Mentally apply the suggestion: drop those lines, splice in the suggestion body, and look at the result in context.
|
|
150
|
+
3. Confirm the resulting code matches **exactly** what your prose description promises — no extra duplicated line above/below, no original line accidentally dropped, no off-by-one.
|
|
151
|
+
4. If the change cannot be expressed cleanly as a contiguous replacement (e.g., it touches non-adjacent lines, or it depends on edits elsewhere in the file), do **not** use a suggestion block — describe the change in prose instead.
|
|
152
|
+
|
|
153
|
+
If you are not 100% sure the suggestion will produce the exact code you described, drop the ` ```suggestion ``` ` block and leave a regular inline comment. A correct prose comment is always better than a one-click suggestion that silently corrupts the file.
|
|
154
|
+
|
|
117
155
|
## Finding Line Numbers
|
|
118
156
|
|
|
119
157
|
```bash
|
|
@@ -143,6 +181,6 @@ curl -X POST \
|
|
|
143
181
|
3. Post **ONE** review using `gh api --input /tmp/review.json`
|
|
144
182
|
4. Use priority labels (🔴🟠🟡) on every comment
|
|
145
183
|
5. Do NOT post comments for code that is acceptable — only comment when action is needed
|
|
146
|
-
6. Use suggestion syntax for concrete code changes
|
|
184
|
+
6. Use suggestion syntax for concrete code changes, but only after verifying the resulting code matches your description (see "How Suggestions Actually Work")
|
|
147
185
|
7. Keep the review body brief (details go in inline comments)
|
|
148
186
|
8. If no issues: post a short approval message with no inline comments
|
|
@@ -41,7 +41,7 @@ event-type selection, cron schedule, and automation creation automatically.
|
|
|
41
41
|
|
|
42
42
|
## Requirements
|
|
43
43
|
|
|
44
|
-
- `
|
|
44
|
+
- `GITHUB_PERSONAL_ACCESS_TOKEN` secret set in OpenHands Settings → Secrets
|
|
45
45
|
- Classic PAT: `repo` (private repos) or `public_repo` (public repos)
|
|
46
46
|
- Fine-grained PAT: Issues — Read and Write
|
|
47
47
|
- The monitored repository must be accessible with that token
|
|
@@ -45,13 +45,13 @@ before proceeding:
|
|
|
45
45
|
|
|
46
46
|
| Secret name | Token type | Minimum permissions |
|
|
47
47
|
|---|---|---|
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
48
|
+
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` (private repos) or `public_repo` (public repos) |
|
|
49
|
+
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Issues: Read and Write |
|
|
50
50
|
|
|
51
51
|
Check with:
|
|
52
52
|
```bash
|
|
53
53
|
curl -s https://api.github.com/user \
|
|
54
|
-
-H "Authorization: Bearer $
|
|
54
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
55
55
|
-H "Accept: application/vnd.github+json" \
|
|
56
56
|
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))"
|
|
57
57
|
```
|
|
@@ -71,12 +71,12 @@ function without GitHub credentials.
|
|
|
71
71
|
|
|
72
72
|
Follow these steps in order.
|
|
73
73
|
|
|
74
|
-
### Step 1 - Verify
|
|
74
|
+
### Step 1 - Verify GITHUB_PERSONAL_ACCESS_TOKEN
|
|
75
75
|
|
|
76
76
|
Fetch the secret and run the `curl` check above.
|
|
77
77
|
|
|
78
78
|
- If the secret is absent: tell the user
|
|
79
|
-
*"
|
|
79
|
+
*"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in OpenHands Settings → Secrets
|
|
80
80
|
(classic PAT with `repo` or `public_repo` scope, or a fine-grained PAT
|
|
81
81
|
with Issues: Read and Write)."* Then stop.
|
|
82
82
|
|
|
@@ -92,7 +92,7 @@ Validate access and write permissions:
|
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
curl -s "https://api.github.com/repos/{owner}/{repo}" \
|
|
95
|
-
-H "Authorization: Bearer $
|
|
95
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
96
96
|
-H "Accept: application/vnd.github+json" \
|
|
97
97
|
| python3 -c "
|
|
98
98
|
import json, sys
|
|
@@ -123,7 +123,7 @@ Record as `TRIGGER_PHRASE`. Default: `"@openhands"`.
|
|
|
123
123
|
### Step 4 - Collect allowed GitHub logins
|
|
124
124
|
|
|
125
125
|
Ask the user: *"Which GitHub users may trigger this automation?
|
|
126
|
-
Press Enter to allow only the authenticated `
|
|
126
|
+
Press Enter to allow only the authenticated `GITHUB_PERSONAL_ACCESS_TOKEN` owner.
|
|
127
127
|
You may also provide comma-separated GitHub logins, or `*` to allow any
|
|
128
128
|
non-bot commenter on the monitored repository."*
|
|
129
129
|
|
|
@@ -261,7 +261,7 @@ Tell the user:
|
|
|
261
261
|
Each cron run executes `main.py`, which:
|
|
262
262
|
|
|
263
263
|
1. **Loads state** from the JSON file (see `references/state-schema.md`).
|
|
264
|
-
2. **Resolves and validates
|
|
264
|
+
2. **Resolves and validates GITHUB_PERSONAL_ACCESS_TOKEN** — aborts immediately if absent or invalid.
|
|
265
265
|
3. **Polls for new events** since the previous `last_poll` timestamp:
|
|
266
266
|
- `GET /repos/{owner}/{repo}/issues/comments?since=…` for `issue_comment`
|
|
267
267
|
- `GET /repos/{owner}/{repo}/pulls/comments?since=…` for `pr_review_comment`
|
|
@@ -307,7 +307,7 @@ Each cron run executes `main.py`, which:
|
|
|
307
307
|
|
|
308
308
|
| Symptom | Likely cause | Fix |
|
|
309
309
|
|---|---|---|
|
|
310
|
-
| Bot doesn't respond to comments | `
|
|
310
|
+
| Bot doesn't respond to comments | `GITHUB_PERSONAL_ACCESS_TOKEN` missing or wrong scopes | Verify token with `curl /user`; check scopes in Step 1 |
|
|
311
311
|
| "Bad credentials" in run logs | Token expired | Rotate token and update the secret in Settings |
|
|
312
312
|
| 404 on repo access | Repo name wrong or token has no access | Re-check `owner/repo` spelling; add token as collaborator |
|
|
313
313
|
| Comments posted but no conversation created | Agent server URL wrong | Check `OPENHANDS_URL` secret and `AGENT_SERVER_URL` env var |
|
|
@@ -10,7 +10,7 @@ the GitHub Repository Monitor automation.
|
|
|
10
10
|
All requests use Bearer authentication:
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
Authorization: Bearer {
|
|
13
|
+
Authorization: Bearer {GITHUB_PERSONAL_ACCESS_TOKEN}
|
|
14
14
|
Accept: application/vnd.github+json
|
|
15
15
|
X-GitHub-Api-Version: 2022-11-28
|
|
16
16
|
```
|
|
@@ -37,7 +37,7 @@ X-GitHub-Api-Version: 2022-11-28
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
curl -I https://api.github.com/user \
|
|
40
|
-
-H "Authorization: Bearer $
|
|
40
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
41
41
|
| grep -i x-oauth-scopes
|
|
42
42
|
# X-OAuth-Scopes: repo, public_repo
|
|
43
43
|
```
|
|
@@ -209,7 +209,7 @@ Typical usage: **< 20 requests/hour** for a quiet repo,
|
|
|
209
209
|
Check remaining quota with:
|
|
210
210
|
```bash
|
|
211
211
|
curl -s https://api.github.com/rate_limit \
|
|
212
|
-
-H "Authorization: Bearer $
|
|
212
|
+
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
|
|
213
213
|
| python3 -c "
|
|
214
214
|
import json, sys
|
|
215
215
|
d = json.load(sys.stdin)
|
|
@@ -18,7 +18,7 @@ Configuration constants are embedded at automation-creation time by the skill.
|
|
|
18
18
|
See SKILL.md for the full setup workflow.
|
|
19
19
|
|
|
20
20
|
Required secrets (set in OpenHands Settings → Secrets):
|
|
21
|
-
|
|
21
|
+
GITHUB_PERSONAL_ACCESS_TOKEN - Personal Access Token
|
|
22
22
|
Classic PAT: 'repo' scope (private) or 'public_repo' (public)
|
|
23
23
|
Fine-grained PAT: Issues: Read and Write
|
|
24
24
|
|
|
@@ -40,7 +40,7 @@ from urllib.parse import urlencode
|
|
|
40
40
|
REPO = "owner/repo" # e.g. "microsoft/vscode"
|
|
41
41
|
TRIGGER_PHRASE = "@openhands" # case-insensitive
|
|
42
42
|
EVENT_TYPES = ["issue_comment"] # e.g. ["issue_comment", "pr_review_comment"]
|
|
43
|
-
# Who may trigger conversations. Default is the authenticated
|
|
43
|
+
# Who may trigger conversations. Default is the authenticated GITHUB_PERSONAL_ACCESS_TOKEN owner.
|
|
44
44
|
# Use ["*"] to allow any non-bot commenter, or explicit logins like ["octocat"].
|
|
45
45
|
ALLOWED_GITHUB_LOGINS = ["<TOKEN_OWNER>"]
|
|
46
46
|
DEFAULT_OPENHANDS_URL = "http://localhost:8000"
|
|
@@ -207,15 +207,15 @@ def _github_paginate(token: str, path: str, params: dict | None = None) -> list:
|
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
def _resolve_github_token() -> str:
|
|
210
|
-
"""Fetch
|
|
210
|
+
"""Fetch GITHUB_PERSONAL_ACCESS_TOKEN from secrets. Raises RuntimeError if absent."""
|
|
211
211
|
try:
|
|
212
|
-
token = get_secret("
|
|
212
|
+
token = get_secret("GITHUB_PERSONAL_ACCESS_TOKEN")
|
|
213
213
|
if token:
|
|
214
214
|
return token
|
|
215
215
|
except Exception:
|
|
216
216
|
pass
|
|
217
217
|
raise RuntimeError(
|
|
218
|
-
"
|
|
218
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN secret is not set. "
|
|
219
219
|
"Go to OpenHands Settings → Secrets and add your GitHub Personal Access Token."
|
|
220
220
|
)
|
|
221
221
|
|
|
@@ -231,7 +231,7 @@ def _verify_token_and_repo(token: str, repo: str) -> str:
|
|
|
231
231
|
except urllib.error.HTTPError as exc:
|
|
232
232
|
if exc.code == 401:
|
|
233
233
|
raise RuntimeError(
|
|
234
|
-
"
|
|
234
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN is invalid or expired. "
|
|
235
235
|
"Update it in OpenHands Settings → Secrets."
|
|
236
236
|
)
|
|
237
237
|
raise RuntimeError(f"GitHub /user check failed: {exc.code}")
|
|
@@ -247,13 +247,13 @@ def _verify_token_and_repo(token: str, repo: str) -> str:
|
|
|
247
247
|
except urllib.error.HTTPError as exc:
|
|
248
248
|
if exc.code == 404:
|
|
249
249
|
raise RuntimeError(
|
|
250
|
-
f"Repository '{repo}' not found or not accessible with the current
|
|
250
|
+
f"Repository '{repo}' not found or not accessible with the current GITHUB_PERSONAL_ACCESS_TOKEN. "
|
|
251
251
|
"Check the repo name (format: owner/repo) and token permissions."
|
|
252
252
|
)
|
|
253
253
|
if exc.code == 403:
|
|
254
254
|
raise RuntimeError(
|
|
255
255
|
f"Access denied to repository '{repo}'. "
|
|
256
|
-
"Ensure
|
|
256
|
+
"Ensure GITHUB_PERSONAL_ACCESS_TOKEN has the required permissions."
|
|
257
257
|
)
|
|
258
258
|
raise RuntimeError(f"GitHub /repos/{repo} check failed: {exc.code}")
|
|
259
259
|
|
|
@@ -268,7 +268,7 @@ def _verify_token_and_repo(token: str, repo: str) -> str:
|
|
|
268
268
|
# Private repo: must have push access or the 'repo' classic-PAT scope.
|
|
269
269
|
if not can_push and not has_repo_scope and scopes:
|
|
270
270
|
raise RuntimeError(
|
|
271
|
-
f"
|
|
271
|
+
f"GITHUB_PERSONAL_ACCESS_TOKEN cannot post comments to private repository '{repo}'. "
|
|
272
272
|
"A classic PAT needs the 'repo' scope; "
|
|
273
273
|
"a fine-grained PAT needs 'Issues: Read and Write' permission."
|
|
274
274
|
)
|
|
@@ -276,7 +276,7 @@ def _verify_token_and_repo(token: str, repo: str) -> str:
|
|
|
276
276
|
# Public repo: need at minimum 'public_repo' scope or push access.
|
|
277
277
|
if scopes and not (can_push or has_public_repo_scope or has_repo_scope):
|
|
278
278
|
raise RuntimeError(
|
|
279
|
-
f"
|
|
279
|
+
f"GITHUB_PERSONAL_ACCESS_TOKEN cannot post comments to public repository '{repo}'. "
|
|
280
280
|
"A classic PAT needs the 'public_repo' scope; "
|
|
281
281
|
"a fine-grained PAT needs 'Issues: Read and Write' permission."
|
|
282
282
|
)
|
|
@@ -591,7 +591,7 @@ def _build_initial_prompt(ctx: dict, trigger_comment: dict, event_type: str) ->
|
|
|
591
591
|
f"\nTriggering comment by @{trigger_author}:{path_info}\n"
|
|
592
592
|
f"---\n{trigger_body}\n---\n"
|
|
593
593
|
f"\nPlease analyse the request and take the appropriate action.\n"
|
|
594
|
-
f"The
|
|
594
|
+
f"The GITHUB_PERSONAL_ACCESS_TOKEN secret is available if you need to interact with the "
|
|
595
595
|
f"GitHub API (fetch the PR diff, create commits, update labels, etc.).\n"
|
|
596
596
|
f"When you are finished, summarise what you did clearly — that summary "
|
|
597
597
|
f"will be posted back to the GitHub {item_type} as a comment."
|