@openhands/extensions 0.20.0 → 0.22.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 (305) hide show
  1. package/.claude-plugin/marketplace.json +881 -0
  2. package/.codex-plugin/marketplace.json +881 -0
  3. package/.github/workflows/check-extensions.yml +4 -0
  4. package/.github/workflows/npm-publish.yml +9 -3
  5. package/.github/workflows/pr-artifacts.yml +294 -0
  6. package/.github/workflows/pr-description-check.yml +4 -2
  7. package/.plugin/marketplace.json +881 -0
  8. package/.release-please-manifest.json +1 -1
  9. package/AGENTS.md +27 -3
  10. package/README.md +6 -2
  11. package/automations/bundle-index.js +22 -3
  12. package/automations/catalog/github-agents-md-maintainer/manifest.json +2 -1
  13. package/automations/catalog/github-delivery-watchdog/manifest.json +79 -0
  14. package/automations/catalog/github-issue-to-pr/manifest.json +24 -15
  15. package/automations/catalog/github-issue-triage/manifest.json +73 -0
  16. package/automations/catalog/github-pr-reviewer/manifest.json +23 -15
  17. package/automations/catalog/gitlab-issue-to-mr/manifest.json +124 -0
  18. package/automations/catalog-index.js +17 -11
  19. package/integrations/catalog/reportportal.json +76 -0
  20. package/integrations/catalog-index.js +6 -4
  21. package/marketplaces/openhands-extensions.json +42 -0
  22. package/package.json +1 -1
  23. package/plugins/city-weather/.claude-plugin/plugin.json +13 -0
  24. package/plugins/city-weather/.codex-plugin/plugin.json +13 -0
  25. package/plugins/cobol-modernization/.claude-plugin/plugin.json +19 -0
  26. package/plugins/cobol-modernization/.codex-plugin/plugin.json +19 -0
  27. package/plugins/issue-duplicate-checker/.claude-plugin/plugin.json +13 -0
  28. package/plugins/issue-duplicate-checker/.codex-plugin/plugin.json +13 -0
  29. package/plugins/magic-test/.claude-plugin/plugin.json +13 -0
  30. package/plugins/magic-test/.codex-plugin/plugin.json +13 -0
  31. package/plugins/migration-scoring/.claude-plugin/plugin.json +19 -0
  32. package/plugins/migration-scoring/.codex-plugin/plugin.json +19 -0
  33. package/plugins/onboarding/.claude-plugin/plugin.json +20 -0
  34. package/plugins/onboarding/.codex-plugin/plugin.json +20 -0
  35. package/plugins/onboarding/skills/agent-readiness-report/references/criteria.md +144 -0
  36. package/plugins/onboarding/skills/improve-agent-readiness/references/criteria.md +144 -0
  37. package/plugins/openhands/.claude-plugin/plugin.json +13 -0
  38. package/plugins/openhands/.codex-plugin/plugin.json +13 -0
  39. package/plugins/openhands/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  40. package/plugins/openhands/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  41. package/plugins/openhands/skills/openhands-api/.plugin/plugin.json +22 -0
  42. package/plugins/openhands/skills/openhands-api/README.md +52 -0
  43. package/plugins/openhands/skills/openhands-api/SKILL.md +469 -0
  44. package/plugins/openhands/skills/openhands-api/references/README.md +33 -0
  45. package/plugins/openhands/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
  46. package/plugins/openhands/skills/openhands-api/references/example_prompt.md +12 -0
  47. package/plugins/openhands/skills/openhands-api/references/windows.md +44 -0
  48. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +606 -0
  49. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.ts +252 -0
  50. package/plugins/openhands/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  51. package/plugins/openhands/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  52. package/plugins/openhands/skills/openhands-automation/.plugin/plugin.json +19 -0
  53. package/plugins/openhands/skills/openhands-automation/README.md +89 -0
  54. package/plugins/openhands/skills/openhands-automation/SKILL.md +899 -0
  55. package/plugins/openhands/skills/openhands-automation/commands/automation-create.md +8 -0
  56. package/plugins/openhands/skills/openhands-automation/references/ab-testing.md +185 -0
  57. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +832 -0
  58. package/plugins/openhands/skills/openhands-automation/references/security.md +84 -0
  59. package/plugins/openhands/skills/openhands-automation/references/windows.md +57 -0
  60. package/plugins/openhands/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  61. package/plugins/openhands/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  62. package/plugins/openhands/skills/openhands-sdk/.plugin/plugin.json +20 -0
  63. package/plugins/openhands/skills/openhands-sdk/README.md +22 -0
  64. package/plugins/openhands/skills/openhands-sdk/SKILL.md +245 -0
  65. package/plugins/openhands/skills/openhands-sdk/commands/sdk.md +8 -0
  66. package/plugins/pr-review/.claude-plugin/plugin.json +13 -0
  67. package/plugins/pr-review/.codex-plugin/plugin.json +13 -0
  68. package/plugins/pr-review/skills/code-review/.claude-plugin/plugin.json +19 -0
  69. package/plugins/pr-review/skills/code-review/.codex-plugin/plugin.json +19 -0
  70. package/plugins/pr-review/skills/code-review/.plugin/plugin.json +19 -0
  71. package/plugins/pr-review/skills/code-review/README.md +18 -0
  72. package/plugins/pr-review/skills/code-review/SKILL.md +209 -0
  73. package/plugins/pr-review/skills/code-review/commands/codereview-roasted.md +8 -0
  74. package/plugins/pr-review/skills/code-review/commands/codereview.md +8 -0
  75. package/plugins/pr-review/skills/code-review/references/risk-evaluation.md +41 -0
  76. package/plugins/pr-review/skills/code-review/references/supply-chain-security.md +31 -0
  77. package/plugins/pr-review/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  78. package/plugins/pr-review/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  79. package/plugins/pr-review/skills/github-pr-review/.plugin/plugin.json +18 -0
  80. package/plugins/pr-review/skills/github-pr-review/README.md +145 -0
  81. package/plugins/pr-review/skills/github-pr-review/SKILL.md +187 -0
  82. package/plugins/pr-review/skills/github-pr-review/commands/github-pr-review.md +8 -0
  83. package/plugins/pr-review/skills/github-pr-review/references/windows.md +47 -0
  84. package/plugins/qa-changes/.claude-plugin/plugin.json +11 -0
  85. package/plugins/qa-changes/.codex-plugin/plugin.json +11 -0
  86. package/plugins/qa-changes/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  87. package/plugins/qa-changes/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  88. package/plugins/qa-changes/skills/github-pr-review/.plugin/plugin.json +18 -0
  89. package/plugins/qa-changes/skills/github-pr-review/README.md +145 -0
  90. package/plugins/qa-changes/skills/github-pr-review/SKILL.md +187 -0
  91. package/plugins/qa-changes/skills/github-pr-review/commands/github-pr-review.md +8 -0
  92. package/plugins/qa-changes/skills/github-pr-review/references/windows.md +47 -0
  93. package/plugins/qa-changes/skills/qa-changes/README.md +18 -0
  94. package/plugins/qa-changes/skills/qa-changes/SKILL.md +229 -0
  95. package/plugins/qa-changes/skills/qa-changes/commands/qa-changes.md +8 -0
  96. package/plugins/release-notes/.claude-plugin/plugin.json +19 -0
  97. package/plugins/release-notes/.codex-plugin/plugin.json +19 -0
  98. package/plugins/release-notes/skills/release-notes/README.md +24 -0
  99. package/plugins/release-notes/skills/release-notes/SKILL.md +19 -0
  100. package/plugins/release-notes/skills/release-notes/commands/release-notes.md +8 -0
  101. package/plugins/vulnerability-remediation/.claude-plugin/plugin.json +19 -0
  102. package/plugins/vulnerability-remediation/.codex-plugin/plugin.json +19 -0
  103. package/plugins/vulnerability-remediation/skills/github/.claude-plugin/plugin.json +19 -0
  104. package/plugins/vulnerability-remediation/skills/github/.codex-plugin/plugin.json +19 -0
  105. package/plugins/vulnerability-remediation/skills/github/.plugin/plugin.json +19 -0
  106. package/plugins/vulnerability-remediation/skills/github/README.md +42 -0
  107. package/plugins/vulnerability-remediation/skills/github/SKILL.md +107 -0
  108. package/plugins/vulnerability-remediation/skills/github/references/windows.md +48 -0
  109. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +143 -0
  110. package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +230 -0
  111. package/plugins/vulnerability-remediation/skills/security/.claude-plugin/plugin.json +18 -0
  112. package/plugins/vulnerability-remediation/skills/security/.codex-plugin/plugin.json +18 -0
  113. package/plugins/vulnerability-remediation/skills/security/.plugin/plugin.json +18 -0
  114. package/plugins/vulnerability-remediation/skills/security/README.md +38 -0
  115. package/plugins/vulnerability-remediation/skills/security/SKILL.md +33 -0
  116. package/pyproject.toml +2 -2
  117. package/python/openhands_extensions/_version.py +1 -1
  118. package/skills/add-javadoc/.claude-plugin/plugin.json +18 -0
  119. package/skills/add-javadoc/.codex-plugin/plugin.json +18 -0
  120. package/skills/add-skill/.claude-plugin/plugin.json +18 -0
  121. package/skills/add-skill/.codex-plugin/plugin.json +18 -0
  122. package/skills/agent-canvas-environment/.claude-plugin/plugin.json +19 -0
  123. package/skills/agent-canvas-environment/.codex-plugin/plugin.json +19 -0
  124. package/skills/agent-creator/.claude-plugin/plugin.json +20 -0
  125. package/skills/agent-creator/.codex-plugin/plugin.json +20 -0
  126. package/skills/agent-memory/.claude-plugin/plugin.json +18 -0
  127. package/skills/agent-memory/.codex-plugin/plugin.json +18 -0
  128. package/skills/agent-sdk-builder/.claude-plugin/plugin.json +18 -0
  129. package/skills/agent-sdk-builder/.codex-plugin/plugin.json +18 -0
  130. package/skills/azure-devops/.claude-plugin/plugin.json +18 -0
  131. package/skills/azure-devops/.codex-plugin/plugin.json +18 -0
  132. package/skills/bitbucket/.claude-plugin/plugin.json +17 -0
  133. package/skills/bitbucket/.codex-plugin/plugin.json +17 -0
  134. package/skills/bitbucket-cloud/.claude-plugin/plugin.json +18 -0
  135. package/skills/bitbucket-cloud/.codex-plugin/plugin.json +18 -0
  136. package/skills/bitbucket-data-center/.claude-plugin/plugin.json +19 -0
  137. package/skills/bitbucket-data-center/.codex-plugin/plugin.json +19 -0
  138. package/skills/code-review/.claude-plugin/plugin.json +19 -0
  139. package/skills/code-review/.codex-plugin/plugin.json +19 -0
  140. package/skills/code-simplifier/.claude-plugin/plugin.json +21 -0
  141. package/skills/code-simplifier/.codex-plugin/plugin.json +21 -0
  142. package/skills/datadog/.claude-plugin/plugin.json +19 -0
  143. package/skills/datadog/.codex-plugin/plugin.json +19 -0
  144. package/skills/deno/.claude-plugin/plugin.json +18 -0
  145. package/skills/deno/.codex-plugin/plugin.json +18 -0
  146. package/skills/discord/.claude-plugin/plugin.json +18 -0
  147. package/skills/discord/.codex-plugin/plugin.json +18 -0
  148. package/skills/docker/.claude-plugin/plugin.json +17 -0
  149. package/skills/docker/.codex-plugin/plugin.json +17 -0
  150. package/skills/evidence-based-citations/.claude-plugin/plugin.json +20 -0
  151. package/skills/evidence-based-citations/.codex-plugin/plugin.json +20 -0
  152. package/skills/flarglebargle/.claude-plugin/plugin.json +16 -0
  153. package/skills/flarglebargle/.codex-plugin/plugin.json +16 -0
  154. package/skills/frontend-design/.claude-plugin/plugin.json +21 -0
  155. package/skills/frontend-design/.codex-plugin/plugin.json +21 -0
  156. package/skills/github/.claude-plugin/plugin.json +19 -0
  157. package/skills/github/.codex-plugin/plugin.json +19 -0
  158. package/skills/github/scripts/agent_conversation.py +143 -0
  159. package/skills/github/scripts/github_client.py +230 -0
  160. package/skills/github-actions/.claude-plugin/plugin.json +19 -0
  161. package/skills/github-actions/.codex-plugin/plugin.json +19 -0
  162. package/skills/github-agents-md-maintainer/.claude-plugin/plugin.json +19 -0
  163. package/skills/github-agents-md-maintainer/.codex-plugin/plugin.json +19 -0
  164. package/skills/github-agents-md-maintainer/SKILL.md +7 -1
  165. package/skills/github-agents-md-maintainer/scripts/github_client.py +230 -0
  166. package/skills/github-agents-md-maintainer/scripts/main.py +4 -40
  167. package/skills/github-delivery-watchdog/.claude-plugin/plugin.json +9 -0
  168. package/skills/github-delivery-watchdog/.codex-plugin/plugin.json +9 -0
  169. package/skills/github-delivery-watchdog/.plugin/plugin.json +9 -0
  170. package/skills/github-delivery-watchdog/README.md +10 -0
  171. package/skills/github-delivery-watchdog/SKILL.md +35 -0
  172. package/skills/github-delivery-watchdog/commands/github-delivery-watchdog.md +8 -0
  173. package/skills/github-delivery-watchdog/scripts/github_client.py +230 -0
  174. package/skills/github-delivery-watchdog/scripts/worker.py +96 -0
  175. package/skills/github-issue-to-pr/.claude-plugin/plugin.json +19 -0
  176. package/skills/github-issue-to-pr/.codex-plugin/plugin.json +19 -0
  177. package/skills/github-issue-to-pr/SKILL.md +15 -1
  178. package/skills/github-issue-to-pr/scripts/agent_conversation.py +143 -0
  179. package/skills/github-issue-to-pr/scripts/github_client.py +230 -0
  180. package/skills/github-issue-to-pr/scripts/main.py +19 -50
  181. package/skills/github-issue-to-pr/scripts/worker.py +172 -0
  182. package/skills/github-issue-triage/.claude-plugin/plugin.json +9 -0
  183. package/skills/github-issue-triage/.codex-plugin/plugin.json +9 -0
  184. package/skills/github-issue-triage/.plugin/plugin.json +9 -0
  185. package/skills/github-issue-triage/README.md +9 -0
  186. package/skills/github-issue-triage/SKILL.md +34 -0
  187. package/skills/github-issue-triage/commands/github-issue-triage.md +8 -0
  188. package/skills/github-issue-triage/scripts/agent_conversation.py +143 -0
  189. package/skills/github-issue-triage/scripts/github_client.py +230 -0
  190. package/skills/github-issue-triage/scripts/worker.py +106 -0
  191. package/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
  192. package/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
  193. package/skills/github-pr-reviewer/.claude-plugin/plugin.json +20 -0
  194. package/skills/github-pr-reviewer/.codex-plugin/plugin.json +20 -0
  195. package/skills/github-pr-reviewer/SKILL.md +15 -1
  196. package/skills/github-pr-reviewer/scripts/agent_conversation.py +143 -0
  197. package/skills/github-pr-reviewer/scripts/github_client.py +230 -0
  198. package/skills/github-pr-reviewer/scripts/main.py +23 -49
  199. package/skills/github-pr-reviewer/scripts/worker.py +94 -0
  200. package/skills/github-repo-monitor/.claude-plugin/plugin.json +22 -0
  201. package/skills/github-repo-monitor/.codex-plugin/plugin.json +22 -0
  202. package/skills/github-repo-monitor/SKILL.md +7 -1
  203. package/skills/github-repo-monitor/scripts/github_client.py +230 -0
  204. package/skills/github-repo-monitor/scripts/main.py +4 -46
  205. package/skills/gitlab/.claude-plugin/plugin.json +17 -0
  206. package/skills/gitlab/.codex-plugin/plugin.json +17 -0
  207. package/skills/gitlab-issue-to-mr/.claude-plugin/plugin.json +19 -0
  208. package/skills/gitlab-issue-to-mr/.codex-plugin/plugin.json +19 -0
  209. package/skills/gitlab-issue-to-mr/.plugin/plugin.json +19 -0
  210. package/skills/gitlab-issue-to-mr/README.md +92 -0
  211. package/skills/gitlab-issue-to-mr/SKILL.md +408 -0
  212. package/skills/gitlab-issue-to-mr/commands/issue-to-mr-setup.md +8 -0
  213. package/skills/gitlab-issue-to-mr/references/state-schema.md +140 -0
  214. package/skills/gitlab-issue-to-mr/scripts/main.py +1448 -0
  215. package/skills/incident-retrospective/.claude-plugin/plugin.json +21 -0
  216. package/skills/incident-retrospective/.codex-plugin/plugin.json +21 -0
  217. package/skills/index.js +52 -6
  218. package/skills/iterate/.claude-plugin/plugin.json +13 -0
  219. package/skills/iterate/.codex-plugin/plugin.json +13 -0
  220. package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +19 -0
  221. package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +19 -0
  222. package/skills/jupyter/.claude-plugin/plugin.json +18 -0
  223. package/skills/jupyter/.codex-plugin/plugin.json +18 -0
  224. package/skills/kubernetes/.claude-plugin/plugin.json +18 -0
  225. package/skills/kubernetes/.codex-plugin/plugin.json +18 -0
  226. package/skills/learn-from-code-review/.claude-plugin/plugin.json +19 -0
  227. package/skills/learn-from-code-review/.codex-plugin/plugin.json +19 -0
  228. package/skills/linear/.claude-plugin/plugin.json +19 -0
  229. package/skills/linear/.codex-plugin/plugin.json +19 -0
  230. package/skills/linear-triage/.claude-plugin/plugin.json +21 -0
  231. package/skills/linear-triage/.codex-plugin/plugin.json +21 -0
  232. package/skills/news-digest/.claude-plugin/plugin.json +19 -0
  233. package/skills/news-digest/.codex-plugin/plugin.json +19 -0
  234. package/skills/notion/.claude-plugin/plugin.json +17 -0
  235. package/skills/notion/.codex-plugin/plugin.json +17 -0
  236. package/skills/npm/.claude-plugin/plugin.json +17 -0
  237. package/skills/npm/.codex-plugin/plugin.json +17 -0
  238. package/skills/openhands-api/.claude-plugin/plugin.json +22 -0
  239. package/skills/openhands-api/.codex-plugin/plugin.json +22 -0
  240. package/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
  241. package/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
  242. package/skills/openhands-automation/README.md +1 -1
  243. package/skills/openhands-automation/SKILL.md +18 -8
  244. package/skills/openhands-enterprise-troubleshooting/.claude-plugin/plugin.json +20 -0
  245. package/skills/openhands-enterprise-troubleshooting/.codex-plugin/plugin.json +20 -0
  246. package/skills/openhands-enterprise-troubleshooting/.plugin/plugin.json +20 -0
  247. package/skills/openhands-enterprise-troubleshooting/README.md +79 -0
  248. package/skills/openhands-enterprise-troubleshooting/SKILL.md +339 -0
  249. package/skills/openhands-enterprise-troubleshooting/references/diagnostics.md +698 -0
  250. package/skills/openhands-enterprise-troubleshooting/references/support-bundle-analysis.md +570 -0
  251. package/skills/openhands-enterprise-troubleshooting/scripts/bundle_triage.py +532 -0
  252. package/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
  253. package/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
  254. package/skills/openhands-sdk/SKILL.md +2 -0
  255. package/skills/pdflatex/.claude-plugin/plugin.json +18 -0
  256. package/skills/pdflatex/.codex-plugin/plugin.json +18 -0
  257. package/skills/plain-english-content/.claude-plugin/plugin.json +15 -0
  258. package/skills/plain-english-content/.codex-plugin/plugin.json +15 -0
  259. package/skills/prd/.claude-plugin/plugin.json +19 -0
  260. package/skills/prd/.codex-plugin/plugin.json +19 -0
  261. package/skills/research-brief/.claude-plugin/plugin.json +20 -0
  262. package/skills/research-brief/.codex-plugin/plugin.json +20 -0
  263. package/skills/security/.claude-plugin/plugin.json +18 -0
  264. package/skills/security/.codex-plugin/plugin.json +18 -0
  265. package/skills/skill-creator/.claude-plugin/plugin.json +17 -0
  266. package/skills/skill-creator/.codex-plugin/plugin.json +17 -0
  267. package/skills/slack-channel-monitor/.claude-plugin/plugin.json +21 -0
  268. package/skills/slack-channel-monitor/.codex-plugin/plugin.json +21 -0
  269. package/skills/slack-standup-digest/.claude-plugin/plugin.json +21 -0
  270. package/skills/slack-standup-digest/.codex-plugin/plugin.json +21 -0
  271. package/skills/spark-version-upgrade/.claude-plugin/plugin.json +20 -0
  272. package/skills/spark-version-upgrade/.codex-plugin/plugin.json +20 -0
  273. package/skills/ssh/.claude-plugin/plugin.json +18 -0
  274. package/skills/ssh/.codex-plugin/plugin.json +18 -0
  275. package/skills/swift-linux/.claude-plugin/plugin.json +17 -0
  276. package/skills/swift-linux/.codex-plugin/plugin.json +17 -0
  277. package/skills/technical-writing/.claude-plugin/plugin.json +15 -0
  278. package/skills/technical-writing/.codex-plugin/plugin.json +15 -0
  279. package/skills/theme-factory/.claude-plugin/plugin.json +19 -0
  280. package/skills/theme-factory/.codex-plugin/plugin.json +19 -0
  281. package/skills/ticket-to-code-change/.claude-plugin/plugin.json +21 -0
  282. package/skills/ticket-to-code-change/.codex-plugin/plugin.json +21 -0
  283. package/skills/upstream-fork-sync/.claude-plugin/plugin.json +21 -0
  284. package/skills/upstream-fork-sync/.codex-plugin/plugin.json +21 -0
  285. package/skills/uv/.claude-plugin/plugin.json +18 -0
  286. package/skills/uv/.codex-plugin/plugin.json +18 -0
  287. package/skills/vercel/.claude-plugin/plugin.json +18 -0
  288. package/skills/vercel/.codex-plugin/plugin.json +18 -0
  289. package/tests/fixtures/automations/capabilities.json +6 -3
  290. package/tests/fixtures/automations/github-pr-reviewer.json +64 -43
  291. package/tests/github_automation_helpers.py +31 -0
  292. package/tests/test_agent_conversation_dispatch.py +180 -0
  293. package/tests/test_automation_setup.py +3 -3
  294. package/tests/test_github_automation_foundation.py +73 -0
  295. package/tests/test_github_developer_delivery.py +150 -0
  296. package/tests/test_github_reviewer_delivery.py +110 -0
  297. package/tests/test_github_skill_installation.py +63 -0
  298. package/tests/test_github_triage_delivery.py +90 -0
  299. package/tests/test_github_watchdog_delivery.py +233 -0
  300. package/tests/test_gitlab_issue_to_mr.py +823 -0
  301. package/.pr/custom-automation-event-trigger.png +0 -0
  302. package/.pr/custom-automation-plugin-fields.png +0 -0
  303. package/.pr/custom-automation-prompt-review.png +0 -0
  304. package/.pr/custom-automation-prompt-scheduled.png +0 -0
  305. package/.pr/custom-automation-upload-fields.png +0 -0
