@openhands/extensions 0.19.0 → 0.21.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/.github/workflows/check-extensions.yml +4 -0
- package/.github/workflows/pr-artifacts.yml +294 -0
- package/.github/workflows/pr-description-check.yml +4 -2
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +28 -4
- package/README.md +5 -2
- package/automations/README.md +52 -36
- package/automations/bundle-index.js +13 -3
- package/automations/catalog/custom-automation/manifest.json +229 -0
- package/automations/catalog/github-agents-md-maintainer/manifest.json +2 -1
- package/automations/catalog/github-issue-to-pr/manifest.json +3 -2
- package/automations/catalog/github-issue-triage/manifest.json +72 -0
- package/automations/catalog/github-pr-reviewer/manifest.json +3 -2
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +124 -0
- package/automations/catalog/qa-changes/manifest.json +1 -1
- package/automations/catalog-index.js +23 -17
- package/automations/catalog.schema.json +81 -7
- package/automations/index.d.ts +52 -2
- package/automations/interface.json +2 -1
- package/integrations/catalog/gitlab.json +27 -5
- package/integrations/catalog/reportportal.json +76 -0
- package/integrations/catalog-index.js +6 -4
- package/marketplaces/openhands-extensions.json +36 -0
- package/package.json +1 -1
- package/pyproject.toml +2 -2
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/github/scripts/github_client.py +204 -0
- package/skills/github-agents-md-maintainer/SKILL.md +7 -1
- package/skills/github-agents-md-maintainer/scripts/main.py +4 -40
- package/skills/github-issue-to-pr/SKILL.md +8 -1
- package/skills/github-issue-to-pr/scripts/main.py +3 -40
- 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 +37 -0
- package/skills/github-issue-triage/commands/github-issue-triage.md +8 -0
- package/skills/github-issue-triage/scripts/worker.py +123 -0
- package/skills/github-pr-reviewer/SKILL.md +8 -1
- package/skills/github-pr-reviewer/scripts/main.py +3 -41
- package/skills/github-repo-monitor/SKILL.md +7 -1
- package/skills/github-repo-monitor/scripts/main.py +4 -46
- 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/index.js +43 -6
- package/skills/openhands-automation/README.md +1 -1
- package/skills/openhands-automation/SKILL.md +18 -8
- 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/SKILL.md +2 -0
- package/tests/fixtures/automations/custom-automation.json +398 -0
- package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
- package/tests/github_automation_helpers.py +25 -0
- package/tests/test_automation_setup.py +340 -92
- package/tests/test_github_automation_foundation.py +73 -0
- package/tests/test_github_skill_installation.py +63 -0
- package/tests/test_github_triage_delivery.py +53 -0
- package/tests/test_gitlab_issue_to_mr.py +823 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Contracts for the shared GitHub automation foundation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from urllib.error import HTTPError
|
|
5
|
+
|
|
6
|
+
import github_client
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_repository_runs_are_isolated(tmp_path, monkeypatch):
|
|
11
|
+
monkeypatch.chdir(tmp_path)
|
|
12
|
+
monkeypatch.setattr("sys.argv", ["worker.py"])
|
|
13
|
+
(tmp_path / "config.json").write_text(
|
|
14
|
+
json.dumps(
|
|
15
|
+
{
|
|
16
|
+
"repos": ["owner/failed", "owner/next"],
|
|
17
|
+
"github_token_secret": "GITHUB_TOKEN",
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
visited = []
|
|
22
|
+
|
|
23
|
+
class Automation:
|
|
24
|
+
def __init__(self, *, repository, github_token_secret, conversation):
|
|
25
|
+
assert github_token_secret == "GITHUB_TOKEN"
|
|
26
|
+
self.repository = repository
|
|
27
|
+
|
|
28
|
+
def run(self):
|
|
29
|
+
visited.append(self.repository)
|
|
30
|
+
if self.repository == "owner/failed":
|
|
31
|
+
raise TimeoutError
|
|
32
|
+
|
|
33
|
+
with pytest.raises(RuntimeError, match="owner/failed"):
|
|
34
|
+
github_client.run_repositories(Automation)
|
|
35
|
+
|
|
36
|
+
assert visited == ["owner/failed", "owner/next"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_pagination_rejects_a_non_list_response(monkeypatch):
|
|
40
|
+
monkeypatch.setattr(
|
|
41
|
+
github_client, "github_request", lambda *args, **kwargs: ({}, {})
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
with pytest.raises(TypeError, match="paginated GitHub list"):
|
|
45
|
+
github_client.github_paginate("token", "/repos/owner/repo/issues")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize(
|
|
49
|
+
"state,reason,expected",
|
|
50
|
+
[
|
|
51
|
+
("closed", "completed", True),
|
|
52
|
+
("closed", "not_planned", False),
|
|
53
|
+
("open", None, False),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_dependencies_must_be_completed(state, reason, expected):
|
|
57
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
58
|
+
repository._completed_dependencies = {}
|
|
59
|
+
repository.gh = lambda *args: {"state": state, "state_reason": reason}
|
|
60
|
+
|
|
61
|
+
assert repository.dependencies_complete({"body": "Depends on: #12"}) is expected
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_dependency_permission_errors_fail_closed():
|
|
65
|
+
repository = object.__new__(github_client.GitHubRepository)
|
|
66
|
+
repository._completed_dependencies = {}
|
|
67
|
+
|
|
68
|
+
def denied(*args):
|
|
69
|
+
raise HTTPError("https://api.github.com", 403, "Forbidden", {}, None)
|
|
70
|
+
|
|
71
|
+
repository.gh = denied
|
|
72
|
+
with pytest.raises(HTTPError):
|
|
73
|
+
repository.dependencies_complete({"body": "Depends on: #12"})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Verify shared support is included in independently installed skills."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from openhands.sdk.skills import install_skill
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_installed_triage_skill_contains_its_bundle(tmp_path):
|
|
15
|
+
name = "github-issue-triage"
|
|
16
|
+
install_skill(source=str(ROOT / "skills" / name), installed_dir=tmp_path)
|
|
17
|
+
scripts = tmp_path / name / "scripts"
|
|
18
|
+
manifest = json.loads(
|
|
19
|
+
(ROOT / f"automations/catalog/{name}/manifest.json").read_text()
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
for filename, source in manifest["setup"]["bundle"]["files"].items():
|
|
23
|
+
installed = scripts / filename
|
|
24
|
+
assert installed.is_file() and not installed.is_symlink()
|
|
25
|
+
assert installed.read_bytes() == (ROOT / source).read_bytes()
|
|
26
|
+
|
|
27
|
+
result = subprocess.run(
|
|
28
|
+
[sys.executable, "-c", "import worker"],
|
|
29
|
+
cwd=scripts,
|
|
30
|
+
text=True,
|
|
31
|
+
capture_output=True,
|
|
32
|
+
check=False,
|
|
33
|
+
)
|
|
34
|
+
assert result.returncode == 0, result.stderr
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
"name",
|
|
39
|
+
[
|
|
40
|
+
"github-repo-monitor",
|
|
41
|
+
"github-agents-md-maintainer",
|
|
42
|
+
"github-issue-to-pr",
|
|
43
|
+
"github-pr-reviewer",
|
|
44
|
+
],
|
|
45
|
+
)
|
|
46
|
+
def test_installed_github_skill_contains_shared_client(tmp_path, name):
|
|
47
|
+
install_skill(source=str(ROOT / "skills" / name), installed_dir=tmp_path)
|
|
48
|
+
scripts = tmp_path / name / "scripts"
|
|
49
|
+
helper = scripts / "github_client.py"
|
|
50
|
+
|
|
51
|
+
assert helper.is_file() and not helper.is_symlink()
|
|
52
|
+
assert (
|
|
53
|
+
helper.read_bytes()
|
|
54
|
+
== (ROOT / "skills/github/scripts/github_client.py").read_bytes()
|
|
55
|
+
)
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
[sys.executable, "-c", "import main"],
|
|
58
|
+
cwd=scripts,
|
|
59
|
+
text=True,
|
|
60
|
+
capture_output=True,
|
|
61
|
+
check=False,
|
|
62
|
+
)
|
|
63
|
+
assert result.returncode == 0, result.stderr
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Contract tests for independent triage delivery."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from github_automation_helpers import worker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"ready,criteria",
|
|
12
|
+
[(True, ["One observable result"]), (False, ["Clarify account ownership"])],
|
|
13
|
+
)
|
|
14
|
+
def test_triage_preserves_labels_and_only_readies_clear_work(
|
|
15
|
+
tmp_path, monkeypatch, ready, criteria
|
|
16
|
+
):
|
|
17
|
+
cls = worker("github-issue-triage", tmp_path, monkeypatch).IssueTriage
|
|
18
|
+
run = object.__new__(cls)
|
|
19
|
+
issue = {
|
|
20
|
+
"number": 1,
|
|
21
|
+
"title": "A small feature",
|
|
22
|
+
"body": "",
|
|
23
|
+
"labels": [{"name": "enhancement"}],
|
|
24
|
+
}
|
|
25
|
+
run.open_issues = lambda: [issue]
|
|
26
|
+
run.gh_pages = lambda path: []
|
|
27
|
+
run.dependencies_complete = lambda issue: True
|
|
28
|
+
result = {
|
|
29
|
+
"ready": ready,
|
|
30
|
+
"priority": "normal",
|
|
31
|
+
"acceptance_criteria": criteria,
|
|
32
|
+
"rationale": "Small independent change",
|
|
33
|
+
}
|
|
34
|
+
run.evidence = tmp_path
|
|
35
|
+
run.conversation = Mock()
|
|
36
|
+
run.conversation.run.side_effect = lambda **kwargs: (
|
|
37
|
+
tmp_path / "triage.json"
|
|
38
|
+
).write_text(json.dumps(result))
|
|
39
|
+
calls = []
|
|
40
|
+
run.gh = lambda *args: calls.append(args)
|
|
41
|
+
run.comment = lambda *args: calls.append(("comment", *args))
|
|
42
|
+
|
|
43
|
+
run.run()
|
|
44
|
+
|
|
45
|
+
updates = [call for call in calls if call[0] == "PATCH"]
|
|
46
|
+
assert bool(updates) is ready
|
|
47
|
+
if ready:
|
|
48
|
+
assert set(updates[0][2]["labels"]) == {
|
|
49
|
+
"enhancement",
|
|
50
|
+
"priority:normal",
|
|
51
|
+
"ready-for-dev",
|
|
52
|
+
}
|
|
53
|
+
assert criteria[0] in next(call[2] for call in calls if call[0] == "comment")
|