@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.
Files changed (64) hide show
  1. package/.github/workflows/check-extensions.yml +4 -0
  2. package/.github/workflows/pr-artifacts.yml +294 -0
  3. package/.github/workflows/pr-description-check.yml +4 -2
  4. package/.release-please-manifest.json +1 -1
  5. package/AGENTS.md +28 -4
  6. package/README.md +5 -2
  7. package/automations/README.md +52 -36
  8. package/automations/bundle-index.js +13 -3
  9. package/automations/catalog/custom-automation/manifest.json +229 -0
  10. package/automations/catalog/github-agents-md-maintainer/manifest.json +2 -1
  11. package/automations/catalog/github-issue-to-pr/manifest.json +3 -2
  12. package/automations/catalog/github-issue-triage/manifest.json +72 -0
  13. package/automations/catalog/github-pr-reviewer/manifest.json +3 -2
  14. package/automations/catalog/gitlab-issue-to-mr/manifest.json +124 -0
  15. package/automations/catalog/qa-changes/manifest.json +1 -1
  16. package/automations/catalog-index.js +23 -17
  17. package/automations/catalog.schema.json +81 -7
  18. package/automations/index.d.ts +52 -2
  19. package/automations/interface.json +2 -1
  20. package/integrations/catalog/gitlab.json +27 -5
  21. package/integrations/catalog/reportportal.json +76 -0
  22. package/integrations/catalog-index.js +6 -4
  23. package/marketplaces/openhands-extensions.json +36 -0
  24. package/package.json +1 -1
  25. package/pyproject.toml +2 -2
  26. package/python/openhands_extensions/_version.py +1 -1
  27. package/skills/github/scripts/github_client.py +204 -0
  28. package/skills/github-agents-md-maintainer/SKILL.md +7 -1
  29. package/skills/github-agents-md-maintainer/scripts/main.py +4 -40
  30. package/skills/github-issue-to-pr/SKILL.md +8 -1
  31. package/skills/github-issue-to-pr/scripts/main.py +3 -40
  32. package/skills/github-issue-triage/.plugin/plugin.json +9 -0
  33. package/skills/github-issue-triage/README.md +9 -0
  34. package/skills/github-issue-triage/SKILL.md +37 -0
  35. package/skills/github-issue-triage/commands/github-issue-triage.md +8 -0
  36. package/skills/github-issue-triage/scripts/worker.py +123 -0
  37. package/skills/github-pr-reviewer/SKILL.md +8 -1
  38. package/skills/github-pr-reviewer/scripts/main.py +3 -41
  39. package/skills/github-repo-monitor/SKILL.md +7 -1
  40. package/skills/github-repo-monitor/scripts/main.py +4 -46
  41. package/skills/gitlab-issue-to-mr/.plugin/plugin.json +19 -0
  42. package/skills/gitlab-issue-to-mr/README.md +92 -0
  43. package/skills/gitlab-issue-to-mr/SKILL.md +408 -0
  44. package/skills/gitlab-issue-to-mr/commands/issue-to-mr-setup.md +8 -0
  45. package/skills/gitlab-issue-to-mr/references/state-schema.md +140 -0
  46. package/skills/gitlab-issue-to-mr/scripts/main.py +1448 -0
  47. package/skills/index.js +43 -6
  48. package/skills/openhands-automation/README.md +1 -1
  49. package/skills/openhands-automation/SKILL.md +18 -8
  50. package/skills/openhands-enterprise-troubleshooting/.plugin/plugin.json +20 -0
  51. package/skills/openhands-enterprise-troubleshooting/README.md +79 -0
  52. package/skills/openhands-enterprise-troubleshooting/SKILL.md +339 -0
  53. package/skills/openhands-enterprise-troubleshooting/references/diagnostics.md +698 -0
  54. package/skills/openhands-enterprise-troubleshooting/references/support-bundle-analysis.md +570 -0
  55. package/skills/openhands-enterprise-troubleshooting/scripts/bundle_triage.py +532 -0
  56. package/skills/openhands-sdk/SKILL.md +2 -0
  57. package/tests/fixtures/automations/custom-automation.json +398 -0
  58. package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
  59. package/tests/github_automation_helpers.py +25 -0
  60. package/tests/test_automation_setup.py +340 -92
  61. package/tests/test_github_automation_foundation.py +73 -0
  62. package/tests/test_github_skill_installation.py +63 -0
  63. package/tests/test_github_triage_delivery.py +53 -0
  64. 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")