@@ -0,0 +1,172 @@
1
+ """Select ready GitHub work and delegate each subject to a sandboxed agent."""
2
+
3
+ import json
4
+ import re
5
+
6
+ import main as workflow
7
+ from agent_conversation import AgentConversationDispatcher
8
+ from github_client import GitHubRepository, run_repositories
9
+
10
+
11
+ class IssueToPR(GitHubRepository):
12
+ name = "github-issue-to-pr"
13
+
14
+ def _workspace_instructions(self, branch, revision):
15
+ token = self.token_name
16
+ if revision:
17
+ checkout = f"check out the existing remote branch `{branch}`"
18
+ else:
19
+ checkout = (
20
+ f"create and check out `{branch}` from the repository's base branch"
21
+ )
22
+ return (
23
+ "The workspace starts empty. Clone the repository into it with "
24
+ f"`GH_TOKEN=${{{token}}} gh repo clone {self.repository} .`, then {checkout}. "
25
+ "Keep the remote free of embedded credentials."
26
+ )
27
+
28
+ def _prompt(self, issue, branch, base_branch, base_sha, *, revision=None):
29
+ review_label = self.config.get("review_label", "openhands-review")
30
+ prompt = workflow._build_implementation_prompt(
31
+ self.repository,
32
+ issue,
33
+ {
34
+ "id": issue.get("updated_at", "?"),
35
+ "created_at": issue.get("updated_at", "?"),
36
+ },
37
+ branch,
38
+ base_branch,
39
+ base_sha,
40
+ workspace_instructions=self._workspace_instructions(branch, revision),
41
+ github_token_secret=self.token_name,
42
+ )
43
+ if revision:
44
+ prompt = (
45
+ f"Revise existing PR #{revision['number']} at exact head "
46
+ f"`{revision['head']['sha']}`. Read its current reviews, inline "
47
+ "comments, discussion, and failing checks directly from GitHub. "
48
+ "Address each current finding or explain with evidence why no code "
49
+ "change is warranted. Push revisions to the existing branch; do not "
50
+ "open another pull request. After responding, remove and reapply the "
51
+ f"`{review_label}` label so the exact new head is reviewed. If no code "
52
+ "change is needed, post that evidence on the PR before reapplying it.\n\n"
53
+ + prompt
54
+ )
55
+ else:
56
+ prompt += (
57
+ f"\n\nAfter opening the pull request, add the `{review_label}` label so "
58
+ "the independent reviewer checks its exact head."
59
+ )
60
+ return prompt
61
+
62
+ def _submit(
63
+ self, repository_id, issue, branch, base_branch, base_sha, *, revision=None
64
+ ):
65
+ revision_sha = revision["head"]["sha"] if revision else None
66
+ key = revision_sha or issue.get("updated_at") or str(issue["number"])
67
+ result = self.dispatcher.deliver(
68
+ subject=f"{repository_id}:issue:{issue['number']}",
69
+ delivery=f"{issue['number']}:{key}",
70
+ prompt=self._prompt(
71
+ issue,
72
+ branch,
73
+ base_branch,
74
+ revision_sha or base_sha,
75
+ revision=revision,
76
+ ),
77
+ )
78
+ print(
79
+ json.dumps(
80
+ {
81
+ "repository": self.repository,
82
+ "issue": issue["number"],
83
+ "pr": revision["number"] if revision else None,
84
+ "disposition": result["disposition"],
85
+ "conversation_id": result["conversation_id"],
86
+ }
87
+ ),
88
+ flush=True,
89
+ )
90
+
91
+ def _try_submit(
92
+ self, repository_id, issue, branch, base_branch, base_sha, *, revision=None
93
+ ):
94
+ try:
95
+ self._submit(
96
+ repository_id,
97
+ issue,
98
+ branch,
99
+ base_branch,
100
+ base_sha,
101
+ revision=revision,
102
+ )
103
+ except Exception as exc: # noqa: BLE001 - one issue must not block the scan
104
+ print(
105
+ f"Failed to submit {self.repository} issue "
106
+ f"#{issue.get('number', '?')}: {exc}",
107
+ flush=True,
108
+ )
109
+
110
+ def run(self):
111
+ trigger_label = self.config.get("trigger_label", workflow.TRIGGER_LABEL)
112
+ branch_prefix = self.config.get("branch_prefix", workflow.BRANCH_PREFIX)
113
+ repository = self.gh("GET", "")
114
+ repository_id = repository["id"]
115
+ base_branch = self.config.get("base_branch") or repository["default_branch"]
116
+ base_sha = self.gh("GET", f"/git/ref/heads/{base_branch}")["object"]["sha"]
117
+ issues = {issue["number"]: issue for issue in self.open_issues()}
118
+
119
+ open_prs = self.gh_pages("/pulls?state=open")
120
+ issue_prs = {}
121
+ for pr in open_prs:
122
+ match = re.fullmatch(
123
+ re.escape(branch_prefix) + r"-(\d+)", pr["head"]["ref"]
124
+ )
125
+ if match:
126
+ issue_prs[int(match[1])] = pr
127
+
128
+ for issue_number, pr in sorted(issue_prs.items()):
129
+ issue = issues.get(issue_number)
130
+ if issue is None:
131
+ continue
132
+ if self.statuses(pr["head"]["sha"]).get("software-factory/review") not in {
133
+ "failure",
134
+ "error",
135
+ }:
136
+ continue
137
+ self._try_submit(
138
+ repository_id,
139
+ issue,
140
+ pr["head"]["ref"],
141
+ (pr.get("base") or {}).get("ref") or base_branch,
142
+ base_sha,
143
+ revision=pr,
144
+ )
145
+
146
+ ready = [
147
+ issue
148
+ for issue in issues.values()
149
+ if issue["number"] not in issue_prs
150
+ and trigger_label in {label["name"] for label in issue.get("labels", [])}
151
+ and self.dependencies_complete(issue)
152
+ ]
153
+ for issue in sorted(
154
+ ready,
155
+ key=lambda item: (
156
+ "priority:high"
157
+ not in {label["name"] for label in item.get("labels", [])},
158
+ item["number"],
159
+ ),
160
+ ):
161
+ self._try_submit(
162
+ repository_id,
163
+ issue,
164
+ f"{branch_prefix}-{issue['number']}",
165
+ base_branch,
166
+ base_sha,
167
+ )
168
+
169
+
170
+ if __name__ == "__main__":
171
+ with AgentConversationDispatcher() as dispatcher:
172
+ run_repositories(IssueToPR, dispatcher=dispatcher)
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-issue-triage",
3
+ "version": "1.0.0",
4
+ "description": "Prioritize open issues and establish acceptance criteria before marking them ready for development.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-issue-triage",
3
+ "version": "1.0.0",
4
+ "description": "Prioritize open issues and establish acceptance criteria before marking them ready for development.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "github-issue-triage",
3
+ "version": "1.0.0",
4
+ "description": "Prioritize open issues and establish acceptance criteria before marking them ready for development.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ },
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,9 @@
1
+ # GitHub issue triage
2
+
3
+ Prioritize open issues and establish acceptance criteria before marking them ready for development.
4
+
5
+ Schedule this automation independently and select its agent profile. The profile
6
+ chooses the model, tools, and GitHub credential; the PAT needs Issues write access
7
+ only. Explicit issue dependencies must be completed before development starts.
8
+
9
+ See [SKILL.md](SKILL.md) for configuration and the catalog bundle for its files.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: github-issue-triage
3
+ description: Prioritize open issues and establish acceptance criteria before marking them ready for development.
4
+ triggers:
5
+ - /github-issue-triage
6
+ ---
7
+
8
+ # GitHub issue triage
9
+
10
+ Prioritize open issues and establish acceptance criteria before marking them ready for development.
11
+
12
+ Create this automation separately from implementation and review. Use a
13
+ fine-grained GitHub PAT limited to the selected repositories with Issues: read and
14
+ write. Never put the token value in the automation definition or prompt.
15
+
16
+ Package `worker.py` with the shared `github_client.py` and
17
+ `agent_conversation.py` helpers. Supply `config.json` with `repos` and the saved
18
+ GitHub secret name; the entrypoint is `python3 worker.py`. Include that secret in
19
+ the selected profile so the delegated agent can use GitHub. Naming it in the
20
+ automation does not grant it to the agent.
21
+
22
+ The scanner is an ordinary Automation host command. It submits selected issues
23
+ through the shared KV-backed conversation dispatcher, which creates or resumes
24
+ one stable conversation per issue through the Software Agent SDK. Only that
25
+ agent workspace is local or Docker. Automation owns scheduling, cancellation,
26
+ and cleanup.
27
+
28
+ Honor `Depends on: #12, #13` lines. A dependency must be closed as completed.
29
+ Post readable acceptance criteria and rationale. Add `ready-for-dev` only when
30
+ criteria are actionable; preserve existing issue labels. Unclear issues stay open
31
+ for clarification. Do not implement code or accept pull requests.
32
+
33
+ Each scheduled run submits every changed eligible issue. A failure on one issue is
34
+ reported and does not prevent the remaining issues from being submitted.
@@ -0,0 +1,8 @@
1
+ ---
2
+ # auto-generated by sync_extensions.py
3
+ description: Prioritize open issues and establish acceptance criteria before marking them ready for development.
4
+ ---
5
+
6
+ Read and follow the complete instructions in the SKILL.md file located in this skill's directory.
7
+
8
+ $ARGUMENTS
@@ -0,0 +1,143 @@
1
+ """Idempotently deliver automation work to profile-backed conversations."""
2
+
3
+ import json
4
+ import os
5
+ from urllib.error import HTTPError
6
+ from urllib.request import Request, urlopen
7
+ from uuid import NAMESPACE_URL, UUID, uuid5
8
+
9
+ import httpx
10
+ from openhands.sdk import RemoteConversation
11
+ from openhands.sdk.conversation.request import (
12
+ SendMessageRequest,
13
+ StartConversationRequest,
14
+ )
15
+ from openhands.sdk.conversation.state import ConversationExecutionStatus
16
+ from openhands.sdk.llm.message import TextContent
17
+ from openhands.sdk.workspace import LocalWorkspace, RemoteWorkspace
18
+
19
+ _CONVERSATION_KEY_PREFIX = "agent-conversation-"
20
+
21
+
22
+ def _register_tools() -> None:
23
+ """Register tool models needed to deserialize an attached agent."""
24
+ from openhands.tools import register_default_tools
25
+
26
+ register_default_tools()
27
+
28
+
29
+ def _kv_request(key: str, method: str, value: dict | None = None) -> dict | None:
30
+ base_url = os.environ.get("AUTOMATION_API_URL", "").rstrip("/")
31
+ token = os.environ.get("AUTOMATION_KV_TOKEN", "")
32
+ if not base_url or not token:
33
+ raise RuntimeError("Automation KV is required for agent conversation dispatch")
34
+ request = Request(
35
+ f"{base_url}/v1/kv/{key}",
36
+ data=json.dumps(value).encode() if value is not None else None,
37
+ headers={
38
+ "Authorization": f"Bearer {token}",
39
+ "Content-Type": "application/json",
40
+ },
41
+ method=method,
42
+ )
43
+ try:
44
+ with urlopen(request, timeout=90) as response:
45
+ body = json.load(response)
46
+ except HTTPError as exc:
47
+ if method == "GET" and exc.code == 404:
48
+ return None
49
+ raise
50
+ return body.get("value") if method == "GET" else body
51
+
52
+
53
+ class AgentConversationDispatcher:
54
+ """Deliver one revision at a time to a stable conversation for each subject."""
55
+
56
+ def __init__(self) -> None:
57
+ self.agent_url = os.environ["AGENT_SERVER_URL"]
58
+ self.api_key = os.environ["SESSION_API_KEY"]
59
+ self.profile_id = UUID(os.environ["AUTOMATION_AGENT_PROFILE_ID"])
60
+ payload = json.loads(os.environ["AUTOMATION_EVENT_PAYLOAD"])
61
+ self.automation_id = str(payload["automation_id"])
62
+ self._workspace: RemoteWorkspace | None = None
63
+
64
+ def __enter__(self):
65
+ _register_tools()
66
+ self._workspace = RemoteWorkspace(
67
+ host=self.agent_url,
68
+ api_key=self.api_key,
69
+ working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
70
+ )
71
+ self._workspace.__enter__()
72
+ return self
73
+
74
+ def __exit__(self, *args):
75
+ assert self._workspace is not None
76
+ return self._workspace.__exit__(*args)
77
+
78
+ def deliver(self, subject: str, delivery: str, prompt: str) -> dict[str, str]:
79
+ conversation_id = uuid5(NAMESPACE_URL, f"{self.automation_id}:{subject}")
80
+ state_key = f"{_CONVERSATION_KEY_PREFIX}{conversation_id}"
81
+ record = _kv_request(state_key, "GET") or {}
82
+ if self._workspace is None:
83
+ raise RuntimeError("AgentConversationDispatcher must be used as a context")
84
+
85
+ same_delivery = record.get("delivery") == delivery
86
+
87
+ try:
88
+ conversation = RemoteConversation.attach(
89
+ self._workspace, conversation_id, visualizer=None
90
+ )
91
+ disposition = "resumed"
92
+ except httpx.HTTPStatusError as exc:
93
+ if exc.response.status_code != 404:
94
+ raise
95
+ conversation = RemoteConversation.create(
96
+ self._workspace,
97
+ StartConversationRequest(
98
+ workspace=LocalWorkspace(working_dir="/workspace"),
99
+ conversation_id=conversation_id,
100
+ agent_profile_id=self.profile_id,
101
+ initial_message=SendMessageRequest(
102
+ content=[TextContent(text=prompt)], run=True
103
+ ),
104
+ ),
105
+ visualizer=None,
106
+ )
107
+ disposition = "created"
108
+ try:
109
+ if disposition == "resumed":
110
+ if same_delivery:
111
+ if conversation.state.execution_status in (
112
+ ConversationExecutionStatus.IDLE,
113
+ ConversationExecutionStatus.RUNNING,
114
+ ConversationExecutionStatus.PAUSED,
115
+ ):
116
+ conversation.run(blocking=False)
117
+ else:
118
+ disposition = "deduplicated"
119
+ else:
120
+ conversation.send_message(prompt)
121
+ conversation.run(blocking=False)
122
+ finally:
123
+ conversation.close()
124
+
125
+ if disposition == "deduplicated":
126
+ return {
127
+ "disposition": disposition,
128
+ "conversation_id": str(conversation_id),
129
+ }
130
+
131
+ _kv_request(
132
+ state_key,
133
+ "PUT",
134
+ {
135
+ "subject": subject,
136
+ "conversation_id": str(conversation_id),
137
+ "delivery": delivery,
138
+ },
139
+ )
140
+ return {
141
+ "disposition": disposition,
142
+ "conversation_id": str(conversation_id),
143
+ }
@@ -0,0 +1,230 @@
1
+ """Shared GitHub transport and repository operations for GitHub automations."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import re
7
+ import subprocess
8
+ from functools import cached_property
9
+ from pathlib import Path
10
+ from urllib.error import HTTPError
11
+ from urllib.parse import parse_qsl, urlencode, urlsplit
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ def _load_secret(name: str) -> str:
16
+ """Read one named secret from the environment or configured Agent Server."""
17
+ value = os.environ.get(name)
18
+ if value:
19
+ return value
20
+
21
+ from openhands.sdk.workspace import RemoteWorkspace
22
+
23
+ workspace = RemoteWorkspace(
24
+ host=os.environ["AGENT_SERVER_URL"],
25
+ api_key=os.environ["SESSION_API_KEY"],
26
+ working_dir=os.environ.get("WORKSPACE_BASE", "/workspace"),
27
+ )
28
+ try:
29
+ secret = workspace.get_secrets([name]).get(name)
30
+ value = secret.get_value() if secret else None
31
+ finally:
32
+ workspace.reset_client()
33
+ if not value:
34
+ raise ValueError(f"The GitHub credential {name} is unavailable")
35
+ return value
36
+
37
+
38
+ def github_request(
39
+ token: str,
40
+ method: str,
41
+ path: str,
42
+ params: dict | None = None,
43
+ body: dict | None = None,
44
+ accept: str = "application/vnd.github+json",
45
+ ) -> tuple:
46
+ url = f"https://api.github.com{path}"
47
+ if params:
48
+ url = f"{url}?{urlencode(params)}"
49
+ headers = {
50
+ "Authorization": f"Bearer {token}",
51
+ "Accept": accept,
52
+ "X-GitHub-Api-Version": "2022-11-28",
53
+ "Content-Type": "application/json",
54
+ }
55
+ data = json.dumps(body).encode() if body is not None else None
56
+ req = Request(url, data=data, headers=headers, method=method)
57
+ with urlopen(req, timeout=90) as r:
58
+ raw = r.read()
59
+ return (json.loads(raw) if raw.strip() else {}), dict(r.headers)
60
+
61
+
62
+ def github_paginate(token: str, path: str, params: dict | None = None) -> list:
63
+ results = []
64
+ base_params = dict(params or {})
65
+ base_params.setdefault("per_page", 100)
66
+ for page in range(1, 101):
67
+ base_params["page"] = page
68
+ data, _ = github_request(token, "GET", path, params=base_params)
69
+ if not isinstance(data, list):
70
+ raise TypeError("Expected a paginated GitHub list")
71
+ results.extend(data)
72
+ if len(data) < int(base_params["per_page"]):
73
+ return results
74
+ raise RuntimeError("GitHub pagination exceeded limit")
75
+
76
+
77
+ class GitHubRepository:
78
+ name = "GitHub automation"
79
+
80
+ def __init__(
81
+ self,
82
+ config_path=Path("config.json"),
83
+ *,
84
+ github_token_secret,
85
+ repository=None,
86
+ conversation=None,
87
+ dispatcher=None,
88
+ ):
89
+ self.config = json.loads(Path(config_path).read_text())
90
+ self.repository = repository or self.config["repository"]
91
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", self.repository):
92
+ raise ValueError("repository must be owner/repo")
93
+ if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", github_token_secret):
94
+ raise ValueError(
95
+ "Expected the environment variable containing the GitHub token"
96
+ )
97
+ self.token_name = github_token_secret
98
+ self.token = _load_secret(github_token_secret)
99
+ self.conversation = conversation
100
+ self.conversation_id = str(conversation.id) if conversation else None
101
+ self.dispatcher = dispatcher
102
+ self.workspace = Path(os.environ["WORKSPACE_BASE"])
103
+ self.project = self.workspace
104
+ self.evidence = self.workspace / "evidence"
105
+ self.evidence.mkdir(exist_ok=True)
106
+ self._completed_dependencies = {}
107
+
108
+ @cached_property
109
+ def base_branch(self):
110
+ return self.config.get("base_branch") or self.gh("GET", "")["default_branch"]
111
+
112
+ @property
113
+ def github_instructions(self):
114
+ return (
115
+ f"Use `GH_TOKEN=${self.token_name} gh api` for GitHub requests. "
116
+ "Never print the credential value. "
117
+ f"Only {self.repository} is in scope. Work in {self.project}. "
118
+ "Do not modify the automation bundle or its configuration."
119
+ )
120
+
121
+ def gh(self, method, path, body=None):
122
+ return github_request(
123
+ self.token, method, f"/repos/{self.repository}" + path, body=body
124
+ )[0]
125
+
126
+ def shell(self, args, cwd=None, timeout=300):
127
+ result = subprocess.run(
128
+ args,
129
+ cwd=cwd or self.project,
130
+ text=True,
131
+ stdout=subprocess.PIPE,
132
+ stderr=subprocess.STDOUT,
133
+ timeout=timeout,
134
+ check=False,
135
+ )
136
+ if result.returncode:
137
+ raise RuntimeError(
138
+ f"{args[0]} failed: {result.stdout[-4000:].replace(self.token, '[REDACTED]')}"
139
+ )
140
+ return result.stdout.strip()
141
+
142
+ def comment(self, number, text):
143
+ return self.gh(
144
+ "POST",
145
+ f"/issues/{number}/comments",
146
+ {
147
+ "body": text
148
+ + f"\n\nFactory role: `{self.name}`; conversation: `{self.conversation_id}`."
149
+ + "\n\n_This comment was posted by an AI agent (OpenHands)._"
150
+ },
151
+ )
152
+
153
+ def open_issues(self):
154
+ return [
155
+ i for i in self.gh_pages("/issues?state=open") if "pull_request" not in i
156
+ ]
157
+
158
+ def statuses(self, sha):
159
+ result = {}
160
+ for item in self.gh_pages(f"/commits/{sha}/statuses"):
161
+ result.setdefault(item["context"], item["state"])
162
+ return result
163
+
164
+ def completed_dependency(self, number):
165
+ if number in self._completed_dependencies:
166
+ return self._completed_dependencies[number]
167
+ try:
168
+ dependency = self.gh("GET", f"/issues/{number}")
169
+ except HTTPError as exc:
170
+ if exc.code == 404:
171
+ return False
172
+ raise
173
+ completed = (
174
+ dependency["state"] == "closed"
175
+ and dependency.get("state_reason") == "completed"
176
+ )
177
+ self._completed_dependencies[number] = completed
178
+ return completed
179
+
180
+ def dependencies_complete(self, issue):
181
+ """Honor explicit Depends on lines; unknown/incomplete issues remain blocked."""
182
+ for line in re.findall(
183
+ "^Depends on:\\s*(.+)$",
184
+ issue.get("body") or "",
185
+ re.MULTILINE | re.IGNORECASE,
186
+ ):
187
+ for number in re.findall("#(\\d+)", line):
188
+ if not self.completed_dependency(number):
189
+ return False
190
+ return True
191
+
192
+ def gh_pages(self, endpoint):
193
+ split = urlsplit(endpoint)
194
+ return github_paginate(
195
+ self.token,
196
+ f"/repos/{self.repository}" + split.path,
197
+ params=dict(parse_qsl(split.query)),
198
+ )
199
+
200
+
201
+ def run_repositories(automation_type, conversation=None, dispatcher=None):
202
+ parser = argparse.ArgumentParser(description=automation_type.__doc__)
203
+ parser.add_argument("--github-token-secret")
204
+ args = parser.parse_args()
205
+ config = json.loads(Path("config.json").read_text())
206
+ token_name = args.github_token_secret or config.get(
207
+ "github_token_secret", "GITHUB_PERSONAL_ACCESS_TOKEN"
208
+ )
209
+ repositories = config.get("repos") or [config["repository"]]
210
+ failures = []
211
+ for repository in repositories:
212
+ options = dict(
213
+ github_token_secret=token_name,
214
+ repository=repository,
215
+ conversation=conversation,
216
+ )
217
+ if dispatcher is not None:
218
+ options["dispatcher"] = dispatcher
219
+ automation = automation_type(**options)
220
+ try:
221
+ automation.run()
222
+ except Exception as exc: # noqa: BLE001 - one repository must not block others
223
+ failures.append(repository)
224
+ print(
225
+ json.dumps({"repository": repository, "error": type(exc).__name__}),
226
+ flush=True,
227
+ )
228
+ if failures:
229
+ raise RuntimeError("Automation failed for: " + ", ".join(failures))
230
+ return str(conversation.id) if conversation else None