@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.
Files changed (44) 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/index.d.ts +2 -0
  6. package/index.js +1 -0
  7. package/marketplaces/openhands-extensions.json +13 -0
  8. package/package.json +9 -1
  9. package/plugins/issue-duplicate-checker/action.yml +2 -2
  10. package/plugins/pr-review/README.md +1 -1
  11. package/plugins/qa-changes/action.yml +4 -2
  12. package/plugins/release-notes/action.yml +3 -2
  13. package/plugins/release-notes/workflows/release-notes.yml +10 -4
  14. package/plugins/vulnerability-remediation/README.md +4 -2
  15. package/plugins/vulnerability-remediation/action.yml +8 -4
  16. package/pyproject.toml +1 -1
  17. package/scripts/build-skills-catalog.mjs +100 -0
  18. package/scripts/sync_extensions.py +2 -1
  19. package/skills/github-actions/.plugin/plugin.json +19 -0
  20. package/skills/github-actions/README.md +190 -0
  21. package/skills/github-actions/SKILL.md +44 -0
  22. package/skills/github-pr-review/SKILL.md +40 -2
  23. package/skills/github-repo-monitor/README.md +1 -1
  24. package/skills/github-repo-monitor/SKILL.md +9 -9
  25. package/skills/github-repo-monitor/references/github-api.md +3 -3
  26. package/skills/github-repo-monitor/scripts/main.py +11 -11
  27. package/skills/index.d.ts +11 -0
  28. package/skills/index.js +477 -0
  29. package/skills/openhands-sdk/SKILL.md +1 -0
  30. package/skills/slack-channel-monitor/README.md +2 -1
  31. package/skills/slack-channel-monitor/SKILL.md +22 -4
  32. package/tests/test_github_pr_review_skill.py +114 -0
  33. package/tests/test_skills_catalog.py +336 -0
  34. package/tests/test_sync_extensions.py +14 -0
  35. /package/plugins/issue-duplicate-checker/scripts/{post_duplicate_notice.js → post_duplicate_notice.cjs} +0 -0
  36. /package/plugins/issue-duplicate-checker/scripts/{remove_duplicate_candidate_label.js → remove_duplicate_candidate_label.cjs} +0 -0
  37. /package/skills/github-pr-reviewer/commands/{pr-reviewer:setup.md → pr-reviewer-setup.md} +0 -0
  38. /package/skills/github-repo-monitor/commands/{github-monitor:poll.md → github-monitor-poll.md} +0 -0
  39. /package/skills/incident-retrospective/commands/{incident-retro:setup.md → incident-retro-setup.md} +0 -0
  40. /package/skills/linear-triage/commands/{linear-triage:setup.md → linear-triage-setup.md} +0 -0
  41. /package/skills/openhands-automation/commands/{automation:create.md → automation-create.md} +0 -0
  42. /package/skills/research-brief/commands/{research-brief:setup.md → research-brief-setup.md} +0 -0
  43. /package/skills/slack-channel-monitor/commands/{slack-monitor:poll.md → slack-monitor-poll.md} +0 -0
  44. /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
- **Important**: The suggestion must have the same number of lines as the range (e.g., lines 10-12 = 3 lines).
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
- - `GITHUB_TOKEN` secret set in OpenHands Settings → Secrets
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
- | `GITHUB_TOKEN` | Classic PAT | `repo` (private repos) or `public_repo` (public repos) |
49
- | `GITHUB_TOKEN` | Fine-grained PAT | Issues: Read and Write |
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 $GITHUB_TOKEN" \
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 GITHUB_TOKEN
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
- *"GITHUB_TOKEN is not set. Please add it in OpenHands Settings → Secrets
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 $GITHUB_TOKEN" \
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 `GITHUB_TOKEN` owner.
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 GITHUB_TOKEN** — aborts immediately if absent or invalid.
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 | `GITHUB_TOKEN` missing or wrong scopes | Verify token with `curl /user`; check scopes in Step 1 |
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 {GITHUB_TOKEN}
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 $GITHUB_TOKEN" \
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 $GITHUB_TOKEN" \
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
- GITHUB_TOKEN - Personal Access Token
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 GITHUB_TOKEN owner.
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 GITHUB_TOKEN from secrets. Raises RuntimeError if absent."""
210
+ """Fetch GITHUB_PERSONAL_ACCESS_TOKEN from secrets. Raises RuntimeError if absent."""
211
211
  try:
212
- token = get_secret("GITHUB_TOKEN")
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
- "GITHUB_TOKEN secret is not set. "
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
- "GITHUB_TOKEN is invalid or expired. "
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 GITHUB_TOKEN. "
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 GITHUB_TOKEN has the required permissions."
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"GITHUB_TOKEN cannot post comments to private repository '{repo}'. "
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"GITHUB_TOKEN cannot post comments to public repository '{repo}'. "
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 GITHUB_TOKEN secret is available if you need to interact with 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."
@@ -0,0 +1,11 @@
1
+ export interface SkillCatalogEntry {
2
+ name: string;
3
+ description: string;
4
+ triggers: string[];
5
+ content: string;
6
+ license?: string;
7
+ compatibility?: string;
8
+ }
9
+
10
+ export const SKILLS_CATALOG: SkillCatalogEntry[];
11
+ export default SKILLS_CATALOG;