@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.
- package/.claude-plugin/marketplace.json +881 -0
- package/.codex-plugin/marketplace.json +881 -0
- package/.github/workflows/check-extensions.yml +4 -0
- package/.github/workflows/npm-publish.yml +9 -3
- package/.github/workflows/pr-artifacts.yml +294 -0
- package/.github/workflows/pr-description-check.yml +4 -2
- package/.plugin/marketplace.json +881 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +27 -3
- package/README.md +6 -2
- package/automations/bundle-index.js +22 -3
- package/automations/catalog/github-agents-md-maintainer/manifest.json +2 -1
- package/automations/catalog/github-delivery-watchdog/manifest.json +79 -0
- package/automations/catalog/github-issue-to-pr/manifest.json +24 -15
- package/automations/catalog/github-issue-triage/manifest.json +73 -0
- package/automations/catalog/github-pr-reviewer/manifest.json +23 -15
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +124 -0
- package/automations/catalog-index.js +17 -11
- package/integrations/catalog/reportportal.json +76 -0
- package/integrations/catalog-index.js +6 -4
- package/marketplaces/openhands-extensions.json +42 -0
- package/package.json +1 -1
- package/plugins/city-weather/.claude-plugin/plugin.json +13 -0
- package/plugins/city-weather/.codex-plugin/plugin.json +13 -0
- package/plugins/cobol-modernization/.claude-plugin/plugin.json +19 -0
- package/plugins/cobol-modernization/.codex-plugin/plugin.json +19 -0
- package/plugins/issue-duplicate-checker/.claude-plugin/plugin.json +13 -0
- package/plugins/issue-duplicate-checker/.codex-plugin/plugin.json +13 -0
- package/plugins/magic-test/.claude-plugin/plugin.json +13 -0
- package/plugins/magic-test/.codex-plugin/plugin.json +13 -0
- package/plugins/migration-scoring/.claude-plugin/plugin.json +19 -0
- package/plugins/migration-scoring/.codex-plugin/plugin.json +19 -0
- package/plugins/onboarding/.claude-plugin/plugin.json +20 -0
- package/plugins/onboarding/.codex-plugin/plugin.json +20 -0
- package/plugins/onboarding/skills/agent-readiness-report/references/criteria.md +144 -0
- package/plugins/onboarding/skills/improve-agent-readiness/references/criteria.md +144 -0
- package/plugins/openhands/.claude-plugin/plugin.json +13 -0
- package/plugins/openhands/.codex-plugin/plugin.json +13 -0
- package/plugins/openhands/skills/openhands-api/.claude-plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/.codex-plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/.plugin/plugin.json +22 -0
- package/plugins/openhands/skills/openhands-api/README.md +52 -0
- package/plugins/openhands/skills/openhands-api/SKILL.md +469 -0
- package/plugins/openhands/skills/openhands-api/references/README.md +33 -0
- package/plugins/openhands/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
- package/plugins/openhands/skills/openhands-api/references/example_prompt.md +12 -0
- package/plugins/openhands/skills/openhands-api/references/windows.md +44 -0
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +606 -0
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.ts +252 -0
- package/plugins/openhands/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/.plugin/plugin.json +19 -0
- package/plugins/openhands/skills/openhands-automation/README.md +89 -0
- package/plugins/openhands/skills/openhands-automation/SKILL.md +899 -0
- package/plugins/openhands/skills/openhands-automation/commands/automation-create.md +8 -0
- package/plugins/openhands/skills/openhands-automation/references/ab-testing.md +185 -0
- package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +832 -0
- package/plugins/openhands/skills/openhands-automation/references/security.md +84 -0
- package/plugins/openhands/skills/openhands-automation/references/windows.md +57 -0
- package/plugins/openhands/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/.plugin/plugin.json +20 -0
- package/plugins/openhands/skills/openhands-sdk/README.md +22 -0
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +245 -0
- package/plugins/openhands/skills/openhands-sdk/commands/sdk.md +8 -0
- package/plugins/pr-review/.claude-plugin/plugin.json +13 -0
- package/plugins/pr-review/.codex-plugin/plugin.json +13 -0
- package/plugins/pr-review/skills/code-review/.claude-plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/.codex-plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/.plugin/plugin.json +19 -0
- package/plugins/pr-review/skills/code-review/README.md +18 -0
- package/plugins/pr-review/skills/code-review/SKILL.md +209 -0
- package/plugins/pr-review/skills/code-review/commands/codereview-roasted.md +8 -0
- package/plugins/pr-review/skills/code-review/commands/codereview.md +8 -0
- package/plugins/pr-review/skills/code-review/references/risk-evaluation.md +41 -0
- package/plugins/pr-review/skills/code-review/references/supply-chain-security.md +31 -0
- package/plugins/pr-review/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/plugins/pr-review/skills/github-pr-review/README.md +145 -0
- package/plugins/pr-review/skills/github-pr-review/SKILL.md +187 -0
- package/plugins/pr-review/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/plugins/pr-review/skills/github-pr-review/references/windows.md +47 -0
- package/plugins/qa-changes/.claude-plugin/plugin.json +11 -0
- package/plugins/qa-changes/.codex-plugin/plugin.json +11 -0
- package/plugins/qa-changes/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/plugins/qa-changes/skills/github-pr-review/README.md +145 -0
- package/plugins/qa-changes/skills/github-pr-review/SKILL.md +187 -0
- package/plugins/qa-changes/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/plugins/qa-changes/skills/github-pr-review/references/windows.md +47 -0
- package/plugins/qa-changes/skills/qa-changes/README.md +18 -0
- package/plugins/qa-changes/skills/qa-changes/SKILL.md +229 -0
- package/plugins/qa-changes/skills/qa-changes/commands/qa-changes.md +8 -0
- package/plugins/release-notes/.claude-plugin/plugin.json +19 -0
- package/plugins/release-notes/.codex-plugin/plugin.json +19 -0
- package/plugins/release-notes/skills/release-notes/README.md +24 -0
- package/plugins/release-notes/skills/release-notes/SKILL.md +19 -0
- package/plugins/release-notes/skills/release-notes/commands/release-notes.md +8 -0
- package/plugins/vulnerability-remediation/.claude-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/.codex-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.claude-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.codex-plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/.plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/skills/github/README.md +42 -0
- package/plugins/vulnerability-remediation/skills/github/SKILL.md +107 -0
- package/plugins/vulnerability-remediation/skills/github/references/windows.md +48 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +143 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +230 -0
- package/plugins/vulnerability-remediation/skills/security/.claude-plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/.codex-plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/.plugin/plugin.json +18 -0
- package/plugins/vulnerability-remediation/skills/security/README.md +38 -0
- package/plugins/vulnerability-remediation/skills/security/SKILL.md +33 -0
- package/pyproject.toml +2 -2
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/add-javadoc/.claude-plugin/plugin.json +18 -0
- package/skills/add-javadoc/.codex-plugin/plugin.json +18 -0
- package/skills/add-skill/.claude-plugin/plugin.json +18 -0
- package/skills/add-skill/.codex-plugin/plugin.json +18 -0
- package/skills/agent-canvas-environment/.claude-plugin/plugin.json +19 -0
- package/skills/agent-canvas-environment/.codex-plugin/plugin.json +19 -0
- package/skills/agent-creator/.claude-plugin/plugin.json +20 -0
- package/skills/agent-creator/.codex-plugin/plugin.json +20 -0
- package/skills/agent-memory/.claude-plugin/plugin.json +18 -0
- package/skills/agent-memory/.codex-plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/.claude-plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/.codex-plugin/plugin.json +18 -0
- package/skills/azure-devops/.claude-plugin/plugin.json +18 -0
- package/skills/azure-devops/.codex-plugin/plugin.json +18 -0
- package/skills/bitbucket/.claude-plugin/plugin.json +17 -0
- package/skills/bitbucket/.codex-plugin/plugin.json +17 -0
- package/skills/bitbucket-cloud/.claude-plugin/plugin.json +18 -0
- package/skills/bitbucket-cloud/.codex-plugin/plugin.json +18 -0
- package/skills/bitbucket-data-center/.claude-plugin/plugin.json +19 -0
- package/skills/bitbucket-data-center/.codex-plugin/plugin.json +19 -0
- package/skills/code-review/.claude-plugin/plugin.json +19 -0
- package/skills/code-review/.codex-plugin/plugin.json +19 -0
- package/skills/code-simplifier/.claude-plugin/plugin.json +21 -0
- package/skills/code-simplifier/.codex-plugin/plugin.json +21 -0
- package/skills/datadog/.claude-plugin/plugin.json +19 -0
- package/skills/datadog/.codex-plugin/plugin.json +19 -0
- package/skills/deno/.claude-plugin/plugin.json +18 -0
- package/skills/deno/.codex-plugin/plugin.json +18 -0
- package/skills/discord/.claude-plugin/plugin.json +18 -0
- package/skills/discord/.codex-plugin/plugin.json +18 -0
- package/skills/docker/.claude-plugin/plugin.json +17 -0
- package/skills/docker/.codex-plugin/plugin.json +17 -0
- package/skills/evidence-based-citations/.claude-plugin/plugin.json +20 -0
- package/skills/evidence-based-citations/.codex-plugin/plugin.json +20 -0
- package/skills/flarglebargle/.claude-plugin/plugin.json +16 -0
- package/skills/flarglebargle/.codex-plugin/plugin.json +16 -0
- package/skills/frontend-design/.claude-plugin/plugin.json +21 -0
- package/skills/frontend-design/.codex-plugin/plugin.json +21 -0
- package/skills/github/.claude-plugin/plugin.json +19 -0
- package/skills/github/.codex-plugin/plugin.json +19 -0
- package/skills/github/scripts/agent_conversation.py +143 -0
- package/skills/github/scripts/github_client.py +230 -0
- package/skills/github-actions/.claude-plugin/plugin.json +19 -0
- package/skills/github-actions/.codex-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/.claude-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/.codex-plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/SKILL.md +7 -1
- package/skills/github-agents-md-maintainer/scripts/github_client.py +230 -0
- package/skills/github-agents-md-maintainer/scripts/main.py +4 -40
- package/skills/github-delivery-watchdog/.claude-plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/.codex-plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/.plugin/plugin.json +9 -0
- package/skills/github-delivery-watchdog/README.md +10 -0
- package/skills/github-delivery-watchdog/SKILL.md +35 -0
- package/skills/github-delivery-watchdog/commands/github-delivery-watchdog.md +8 -0
- package/skills/github-delivery-watchdog/scripts/github_client.py +230 -0
- package/skills/github-delivery-watchdog/scripts/worker.py +96 -0
- package/skills/github-issue-to-pr/.claude-plugin/plugin.json +19 -0
- package/skills/github-issue-to-pr/.codex-plugin/plugin.json +19 -0
- package/skills/github-issue-to-pr/SKILL.md +15 -1
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +143 -0
- package/skills/github-issue-to-pr/scripts/github_client.py +230 -0
- package/skills/github-issue-to-pr/scripts/main.py +19 -50
- package/skills/github-issue-to-pr/scripts/worker.py +172 -0
- package/skills/github-issue-triage/.claude-plugin/plugin.json +9 -0
- package/skills/github-issue-triage/.codex-plugin/plugin.json +9 -0
- package/skills/github-issue-triage/.plugin/plugin.json +9 -0
- package/skills/github-issue-triage/README.md +9 -0
- package/skills/github-issue-triage/SKILL.md +34 -0
- package/skills/github-issue-triage/commands/github-issue-triage.md +8 -0
- package/skills/github-issue-triage/scripts/agent_conversation.py +143 -0
- package/skills/github-issue-triage/scripts/github_client.py +230 -0
- package/skills/github-issue-triage/scripts/worker.py +106 -0
- package/skills/github-pr-review/.claude-plugin/plugin.json +18 -0
- package/skills/github-pr-review/.codex-plugin/plugin.json +18 -0
- package/skills/github-pr-reviewer/.claude-plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/.codex-plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/SKILL.md +15 -1
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +143 -0
- package/skills/github-pr-reviewer/scripts/github_client.py +230 -0
- package/skills/github-pr-reviewer/scripts/main.py +23 -49
- package/skills/github-pr-reviewer/scripts/worker.py +94 -0
- package/skills/github-repo-monitor/.claude-plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/.codex-plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/SKILL.md +7 -1
- package/skills/github-repo-monitor/scripts/github_client.py +230 -0
- package/skills/github-repo-monitor/scripts/main.py +4 -46
- package/skills/gitlab/.claude-plugin/plugin.json +17 -0
- package/skills/gitlab/.codex-plugin/plugin.json +17 -0
- package/skills/gitlab-issue-to-mr/.claude-plugin/plugin.json +19 -0
- package/skills/gitlab-issue-to-mr/.codex-plugin/plugin.json +19 -0
- package/skills/gitlab-issue-to-mr/.plugin/plugin.json +19 -0
- package/skills/gitlab-issue-to-mr/README.md +92 -0
- package/skills/gitlab-issue-to-mr/SKILL.md +408 -0
- package/skills/gitlab-issue-to-mr/commands/issue-to-mr-setup.md +8 -0
- package/skills/gitlab-issue-to-mr/references/state-schema.md +140 -0
- package/skills/gitlab-issue-to-mr/scripts/main.py +1448 -0
- package/skills/incident-retrospective/.claude-plugin/plugin.json +21 -0
- package/skills/incident-retrospective/.codex-plugin/plugin.json +21 -0
- package/skills/index.js +52 -6
- package/skills/iterate/.claude-plugin/plugin.json +13 -0
- package/skills/iterate/.codex-plugin/plugin.json +13 -0
- package/skills/jira-issue-to-pr/.claude-plugin/plugin.json +19 -0
- package/skills/jira-issue-to-pr/.codex-plugin/plugin.json +19 -0
- package/skills/jupyter/.claude-plugin/plugin.json +18 -0
- package/skills/jupyter/.codex-plugin/plugin.json +18 -0
- package/skills/kubernetes/.claude-plugin/plugin.json +18 -0
- package/skills/kubernetes/.codex-plugin/plugin.json +18 -0
- package/skills/learn-from-code-review/.claude-plugin/plugin.json +19 -0
- package/skills/learn-from-code-review/.codex-plugin/plugin.json +19 -0
- package/skills/linear/.claude-plugin/plugin.json +19 -0
- package/skills/linear/.codex-plugin/plugin.json +19 -0
- package/skills/linear-triage/.claude-plugin/plugin.json +21 -0
- package/skills/linear-triage/.codex-plugin/plugin.json +21 -0
- package/skills/news-digest/.claude-plugin/plugin.json +19 -0
- package/skills/news-digest/.codex-plugin/plugin.json +19 -0
- package/skills/notion/.claude-plugin/plugin.json +17 -0
- package/skills/notion/.codex-plugin/plugin.json +17 -0
- package/skills/npm/.claude-plugin/plugin.json +17 -0
- package/skills/npm/.codex-plugin/plugin.json +17 -0
- package/skills/openhands-api/.claude-plugin/plugin.json +22 -0
- package/skills/openhands-api/.codex-plugin/plugin.json +22 -0
- package/skills/openhands-automation/.claude-plugin/plugin.json +19 -0
- package/skills/openhands-automation/.codex-plugin/plugin.json +19 -0
- package/skills/openhands-automation/README.md +1 -1
- package/skills/openhands-automation/SKILL.md +18 -8
- package/skills/openhands-enterprise-troubleshooting/.claude-plugin/plugin.json +20 -0
- package/skills/openhands-enterprise-troubleshooting/.codex-plugin/plugin.json +20 -0
- package/skills/openhands-enterprise-troubleshooting/.plugin/plugin.json +20 -0
- package/skills/openhands-enterprise-troubleshooting/README.md +79 -0
- package/skills/openhands-enterprise-troubleshooting/SKILL.md +339 -0
- package/skills/openhands-enterprise-troubleshooting/references/diagnostics.md +698 -0
- package/skills/openhands-enterprise-troubleshooting/references/support-bundle-analysis.md +570 -0
- package/skills/openhands-enterprise-troubleshooting/scripts/bundle_triage.py +532 -0
- package/skills/openhands-sdk/.claude-plugin/plugin.json +20 -0
- package/skills/openhands-sdk/.codex-plugin/plugin.json +20 -0
- package/skills/openhands-sdk/SKILL.md +2 -0
- package/skills/pdflatex/.claude-plugin/plugin.json +18 -0
- package/skills/pdflatex/.codex-plugin/plugin.json +18 -0
- package/skills/plain-english-content/.claude-plugin/plugin.json +15 -0
- package/skills/plain-english-content/.codex-plugin/plugin.json +15 -0
- package/skills/prd/.claude-plugin/plugin.json +19 -0
- package/skills/prd/.codex-plugin/plugin.json +19 -0
- package/skills/research-brief/.claude-plugin/plugin.json +20 -0
- package/skills/research-brief/.codex-plugin/plugin.json +20 -0
- package/skills/security/.claude-plugin/plugin.json +18 -0
- package/skills/security/.codex-plugin/plugin.json +18 -0
- package/skills/skill-creator/.claude-plugin/plugin.json +17 -0
- package/skills/skill-creator/.codex-plugin/plugin.json +17 -0
- package/skills/slack-channel-monitor/.claude-plugin/plugin.json +21 -0
- package/skills/slack-channel-monitor/.codex-plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/.claude-plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/.codex-plugin/plugin.json +21 -0
- package/skills/spark-version-upgrade/.claude-plugin/plugin.json +20 -0
- package/skills/spark-version-upgrade/.codex-plugin/plugin.json +20 -0
- package/skills/ssh/.claude-plugin/plugin.json +18 -0
- package/skills/ssh/.codex-plugin/plugin.json +18 -0
- package/skills/swift-linux/.claude-plugin/plugin.json +17 -0
- package/skills/swift-linux/.codex-plugin/plugin.json +17 -0
- package/skills/technical-writing/.claude-plugin/plugin.json +15 -0
- package/skills/technical-writing/.codex-plugin/plugin.json +15 -0
- package/skills/theme-factory/.claude-plugin/plugin.json +19 -0
- package/skills/theme-factory/.codex-plugin/plugin.json +19 -0
- package/skills/ticket-to-code-change/.claude-plugin/plugin.json +21 -0
- package/skills/ticket-to-code-change/.codex-plugin/plugin.json +21 -0
- package/skills/upstream-fork-sync/.claude-plugin/plugin.json +21 -0
- package/skills/upstream-fork-sync/.codex-plugin/plugin.json +21 -0
- package/skills/uv/.claude-plugin/plugin.json +18 -0
- package/skills/uv/.codex-plugin/plugin.json +18 -0
- package/skills/vercel/.claude-plugin/plugin.json +18 -0
- package/skills/vercel/.codex-plugin/plugin.json +18 -0
- package/tests/fixtures/automations/capabilities.json +6 -3
- package/tests/fixtures/automations/github-pr-reviewer.json +64 -43
- package/tests/github_automation_helpers.py +31 -0
- package/tests/test_agent_conversation_dispatch.py +180 -0
- package/tests/test_automation_setup.py +3 -3
- package/tests/test_github_automation_foundation.py +73 -0
- package/tests/test_github_developer_delivery.py +150 -0
- package/tests/test_github_reviewer_delivery.py +110 -0
- package/tests/test_github_skill_installation.py +63 -0
- package/tests/test_github_triage_delivery.py +90 -0
- package/tests/test_github_watchdog_delivery.py +233 -0
- package/tests/test_gitlab_issue_to_mr.py +823 -0
- package/.pr/custom-automation-event-trigger.png +0 -0
- package/.pr/custom-automation-plugin-fields.png +0 -0
- package/.pr/custom-automation-prompt-review.png +0 -0
- package/.pr/custom-automation-prompt-scheduled.png +0 -0
- package/.pr/custom-automation-upload-fields.png +0 -0
|
@@ -8,10 +8,10 @@ GitHub `labeled` event has not already been processed by this automation.
|
|
|
8
8
|
Each repository is polled independently and keeps its own state document, so
|
|
9
9
|
pull-request numbers never collide across repositories.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
commit as a tarball, hands the agent that directory as its
|
|
13
|
-
removes it once the review has finished.
|
|
14
|
-
|
|
11
|
+
This standalone script owns the repository checkout: it downloads the pull
|
|
12
|
+
request's head commit as a tarball, hands the agent that directory as its
|
|
13
|
+
workspace, and removes it once the review has finished. Catalog workers may
|
|
14
|
+
instead reuse its prompt builder with their own workspace instructions.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import io
|
|
@@ -26,7 +26,9 @@ import urllib.error
|
|
|
26
26
|
import urllib.request
|
|
27
27
|
from collections.abc import Callable
|
|
28
28
|
from pathlib import Path, PurePosixPath
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
from github_client import github_request as _github_request
|
|
31
|
+
from github_client import github_paginate as _github_paginate
|
|
30
32
|
|
|
31
33
|
# Configuration. Two setup paths write it, and both end up here:
|
|
32
34
|
#
|
|
@@ -336,46 +338,6 @@ def save_state(repo: str, state: dict) -> None:
|
|
|
336
338
|
print(f" State saved to {path}")
|
|
337
339
|
|
|
338
340
|
|
|
339
|
-
def _github_request(
|
|
340
|
-
token: str,
|
|
341
|
-
method: str,
|
|
342
|
-
path: str,
|
|
343
|
-
params: dict | None = None,
|
|
344
|
-
body: dict | None = None,
|
|
345
|
-
accept: str = "application/vnd.github+json",
|
|
346
|
-
) -> tuple:
|
|
347
|
-
url = f"https://api.github.com{path}"
|
|
348
|
-
if params:
|
|
349
|
-
url = f"{url}?{urlencode(params)}"
|
|
350
|
-
headers = {
|
|
351
|
-
"Authorization": f"Bearer {token}",
|
|
352
|
-
"Accept": accept,
|
|
353
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
354
|
-
"Content-Type": "application/json",
|
|
355
|
-
}
|
|
356
|
-
data = json.dumps(body).encode() if body is not None else None
|
|
357
|
-
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
358
|
-
with urllib.request.urlopen(req) as r:
|
|
359
|
-
raw = r.read()
|
|
360
|
-
return (json.loads(raw) if raw.strip() else {}), dict(r.headers)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def _github_paginate(token: str, path: str, params: dict | None = None) -> list:
|
|
364
|
-
results = []
|
|
365
|
-
page = 1
|
|
366
|
-
base_params = dict(params or {})
|
|
367
|
-
base_params.setdefault("per_page", 100)
|
|
368
|
-
while True:
|
|
369
|
-
base_params["page"] = page
|
|
370
|
-
data, _ = _github_request(token, "GET", path, params=base_params)
|
|
371
|
-
if not isinstance(data, list):
|
|
372
|
-
break
|
|
373
|
-
results.extend(data)
|
|
374
|
-
if len(data) < base_params["per_page"]:
|
|
375
|
-
break
|
|
376
|
-
page += 1
|
|
377
|
-
return results
|
|
378
|
-
|
|
379
341
|
|
|
380
342
|
def _resolve_github_token() -> str:
|
|
381
343
|
try:
|
|
@@ -770,7 +732,16 @@ def _load_repo_review_guide(workspace_dir: Path) -> str | None:
|
|
|
770
732
|
return None
|
|
771
733
|
|
|
772
734
|
|
|
773
|
-
def _build_review_prompt(
|
|
735
|
+
def _build_review_prompt(
|
|
736
|
+
repo: str,
|
|
737
|
+
pr: dict,
|
|
738
|
+
head_sha: str,
|
|
739
|
+
label_event: dict,
|
|
740
|
+
repo_review_guide: str | None = None,
|
|
741
|
+
*,
|
|
742
|
+
workspace_instructions: str | None = None,
|
|
743
|
+
github_token_secret: str = "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
744
|
+
) -> str:
|
|
774
745
|
number = pr.get("number", "?")
|
|
775
746
|
title = pr.get("title", "(no title)")
|
|
776
747
|
body = (pr.get("body") or "").strip() or "(no description)"
|
|
@@ -790,6 +761,10 @@ def _build_review_prompt(repo: str, pr: dict, head_sha: str, label_event: dict,
|
|
|
790
761
|
f"\n\nRepo-specific review guide (from {REPO_REVIEW_GUIDE_PATH}):\n---\n{repo_review_guide}\n---\n"
|
|
791
762
|
if repo_review_guide else ""
|
|
792
763
|
)
|
|
764
|
+
workspace = workspace_instructions or (
|
|
765
|
+
"The workspace is already the repository root at the exact Head SHA above. "
|
|
766
|
+
"Do not clone, fetch, check out, or delete the repository."
|
|
767
|
+
)
|
|
793
768
|
|
|
794
769
|
return (
|
|
795
770
|
"You are an AI code reviewer. Review the GitHub pull request below and publish "
|
|
@@ -806,8 +781,7 @@ def _build_review_prompt(repo: str, pr: dict, head_sha: str, label_event: dict,
|
|
|
806
781
|
f"URL : {html_url}\n"
|
|
807
782
|
f"\nPR Description:\n---\n{body}\n---\n\n"
|
|
808
783
|
"Required workflow:\n"
|
|
809
|
-
"1.
|
|
810
|
-
"Do not clone, fetch, check out, or delete the repository.\n"
|
|
784
|
+
f"1. {workspace}\n"
|
|
811
785
|
"2. Before reviewing, you MUST read the repository's own guidance to understand the repo first.\n"
|
|
812
786
|
" Read `AGENTS.md` at the repository root (and any nested `AGENTS.md` covering the "
|
|
813
787
|
"changed files), plus other relevant docs when present - e.g. `CONTRIBUTING.md`, "
|
|
@@ -815,7 +789,7 @@ def _build_review_prompt(repo: str, pr: dict, head_sha: str, label_event: dict,
|
|
|
815
789
|
"guidance to your review.\n"
|
|
816
790
|
" Then inspect the PR discussion, existing review comments, changed files, and the diff, "
|
|
817
791
|
"together with the surrounding code in the workspace.\n"
|
|
818
|
-
" Use `gh` or GitHub REST API calls with `
|
|
792
|
+
f" Use `gh` or GitHub REST API calls with `{github_token_secret}`; never print secret values.\n"
|
|
819
793
|
"3. Ground every finding in the workspace code. Before using an inline location, verify that "
|
|
820
794
|
"the path and line are part of this pull request's diff.\n"
|
|
821
795
|
f"4. Publish one review with `POST /repos/{repo}/pulls/{number}/reviews`, using "
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Select labeled PR heads and delegate each review to a sandboxed agent."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import main as workflow
|
|
6
|
+
from agent_conversation import AgentConversationDispatcher
|
|
7
|
+
from github_client import GitHubRepository, run_repositories
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PullRequestReviewer(GitHubRepository):
|
|
11
|
+
name = "github-pr-reviewer"
|
|
12
|
+
|
|
13
|
+
def _prompt(self, pr, label_event):
|
|
14
|
+
number = pr["number"]
|
|
15
|
+
sha = pr["head"]["sha"]
|
|
16
|
+
label = self.config.get("trigger_label", workflow.TRIGGER_LABEL)
|
|
17
|
+
token = self.token_name
|
|
18
|
+
workspace = (
|
|
19
|
+
"The workspace contains an empty Git repository. Before fetching, run "
|
|
20
|
+
f"`GH_TOKEN=${{{token}}} gh auth setup-git` so Git uses the "
|
|
21
|
+
"profile-scoped credential without storing it in the remote URL. Set "
|
|
22
|
+
f"the plain HTTPS origin to `https://github.com/{self.repository}.git`, "
|
|
23
|
+
f"fetch PR #{number}, and check out exact head `{sha}` in detached-HEAD "
|
|
24
|
+
"mode. Set `GIT_TERMINAL_PROMPT=0` on Git network commands so a missing "
|
|
25
|
+
"permission fails immediately instead of waiting for input."
|
|
26
|
+
)
|
|
27
|
+
prompt = workflow._build_review_prompt(
|
|
28
|
+
self.repository,
|
|
29
|
+
pr,
|
|
30
|
+
sha,
|
|
31
|
+
label_event,
|
|
32
|
+
workspace_instructions=workspace,
|
|
33
|
+
github_token_secret=token,
|
|
34
|
+
)
|
|
35
|
+
return (
|
|
36
|
+
prompt + "\n\nAcceptance reporting:\n"
|
|
37
|
+
"- Inspect the current GitHub Actions results for the exact head and run "
|
|
38
|
+
"the repository's appropriate focused tests in the workspace. Do not "
|
|
39
|
+
"modify tracked files.\n"
|
|
40
|
+
f"- Re-read {self.repository} PR #{number} immediately before reporting. "
|
|
41
|
+
f"If its head is no longer `{sha}`, publish no status and leave `{label}` "
|
|
42
|
+
"in place so the new head is reviewed.\n"
|
|
43
|
+
"- After the readable native review is accepted by GitHub, set commit "
|
|
44
|
+
"status `software-factory/review` to success only for an `✅ APPROVED` "
|
|
45
|
+
"verdict, otherwise failure. Set `software-factory/tests` to success only "
|
|
46
|
+
"when the relevant checks and tests pass, otherwise failure. Use the exact "
|
|
47
|
+
f"head `{sha}` for both statuses.\n"
|
|
48
|
+
f"- After both statuses are visible on that head, remove the `{label}` "
|
|
49
|
+
"label. Never paste JSON artifacts or full command logs into comments."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def run(self):
|
|
53
|
+
repository_id = self.gh("GET", "")["id"]
|
|
54
|
+
label = self.config.get("trigger_label", workflow.TRIGGER_LABEL)
|
|
55
|
+
prs = self.gh_pages("/pulls?state=open&sort=updated&direction=asc")
|
|
56
|
+
for candidate in prs:
|
|
57
|
+
if label not in {item["name"] for item in candidate.get("labels", [])}:
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
pr = self.gh("GET", f"/pulls/{candidate['number']}")
|
|
61
|
+
event = workflow._latest_trigger_label_event(
|
|
62
|
+
self.token, self.repository, pr["number"]
|
|
63
|
+
)
|
|
64
|
+
if event is None:
|
|
65
|
+
continue
|
|
66
|
+
sha = pr["head"]["sha"]
|
|
67
|
+
result = self.dispatcher.deliver(
|
|
68
|
+
subject=f"{repository_id}:pr:{pr['number']}",
|
|
69
|
+
delivery=f"{event['id']}:{sha}",
|
|
70
|
+
prompt=self._prompt(pr, event),
|
|
71
|
+
)
|
|
72
|
+
print(
|
|
73
|
+
json.dumps(
|
|
74
|
+
{
|
|
75
|
+
"repository": self.repository,
|
|
76
|
+
"pr": pr["number"],
|
|
77
|
+
"head_sha": sha,
|
|
78
|
+
"disposition": result["disposition"],
|
|
79
|
+
"conversation_id": result["conversation_id"],
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
flush=True,
|
|
83
|
+
)
|
|
84
|
+
except Exception as exc: # noqa: BLE001 - one PR must not block the scan
|
|
85
|
+
print(
|
|
86
|
+
f"Failed to submit {self.repository} PR "
|
|
87
|
+
f"#{candidate.get('number', '?')}: {exc}",
|
|
88
|
+
flush=True,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
with AgentConversationDispatcher() as dispatcher:
|
|
94
|
+
run_repositories(PullRequestReviewer, dispatcher=dispatcher)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "github-repo-monitor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create a cron automation that polls a GitHub repository for issue and PR comments containing a configurable trigger phrase (@OpenHands by default). Starts an OpenHands conversation with full issue/PR context, posts acknowledgement comments with a conversation link, and summarises results back to the issue/PR when the agent finishes.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"github",
|
|
14
|
+
"monitor",
|
|
15
|
+
"issues",
|
|
16
|
+
"pull-requests",
|
|
17
|
+
"trigger",
|
|
18
|
+
"cron",
|
|
19
|
+
"automation",
|
|
20
|
+
"integration"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "github-repo-monitor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create a cron automation that polls a GitHub repository for issue and PR comments containing a configurable trigger phrase (@OpenHands by default). Starts an OpenHands conversation with full issue/PR context, posts acknowledgement comments with a conversation link, and summarises results back to the issue/PR when the agent finishes.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"github",
|
|
14
|
+
"monitor",
|
|
15
|
+
"issues",
|
|
16
|
+
"pull-requests",
|
|
17
|
+
"trigger",
|
|
18
|
+
"cron",
|
|
19
|
+
"automation",
|
|
20
|
+
"integration"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -37,6 +37,10 @@ On every subsequent run:
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+
The script imports shared GitHub transport from
|
|
41
|
+
`scripts/github_client.py`, installed with this skill. Include it beside
|
|
42
|
+
`main.py` when packaging manually, as shown below.
|
|
43
|
+
|
|
40
44
|
## Prerequisites
|
|
41
45
|
|
|
42
46
|
### Required secret
|
|
@@ -181,9 +185,11 @@ constant substitutions near the top of the file:
|
|
|
181
185
|
| `ALLOWED_GITHUB_LOGINS = ["<TOKEN_OWNER>"]` | `ALLOWED_GITHUB_LOGINS = {allowed_logins_list}` |
|
|
182
186
|
| `DEFAULT_OPENHANDS_URL = "http://localhost:8000"` | `DEFAULT_OPENHANDS_URL = "{url}"` (keep default if the user has no preference) |
|
|
183
187
|
|
|
184
|
-
|
|
188
|
+
Run these commands from this skill's directory and write the customised script
|
|
189
|
+
to a temporary build directory:
|
|
185
190
|
```bash
|
|
186
191
|
mkdir -p /tmp/github-monitor-build
|
|
192
|
+
cp -L scripts/github_client.py /tmp/github-monitor-build/github_client.py
|
|
187
193
|
# (write the customised main.py to /tmp/github-monitor-build/main.py)
|
|
188
194
|
```
|
|
189
195
|
|
|
@@ -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
|
|
@@ -34,7 +34,10 @@ import time
|
|
|
34
34
|
import urllib.error
|
|
35
35
|
import urllib.request
|
|
36
36
|
from datetime import datetime, timedelta, timezone
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
from github_client import github_request as _github_request
|
|
39
|
+
from github_client import github_paginate as _github_paginate
|
|
40
|
+
|
|
38
41
|
|
|
39
42
|
# ── Embedded configuration (filled in by the skill at creation time) ──────────
|
|
40
43
|
REPO = "owner/repo" # e.g. "microsoft/vscode"
|
|
@@ -210,51 +213,6 @@ def save_state(state: dict) -> None:
|
|
|
210
213
|
|
|
211
214
|
# ── GitHub API helpers ─────────────────────────────────────────────────────────
|
|
212
215
|
|
|
213
|
-
def _github_request(
|
|
214
|
-
token: str,
|
|
215
|
-
method: str,
|
|
216
|
-
path: str,
|
|
217
|
-
params: dict | None = None,
|
|
218
|
-
body: dict | None = None,
|
|
219
|
-
) -> tuple[dict | list, dict]:
|
|
220
|
-
"""Low-level GitHub API call. Returns (parsed_body, response_headers).
|
|
221
|
-
Raises urllib.error.HTTPError on non-2xx responses.
|
|
222
|
-
"""
|
|
223
|
-
base = "https://api.github.com"
|
|
224
|
-
url = f"{base}{path}"
|
|
225
|
-
if params:
|
|
226
|
-
url = f"{url}?{urlencode(params)}"
|
|
227
|
-
headers = {
|
|
228
|
-
"Authorization": f"Bearer {token}",
|
|
229
|
-
"Accept": "application/vnd.github+json",
|
|
230
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
231
|
-
"Content-Type": "application/json",
|
|
232
|
-
}
|
|
233
|
-
data = json.dumps(body).encode() if body is not None else None
|
|
234
|
-
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
235
|
-
with urllib.request.urlopen(req) as r:
|
|
236
|
-
resp_headers = dict(r.headers)
|
|
237
|
-
raw = r.read()
|
|
238
|
-
return (json.loads(raw) if raw.strip() else {}), resp_headers
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def _github_paginate(token: str, path: str, params: dict | None = None) -> list:
|
|
242
|
-
"""Fetch all pages from a GitHub list endpoint."""
|
|
243
|
-
results = []
|
|
244
|
-
page = 1
|
|
245
|
-
base_params = dict(params or {})
|
|
246
|
-
base_params.setdefault("per_page", 100)
|
|
247
|
-
while True:
|
|
248
|
-
base_params["page"] = page
|
|
249
|
-
data, _ = _github_request(token, "GET", path, params=base_params)
|
|
250
|
-
if not isinstance(data, list):
|
|
251
|
-
break
|
|
252
|
-
results.extend(data)
|
|
253
|
-
if len(data) < base_params["per_page"]:
|
|
254
|
-
break
|
|
255
|
-
page += 1
|
|
256
|
-
return results
|
|
257
|
-
|
|
258
216
|
|
|
259
217
|
def _resolve_github_token() -> str:
|
|
260
218
|
"""Fetch GITHUB_PERSONAL_ACCESS_TOKEN from secrets. Raises RuntimeError if absent."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when working with code hosted on GitLab or managing GitLab resources.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"git",
|
|
15
|
+
"merge-request"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when working with code hosted on GitLab or managing GitLab resources.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"git",
|
|
15
|
+
"merge-request"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab-issue-to-mr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. It clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the merge request itself, without ever handing the agent a push credential.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"issue",
|
|
15
|
+
"merge-request",
|
|
16
|
+
"automation",
|
|
17
|
+
"integration"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab-issue-to-mr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. It clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the merge request itself, without ever handing the agent a push credential.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"issue",
|
|
15
|
+
"merge-request",
|
|
16
|
+
"automation",
|
|
17
|
+
"integration"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitlab-issue-to-mr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create an automation that implements GitLab issues when a configurable trigger label is applied. It clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the merge request itself, without ever handing the agent a push credential.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"gitlab",
|
|
14
|
+
"issue",
|
|
15
|
+
"merge-request",
|
|
16
|
+
"automation",
|
|
17
|
+
"integration"
|
|
18
|
+
]
|
|
19
|
+
}
|