@openhands/extensions 0.24.0 → 0.26.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 +30 -0
- package/.codex-plugin/marketplace.json +30 -0
- package/.github/pull_request_template.md +17 -2
- package/.github/scripts/check_pr_description.py +64 -5
- package/.github/scripts/markdown_sections.py +95 -0
- package/.github/workflows/build-vscode-app.yml +30 -0
- package/.github/workflows/check-extensions.yml +3 -0
- package/.github/workflows/tests.yml +26 -0
- package/.plugin/marketplace.json +30 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +3 -2
- package/README.md +10 -3
- package/apps/README.md +20 -0
- package/apps/catalog.json +10 -0
- package/apps/index.d.ts +11 -0
- package/apps/index.js +5 -0
- package/apps/vscode/README.md +13 -0
- package/apps/vscode/canvas-extension.json +19 -0
- package/apps/vscode/extension.js +114 -0
- package/apps/vscode/scripts/package.mjs +84 -0
- package/automations/bundle-index.js +18 -14
- package/automations/catalog/github-agents-md-maintainer/manifest.json +1 -1
- package/automations/catalog/github-pr-reviewer/manifest.json +19 -8
- package/automations/catalog/github-stale-ci-pr-closer/manifest.json +71 -0
- package/automations/catalog/gitlab-issue-to-mr/manifest.json +1 -1
- package/automations/catalog/news-digest/manifest.json +2 -2
- package/automations/catalog-index.js +2 -0
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/marketplaces/openhands-extensions.json +30 -0
- package/package.json +9 -1
- package/plugins/openhands/skills/openhands-api/README.md +1 -1
- package/plugins/openhands/skills/openhands-api/SKILL.md +3 -3
- package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +13 -0
- package/plugins/openhands/skills/openhands-automation/SKILL.md +66 -4
- package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +21 -7
- package/plugins/openhands/skills/openhands-sdk/SKILL.md +1 -0
- package/plugins/pr-review/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/qa-changes/skills/github-pr-review/SKILL.md +12 -2
- package/plugins/runtime-api-configs/.claude-plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/.codex-plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/.plugin/plugin.json +20 -0
- package/plugins/runtime-api-configs/README.md +59 -0
- package/plugins/runtime-api-configs/SKILL.md +240 -0
- package/plugins/runtime-api-configs/scripts/warm_runtime_configs.py +331 -0
- package/plugins/runtime-api-configs/tests/test_warm_runtime_configs.py +338 -0
- package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +223 -43
- package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +183 -15
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/scripts/check_plugin_resources.py +116 -0
- package/scripts/sync_extensions.py +32 -5
- package/skills/canvas-extension-api/SKILL.md +34 -3
- package/skills/canvas-extension-api/references/backend-safety.md +14 -0
- package/skills/canvas-extension-api/references/connections.md +24 -0
- package/skills/canvas-extension-api/references/sidecar-pattern.md +49 -10
- package/skills/canvas-extension-api/references/v1-contract.md +53 -3
- package/skills/canvas-extension-api/scripts/validate-extension.mjs +52 -0
- package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +192 -1
- package/skills/github/scripts/agent_conversation.py +223 -43
- package/skills/github/scripts/github_client.py +183 -15
- package/skills/github-agents-md-maintainer/README.md +7 -0
- package/skills/github-agents-md-maintainer/SKILL.md +13 -6
- package/skills/github-agents-md-maintainer/scripts/github_client.py +183 -15
- package/skills/github-agents-md-maintainer/scripts/main.py +199 -29
- package/skills/github-delivery-watchdog/scripts/github_client.py +183 -15
- package/skills/github-issue-to-pr/scripts/agent_conversation.py +223 -43
- package/skills/github-issue-to-pr/scripts/github_client.py +183 -15
- package/skills/github-issue-to-pr/scripts/worker.py +10 -1
- package/skills/github-issue-triage/scripts/agent_conversation.py +223 -43
- package/skills/github-issue-triage/scripts/github_client.py +183 -15
- package/skills/github-pr-review/SKILL.md +12 -2
- package/skills/github-pr-reviewer/README.md +36 -0
- package/skills/github-pr-reviewer/SKILL.md +147 -4
- package/skills/github-pr-reviewer/scripts/agent_conversation.py +223 -43
- package/skills/github-pr-reviewer/scripts/github_client.py +183 -15
- package/skills/github-pr-reviewer/scripts/main.py +17 -1
- package/skills/github-pr-reviewer/scripts/worker.py +842 -57
- package/skills/github-repo-monitor/scripts/github_client.py +183 -15
- package/skills/github-repo-monitor/scripts/main.py +32 -1
- package/skills/github-stale-ci-pr-closer/.claude-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.codex-plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/.plugin/plugin.json +9 -0
- package/skills/github-stale-ci-pr-closer/README.md +9 -0
- package/skills/github-stale-ci-pr-closer/SKILL.md +24 -0
- package/skills/github-stale-ci-pr-closer/commands/github-stale-ci-pr-closer.md +8 -0
- package/skills/github-stale-ci-pr-closer/scripts/worker.py +508 -0
- package/skills/github-stale-ci-pr-closer/tests/test_worker.py +353 -0
- package/skills/gitlab-issue-to-mr/README.md +12 -0
- package/skills/gitlab-issue-to-mr/SKILL.md +13 -6
- package/skills/gitlab-issue-to-mr/scripts/main.py +217 -42
- package/skills/incident-retrospective/SKILL.md +5 -5
- package/skills/index.js +34 -16
- package/skills/jira-issue-to-pr/README.md +12 -4
- package/skills/jira-issue-to-pr/SKILL.md +129 -18
- package/skills/jira-issue-to-pr/references/setup.md +8 -0
- package/skills/jira-issue-to-pr/scripts/main.py +181 -54
- package/skills/jira-issue-to-pr/scripts/setup_webhook.py +290 -0
- package/skills/linear-triage/SKILL.md +5 -5
- package/skills/news-digest/README.md +6 -0
- package/skills/news-digest/SKILL.md +19 -9
- package/skills/news-digest/commands/news-digest-setup.md +1 -1
- package/skills/news-digest/scripts/main.py +133 -15
- package/skills/openhands-api/README.md +1 -1
- package/skills/openhands-api/SKILL.md +3 -3
- package/skills/openhands-api/scripts/openhands_api.py +13 -0
- package/skills/openhands-automation/SKILL.md +66 -4
- package/skills/openhands-automation/references/custom-automation.md +21 -7
- package/skills/openhands-sdk/SKILL.md +1 -0
- package/skills/research-brief/SKILL.md +5 -5
- package/skills/slack-standup-digest/SKILL.md +5 -5
- package/skills/ticket-to-code-change/SKILL.md +6 -3
- package/skills/use-jev/.claude-plugin/plugin.json +8 -0
- package/skills/use-jev/.codex-plugin/plugin.json +8 -0
- package/skills/use-jev/.plugin/plugin.json +8 -0
- package/skills/use-jev/README.md +66 -0
- package/skills/use-jev/SKILL.md +66 -0
- package/tests/fixtures/automations/github-pr-reviewer.json +39 -24
- package/tests/test_agent_conversation_dispatch.py +212 -0
- package/tests/test_apps_catalog.py +26 -0
- package/tests/test_automation_setup.py +44 -0
- package/tests/test_check_pr_description.py +89 -6
- package/tests/test_github_automation_foundation.py +158 -0
- package/tests/test_github_repo_monitor.py +107 -0
- package/tests/test_github_reviewer_delivery.py +2247 -6
- package/tests/test_github_stale_ci_pr_closer.py +56 -0
- package/tests/test_jira_issue_to_pr.py +312 -0
- package/tests/test_jira_setup_webhook.py +309 -0
- package/tests/test_openhands_api_python.py +37 -0
- package/tests/test_skill_plugin_loading.py +25 -15
- package/tests/test_sync_extensions.py +102 -0
- package/tests/test_vscode_app.py +33 -0
- package/tests/vscode-app.test.mjs +43 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "github-pr-reviewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"name": "GitHub code review",
|
|
5
5
|
"category": "Code review",
|
|
6
|
-
"description": "Review requested pull-request heads, record exact-head decisions, stop early on out-of-scope changes, and optionally request a code-aware human maintainer after approval or a maintainer-decision scope stop.",
|
|
6
|
+
"description": "Review requested pull-request heads, resume an outstanding reviewer request on a schedule once its checks are green, classify all open non-draft pull requests that nobody requested and review eligible green heads, drain the backlog oldest-first under a small per-scan new-conversation bound, record exact-head decisions, stop early on out-of-scope changes, and optionally request a code-aware human maintainer after approval or a maintainer-decision scope stop.",
|
|
7
7
|
"requires": {
|
|
8
8
|
"integrations": {},
|
|
9
9
|
"features": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"popularityRank": 100,
|
|
15
15
|
"estimatedSetupMinutes": 4,
|
|
16
|
-
"exampleImplementation": "A scheduled label
|
|
16
|
+
"exampleImplementation": "A scheduled scan of the trigger label, of open PRs (including drafts) holding an outstanding reviewer request, and of every open non-draft PR nobody requested whose current head may be green and unreviewed, or a GitHub reviewer-request event, selects a PR head and submits an idempotent subject turn. Each scheduled scan classifies the full backlog, while the global per-run quota limits only newly created review conversations; blocked and pending heads consume no launch slot, and explicit requests retain priority. The scheduled scan orders eligible heads with explicit requests first and then oldest-first, and starts at most a small configurable number of new conversations per scan across every repository. The profile-backed agent clones that exact head in its runtime, applies an early repository-ownership and product/architecture scope gate, then reuses the established review workflow, runs appropriate tests, and publishes a readable native review. A native approval, or a maintainer-decision scope stop, can then be handed to a code-aware human maintainer.",
|
|
17
17
|
"impact": {
|
|
18
18
|
"basis": "completed-runs",
|
|
19
19
|
"one": "1 PR review sweep completed",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"schedule": {
|
|
29
29
|
"type": "cron",
|
|
30
30
|
"label": "Check frequency",
|
|
31
|
-
"help": "How often to look for newly labelled pull requests.",
|
|
32
|
-
"default": "*/
|
|
31
|
+
"help": "How often to look for newly labelled pull requests, outstanding reviewer requests whose checks have not yet finished, and open non-draft pull requests nobody requested that still need a review.",
|
|
32
|
+
"default": "*/5 * * * *",
|
|
33
33
|
"required": true
|
|
34
34
|
},
|
|
35
35
|
"timezone": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"triggerLabel": {
|
|
69
69
|
"type": "text",
|
|
70
70
|
"label": "Trigger label",
|
|
71
|
-
"help": "
|
|
71
|
+
"help": "Pull requests carrying this label are reviewed. An open, non-draft pull request that nobody requested is also reviewed once its current head is green and carries no review yet, with the whole backlog classified each scan and only newly launched reviews counting against the configured quota.",
|
|
72
72
|
"default": "openhands-review",
|
|
73
73
|
"required": true,
|
|
74
74
|
"constraints": {
|
|
@@ -87,6 +87,16 @@
|
|
|
87
87
|
"maxLength": 39
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
|
+
"maxNewPerRun": {
|
|
91
|
+
"type": "number",
|
|
92
|
+
"label": "New reviews per scan",
|
|
93
|
+
"help": "Most new review conversations one scheduled scan may start, counted across every repository. Keeps a first scan over a large backlog from starting an agent for every eligible pull request at once. Reviews that are already running are resumed without counting against it.",
|
|
94
|
+
"default": 2,
|
|
95
|
+
"required": true,
|
|
96
|
+
"constraints": {
|
|
97
|
+
"min": 1
|
|
98
|
+
}
|
|
99
|
+
},
|
|
90
100
|
"reviewTone": {
|
|
91
101
|
"type": "select",
|
|
92
102
|
"label": "Review tone",
|
|
@@ -125,7 +135,7 @@
|
|
|
125
135
|
}
|
|
126
136
|
},
|
|
127
137
|
"bundle": {
|
|
128
|
-
"version": "1.
|
|
138
|
+
"version": "1.8.1",
|
|
129
139
|
"entrypoint": "python3 worker.py",
|
|
130
140
|
"timeout": 300,
|
|
131
141
|
"files": {
|
|
@@ -139,12 +149,13 @@
|
|
|
139
149
|
"repos": "{{form.repositories}}",
|
|
140
150
|
"trigger_label": "{{form.triggerLabel}}",
|
|
141
151
|
"trigger_reviewer": "{{form.triggerReviewer}}",
|
|
152
|
+
"max_new_per_run": "{{form.maxNewPerRun}}",
|
|
142
153
|
"review_tone": "{{form.reviewTone}}",
|
|
143
154
|
"maintainers": "{{form.maintainers}}",
|
|
144
155
|
"github_token_secret": "{{form.githubTokenSecret}}"
|
|
145
156
|
}
|
|
146
157
|
},
|
|
147
158
|
"filter": "contains({{form.repositories}}, repository.full_name) && contains(keys(@), 'pull_request') && ((action == 'review_requested' && requested_reviewer.login == '{{form.triggerReviewer}}') || (action == 'submitted' && contains(keys(@), 'review') && review.user.login == '{{form.triggerReviewer}}'))",
|
|
148
|
-
"message": "Configure the repositories, trigger, review tone, optional maintainer roster, credential name, and agent profile."
|
|
159
|
+
"message": "Configure the repositories, trigger, review tone, optional maintainer roster, credential name, per-scan new-review bound, and agent profile."
|
|
149
160
|
}
|
|
150
161
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github-stale-ci-pr-closer",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"name": "GitHub stale CI cleanup",
|
|
5
|
+
"category": "Code review",
|
|
6
|
+
"description": "Warn and close abandoned pull requests whose required CI remains failing.",
|
|
7
|
+
"requires": {
|
|
8
|
+
"integrations": {},
|
|
9
|
+
"features": [
|
|
10
|
+
"customTarball"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"estimatedSetupMinutes": 3,
|
|
14
|
+
"exampleImplementation": "A deterministic scheduled scan reads the required checks that apply to each pull request's base branch. It warns seven days after the newest required-check failure and closes seven days later only when the author has neither pushed nor commented.",
|
|
15
|
+
"setup": {
|
|
16
|
+
"version": "1.0",
|
|
17
|
+
"mode": "direct",
|
|
18
|
+
"form": {
|
|
19
|
+
"triggers": {
|
|
20
|
+
"cron": {
|
|
21
|
+
"schedule": {
|
|
22
|
+
"type": "cron",
|
|
23
|
+
"label": "Check frequency",
|
|
24
|
+
"help": "How often to reconcile failing pull requests.",
|
|
25
|
+
"default": "0 * * * *",
|
|
26
|
+
"required": true
|
|
27
|
+
},
|
|
28
|
+
"timezone": {
|
|
29
|
+
"type": "timezone",
|
|
30
|
+
"label": "Timezone",
|
|
31
|
+
"help": "Timezone for the schedule.",
|
|
32
|
+
"default": "UTC",
|
|
33
|
+
"required": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"args": {
|
|
38
|
+
"repositories": {
|
|
39
|
+
"type": "repo-picker",
|
|
40
|
+
"provider": "github",
|
|
41
|
+
"multiple": true,
|
|
42
|
+
"label": "Repositories",
|
|
43
|
+
"help": "Repositories whose open pull requests should be maintained.",
|
|
44
|
+
"required": true
|
|
45
|
+
},
|
|
46
|
+
"githubTokenSecret": {
|
|
47
|
+
"type": "text",
|
|
48
|
+
"label": "GitHub token secret",
|
|
49
|
+
"help": "Name of a saved GitHub token with rules and CI read access plus issue and pull-request write access.",
|
|
50
|
+
"default": "GITHUB_TOKEN_REVIEW",
|
|
51
|
+
"required": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"bundle": {
|
|
56
|
+
"version": "1.0.1",
|
|
57
|
+
"entrypoint": "python3 worker.py",
|
|
58
|
+
"timeout": 300,
|
|
59
|
+
"files": {
|
|
60
|
+
"worker.py": "skills/github-stale-ci-pr-closer/scripts/worker.py",
|
|
61
|
+
"github_client.py": "skills/github/scripts/github_client.py"
|
|
62
|
+
},
|
|
63
|
+
"config": {
|
|
64
|
+
"repos": "{{form.repositories}}",
|
|
65
|
+
"github_token_secret": "{{form.githubTokenSecret}}"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"message": "Configure the repositories, credential name, and schedule."
|
|
69
|
+
},
|
|
70
|
+
"popularityRank": 77
|
|
71
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"popularityRank": 78,
|
|
14
14
|
"estimatedSetupMinutes": 2,
|
|
15
|
-
"exampleImplementation": "Trigger: cron, daily by default (0 8 * * *)\nRequired secret: none. The feeds are public URLs and the conversation is started with an empty secret allow-list and no MCP servers.\n\n1. Read the feed list, the topics, and the schedule from setup.\n2. Key one unit of work to the UTC date, so a cron that fires more often, a retried run, or a restarted service cannot write the same digest twice.\n3. Fetch every feed over plain HTTPS and parse RSS 2.0, RSS 1.0/RDF and Atom by local element name. A feed that is down, moved, or no longer a feed is reported and skipped; the run fails only when every feed fails.\n4. Drop stories already covered by an earlier digest and stories older than the lookback window. Do not judge what a story is about: that has no right answer and is the agent's call.\n5. Start no conversation at all when nothing new was published, and leave the day open for a later run. A quiet day costs no tokens.\n6. Otherwise start an OpenHands conversation with the newest stories and the topics in the prompt, and let it decide which are relevant, group them, merge duplicate coverage, and write the digest.\n7. Deliver it three ways that need no credentials: it stays in the conversation, it is printed into the run log, and its opening is kept in state.\n8. Remember the reported stories only once a digest exists, so a failed run is recovered by the next one rather than lost.",
|
|
15
|
+
"exampleImplementation": "Trigger: cron, daily by default (0 8 * * *)\nRequired secret: none. The feeds are public URLs and, on a local Agent Canvas, the conversation is started with an empty secret allow-list and no MCP servers. On OpenHands Cloud and Enterprise it is started with the user's own settings.\n\n1. Read the feed list, the topics, and the schedule from setup.\n2. Key one unit of work to the UTC date, so a cron that fires more often, a retried run, or a restarted service cannot write the same digest twice.\n3. Fetch every feed over plain HTTPS and parse RSS 2.0, RSS 1.0/RDF and Atom by local element name. A feed that is down, moved, or no longer a feed is reported and skipped; the run fails only when every feed fails.\n4. Drop stories already covered by an earlier digest and stories older than the lookback window. Do not judge what a story is about: that has no right answer and is the agent's call.\n5. Start no conversation at all when nothing new was published, and leave the day open for a later run. A quiet day costs no tokens.\n6. Otherwise start an OpenHands conversation with the newest stories and the topics in the prompt, and let it decide which are relevant, group them, merge duplicate coverage, and write the digest.\n7. Deliver it three ways that need no credentials: it stays in the conversation, it is printed into the run log, and its opening is kept in state.\n8. Remember the reported stories only once a digest exists, so a failed run is recovered by the next one rather than lost.",
|
|
16
16
|
"impact": {
|
|
17
17
|
"basis": "completed-runs",
|
|
18
18
|
"one": "1 feed check completed",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"bundle": {
|
|
68
|
-
"version": "1.
|
|
68
|
+
"version": "1.1.0",
|
|
69
69
|
"entrypoint": "python3 main.py",
|
|
70
70
|
"timeout": 900,
|
|
71
71
|
"files": {
|
|
@@ -24,6 +24,7 @@ import entry18 from "./catalog/github-issue-triage/manifest.json" with { type: "
|
|
|
24
24
|
import entry19 from "./catalog/upstream-fork-sync/manifest.json" with { type: "json" };
|
|
25
25
|
import entry20 from "./catalog/incident-retrospective-drafter/manifest.json" with { type: "json" };
|
|
26
26
|
import entry21 from "./catalog/news-digest/manifest.json" with { type: "json" };
|
|
27
|
+
import entry22 from "./catalog/github-stale-ci-pr-closer/manifest.json" with { type: "json" };
|
|
27
28
|
|
|
28
29
|
export const AUTOMATION_CATALOG_ENTRIES = [
|
|
29
30
|
entry0,
|
|
@@ -48,4 +49,5 @@ export const AUTOMATION_CATALOG_ENTRIES = [
|
|
|
48
49
|
entry19,
|
|
49
50
|
entry20,
|
|
50
51
|
entry21,
|
|
52
|
+
entry22,
|
|
51
53
|
];
|
package/index.d.ts
CHANGED
|
@@ -44,3 +44,5 @@ export type {
|
|
|
44
44
|
} from "./automations/index.js";
|
|
45
45
|
export { SKILLS_CATALOG, DEFAULT_ENABLED_SKILL_NAMES } from "./skills/index.js";
|
|
46
46
|
export type { SkillCatalogEntry } from "./skills/index.js";
|
|
47
|
+
export { APPS_CATALOG } from "./apps/index.js";
|
|
48
|
+
export type { AppCatalogEntry } from "./apps/index.js";
|
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { INTEGRATION_CATALOG } from "./integrations/index.js";
|
|
2
2
|
export { AUTOMATION_CATALOG, AUTOMATION_INTERFACE } from "./automations/index.js";
|
|
3
3
|
export { SKILLS_CATALOG, DEFAULT_ENABLED_SKILL_NAMES } from "./skills/index.js";
|
|
4
|
+
export { APPS_CATALOG } from "./apps/index.js";
|
|
@@ -541,6 +541,20 @@
|
|
|
541
541
|
"semver"
|
|
542
542
|
]
|
|
543
543
|
},
|
|
544
|
+
{
|
|
545
|
+
"name": "runtime-api-configs",
|
|
546
|
+
"source": "./plugins/runtime-api-configs",
|
|
547
|
+
"description": "Manage warm sandbox pools on OpenHands Enterprise from a Python CLI: list, template, save, and delete warm runtime configurations against the runtime-api, and bootstrap credentials from Kubernetes secrets. Standard-library only, runs anywhere python3 is available.",
|
|
548
|
+
"category": "environment",
|
|
549
|
+
"keywords": [
|
|
550
|
+
"openhands-enterprise",
|
|
551
|
+
"runtime-api",
|
|
552
|
+
"warm-runtime-config",
|
|
553
|
+
"warm-pool",
|
|
554
|
+
"custom-sandbox-image",
|
|
555
|
+
"admin"
|
|
556
|
+
]
|
|
557
|
+
},
|
|
544
558
|
{
|
|
545
559
|
"name": "security",
|
|
546
560
|
"source": "./skills/security",
|
|
@@ -890,6 +904,22 @@
|
|
|
890
904
|
"source": "./skills/github-delivery-watchdog",
|
|
891
905
|
"description": "Periodically check pull requests and merge only current heads with independent review, tests, and passing CI.",
|
|
892
906
|
"category": "automations"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
"name": "github-stale-ci-pr-closer",
|
|
910
|
+
"source": "./skills/github-stale-ci-pr-closer",
|
|
911
|
+
"description": "Warn and close abandoned pull requests whose required CI remains failing.",
|
|
912
|
+
"category": "automations"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"name": "use-jev",
|
|
916
|
+
"source": "./skills/use-jev",
|
|
917
|
+
"description": "Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.",
|
|
918
|
+
"category": "integrations",
|
|
919
|
+
"keywords": [
|
|
920
|
+
"jev",
|
|
921
|
+
"classification"
|
|
922
|
+
]
|
|
893
923
|
}
|
|
894
924
|
]
|
|
895
925
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openhands/extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"build:skills": "node scripts/build-skills-catalog.mjs",
|
|
13
13
|
"build:integrations": "node scripts/build-integration-catalog.mjs",
|
|
14
14
|
"build:automations": "node scripts/build-automation-catalog.mjs",
|
|
15
|
+
"build:vscode-app": "node apps/vscode/scripts/package.mjs",
|
|
16
|
+
"test:vscode-app": "node tests/vscode-app.test.mjs",
|
|
15
17
|
"build": "npm run build:skills && npm run build:integrations && npm run build:automations"
|
|
16
18
|
},
|
|
17
19
|
"engines": {
|
|
@@ -51,6 +53,12 @@
|
|
|
51
53
|
"import": "./skills/index.js",
|
|
52
54
|
"default": "./skills/index.js"
|
|
53
55
|
},
|
|
56
|
+
"./apps": {
|
|
57
|
+
"types": "./apps/index.d.ts",
|
|
58
|
+
"import": "./apps/index.js",
|
|
59
|
+
"default": "./apps/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./apps/*": "./apps/*",
|
|
54
62
|
"./skills/*": "./skills/*",
|
|
55
63
|
"./plugins/*": "./plugins/*",
|
|
56
64
|
"./marketplaces/*.json": "./marketplaces/*.json",
|
|
@@ -22,7 +22,7 @@ The Python client prefers `OPENHANDS_CLOUD_API_KEY` and falls back to `OPENHANDS
|
|
|
22
22
|
|
|
23
23
|
## Delegating work with new Cloud conversations
|
|
24
24
|
|
|
25
|
-
Use `POST /api/v1/app-conversations` to create a separate OpenHands Cloud conversation for a self-contained task, then poll `GET /api/v1/app-conversations/start-tasks?ids=...` until you have an `app_conversation_id`.
|
|
25
|
+
Use `POST /api/v1/app-conversations` to create a separate OpenHands Cloud conversation for a self-contained task, then poll `GET /api/v1/app-conversations/start-tasks?ids=...` until you have an `app_conversation_id`. Share the Agent Canvas link from `OpenHandsAPI.app_conversation_ui_url(app_conversation_id)` (the conversation's `conversation_ui_url`, falling back to `<base_url>/canvas/conversations/<id>`).
|
|
26
26
|
|
|
27
27
|
Keep delegated prompts self-contained: include the repository, branch, relevant files, constraints, and expected output. Prefer five or fewer concurrently running delegated conversations.
|
|
28
28
|
|
|
@@ -177,7 +177,7 @@ When you start a delegated Cloud conversation:
|
|
|
177
177
|
3. Start the new conversation with `POST /api/v1/app-conversations`.
|
|
178
178
|
4. Poll the start-task until `status` is `READY` and you have an `app_conversation_id`.
|
|
179
179
|
5. Monitor the delegated conversation via `GET /api/v1/app-conversations?ids=...`.
|
|
180
|
-
6. Share or store the
|
|
180
|
+
6. Share or store the conversation's `conversation_ui_url` from that response. It opens the conversation in Agent Canvas. If the server does not return it, use `<base_url>/canvas/conversations/<app_conversation_id>`, where `<base_url>` is the server you called the API on. Never share `conversation_url`; it is the sandbox's agent-server API, not a page users can open.
|
|
181
181
|
|
|
182
182
|
### Minimal cURL flow
|
|
183
183
|
|
|
@@ -200,7 +200,7 @@ curl -s "https://app.all-hands.dev/api/v1/app-conversations/start-tasks?ids=${ST
|
|
|
200
200
|
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY}"
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
Then check execution status:
|
|
203
|
+
Then check execution status and get the link to share (`conversation_ui_url`):
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
206
|
curl -s "https://app.all-hands.dev/api/v1/app-conversations?ids=${APP_CONVERSATION_ID}" \
|
|
@@ -229,7 +229,7 @@ if not ready.get("app_conversation_id"):
|
|
|
229
229
|
ready = api.poll_start_task_until_ready(start["id"])
|
|
230
230
|
|
|
231
231
|
conversation_id = ready["app_conversation_id"]
|
|
232
|
-
print(f"Delegated conversation: {api.
|
|
232
|
+
print(f"Delegated conversation: {api.app_conversation_ui_url(conversation_id)}")
|
|
233
233
|
|
|
234
234
|
status = api.app_conversation_get(conversation_id)
|
|
235
235
|
print(status.get("sandbox_status"), status.get("execution_status"))
|
|
@@ -129,6 +129,19 @@ class OpenHandsAPI:
|
|
|
129
129
|
items = self.app_conversations_get_batch(ids=[conversation_id])
|
|
130
130
|
return items[0] if items else None
|
|
131
131
|
|
|
132
|
+
def app_conversation_ui_url(self, conversation_id: str) -> str:
|
|
133
|
+
"""Browser link that opens the conversation in Agent Canvas.
|
|
134
|
+
|
|
135
|
+
Share this link with users. `conversation_url` on the conversation record is
|
|
136
|
+
the sandbox's agent-server API, not a page a user can open.
|
|
137
|
+
"""
|
|
138
|
+
conversation = self.app_conversation_get(conversation_id) or {}
|
|
139
|
+
# Servers that predate `conversation_ui_url` serve Agent Canvas at /canvas.
|
|
140
|
+
return (
|
|
141
|
+
conversation.get("conversation_ui_url")
|
|
142
|
+
or f"{self.base_url}/canvas/conversations/{conversation_id}"
|
|
143
|
+
)
|
|
144
|
+
|
|
132
145
|
def sandboxes_search(self, *, limit: int = 20) -> dict[str, Any]:
|
|
133
146
|
limit = max(1, int(limit))
|
|
134
147
|
r = self._client.get(f"{self.api_v1_url}/sandboxes/search", params={"limit": limit})
|
|
@@ -47,7 +47,7 @@ The agent server typically runs inside a **sandbox** (a Docker or Kubernetes con
|
|
|
47
47
|
| Variable | Availability | Description |
|
|
48
48
|
|---|---|---|
|
|
49
49
|
| `RUNTIME_URL` | Ambient in cloud environments | Public-facing URL of the **agent server** sandbox. Use this to determine whether external webhook delivery is possible — if unset or local, webhooks cannot be received. The automation service may run at a separate URL (see Determining the API Host). |
|
|
50
|
-
| `AGENT_SERVER_URL` | Injected into scripts at run time only | Internal URL of the agent server. Available inside script execution context; **not** an ambient environment variable outside of a running script. |
|
|
50
|
+
| `AGENT_SERVER_URL` | Injected into scripts at run time, local Agent Canvas only | Internal URL of the agent server. Available inside script execution context; **not** an ambient environment variable outside of a running script, and not set on OpenHands Cloud or Enterprise. |
|
|
51
51
|
| `OPENHANDS_HOST` | Shell convention only — set manually | Base URL for the automation service API. **Not a real environment variable.** Set it from an explicit host, a detected local Agent Canvas server, or the cloud default. Used in all `curl` examples throughout this skill. |
|
|
52
52
|
|
|
53
53
|
> **⚠️ CRITICAL — Agent behavior rules:**
|
|
@@ -77,17 +77,22 @@ The agent server typically runs inside a **sandbox** (a Docker or Kubernetes con
|
|
|
77
77
|
|
|
78
78
|
### No-LLM Script Helpers
|
|
79
79
|
|
|
80
|
-
When building a deterministic custom script, these two stdlib-only functions are required. Copy them verbatim
|
|
80
|
+
When building a deterministic custom script, these two stdlib-only functions are required. Copy them verbatim - they use variables injected by the automation service: `SESSION_API_KEY`, plus `AGENT_SERVER_URL` on a local Agent Canvas or `OPENHANDS_CLOUD_API_URL` and `SANDBOX_ID` on OpenHands Cloud and Enterprise.
|
|
81
81
|
|
|
82
82
|
```python
|
|
83
83
|
import json, os, urllib.request
|
|
84
84
|
|
|
85
85
|
def get_secret(name):
|
|
86
|
-
"""Fetch a named secret
|
|
86
|
+
"""Fetch a named secret: from the agent server when AGENT_SERVER_URL is set, else from the OpenHands API."""
|
|
87
87
|
url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
|
|
88
88
|
key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
89
|
+
if url: # local Agent Canvas
|
|
90
|
+
url = f"{url}/api/settings/secrets/{name}"
|
|
91
|
+
else: # OpenHands Cloud / Enterprise
|
|
92
|
+
api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
|
|
93
|
+
url = f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}"
|
|
89
94
|
with urllib.request.urlopen(urllib.request.Request(
|
|
90
|
-
|
|
95
|
+
url, headers={"X-Session-API-Key": key}
|
|
91
96
|
)) as r:
|
|
92
97
|
return r.read().decode().strip()
|
|
93
98
|
|
|
@@ -498,6 +503,7 @@ For services other than GitHub (Linear, Stripe, Slack, etc.), register a custom
|
|
|
498
503
|
|
|
499
504
|
> **Agent behavior:**
|
|
500
505
|
> - **Always provide the curl request** to the user — do not attempt to register webhooks yourself.
|
|
506
|
+
> The exception is a service skill that ships a setup script for its webhook, such as `jira-issue-to-pr`: the script registers both sides and keeps the signing secret out of the conversation, so run it as that skill describes.
|
|
501
507
|
> - **Ask the user:** "Do you have a webhook signing secret from [service], or should the system generate one?"
|
|
502
508
|
> - If they have one → include `webhook_secret` in the request
|
|
503
509
|
> - If not → omit it; the response will contain a generated secret they must configure in their service
|
|
@@ -619,11 +625,67 @@ curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
|
619
625
|
}'
|
|
620
626
|
```
|
|
621
627
|
|
|
628
|
+
### Custom Webhook Example: Jira Cloud
|
|
629
|
+
|
|
630
|
+
Jira Cloud sends webhooks with:
|
|
631
|
+
- Signature header: `X-Hub-Signature` (`sha256=` followed by the hex HMAC-SHA256 of the body), sent only when the webhook has a secret
|
|
632
|
+
- Event type in payload: `webhookEvent` field (e.g., `jira:issue_created`, `jira:issue_updated`)
|
|
633
|
+
- Labels as plain strings: `issue.fields.labels` is `["create-pr"]`, not a list of objects, so `issue.fields.labels[].name` matches nothing
|
|
634
|
+
- On an update, a `changelog.items` list naming each changed `field`
|
|
635
|
+
|
|
636
|
+
Only a webhook a Jira admin registers (Jira settings → System → WebHooks, or the REST API) can carry a secret. The "Send web request" action of a Jira Automation rule cannot sign its request, so it cannot deliver to a custom webhook.
|
|
637
|
+
|
|
638
|
+
When the user can supply the API token of a Jira administrator, `scripts/setup_webhook.py` in the `jira-issue-to-pr` skill registers the webhook on both sides for them, replacing step 1 and the Jira configuration below. Done by hand:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
# 1. Register the Jira webhook
|
|
642
|
+
# - Use "X-Hub-Signature" as the signature header
|
|
643
|
+
# - Use "webhookEvent" to extract the event type from the payload
|
|
644
|
+
# - Omit webhook_secret to have one generated, or pass the one Jira generated
|
|
645
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1/webhooks" \
|
|
646
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
647
|
+
-H "Content-Type: application/json" \
|
|
648
|
+
-d '{
|
|
649
|
+
"name": "Jira Cloud",
|
|
650
|
+
"source": "jira",
|
|
651
|
+
"event_key_expr": "webhookEvent",
|
|
652
|
+
"signature_header": "X-Hub-Signature"
|
|
653
|
+
}'
|
|
654
|
+
|
|
655
|
+
# Response includes webhook_url and the generated webhook_secret - configure both in Jira:
|
|
656
|
+
# Jira settings → System → WebHooks → Create a WebHook → paste the webhook_url and the
|
|
657
|
+
# secret, then select the issue events. A JQL filter such as `labels = create-pr` keeps
|
|
658
|
+
# unrelated issues from being sent at all.
|
|
659
|
+
|
|
660
|
+
# 2. Create an automation for issues that receive a label
|
|
661
|
+
# The filter matches an issue created with the label and an update that changes the
|
|
662
|
+
# labels of an issue carrying it. Without the changelog check, every later edit of a
|
|
663
|
+
# labelled issue would start another run. It still fires when another label is added
|
|
664
|
+
# to or removed from an issue that carries the label, so have the prompt skip an
|
|
665
|
+
# issue that already has a pull request.
|
|
666
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
667
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
668
|
+
-H "Content-Type: application/json" \
|
|
669
|
+
-d '{
|
|
670
|
+
"name": "Jira Issues Labelled create-pr",
|
|
671
|
+
"prompt": "A Jira issue received the create-pr label. Read the issue from the event payload and open a pull request for it.",
|
|
672
|
+
"trigger": {
|
|
673
|
+
"type": "event",
|
|
674
|
+
"source": "jira",
|
|
675
|
+
"on": ["jira:issue_created", "jira:issue_updated"],
|
|
676
|
+
"filter": "contains(issue.fields.labels, '\''create-pr'\'') && (webhookEvent == '\''jira:issue_created'\'' || length(changelog.items[?field == '\''labels'\''] || `[]`) > `0`)"
|
|
677
|
+
}
|
|
678
|
+
}'
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
An unsigned request to the webhook URL answers `401 Missing signature header: X-Hub-Signature`; that is the webhook working, not a fault. `404 Unknown webhook source` means no webhook with that source is registered for the organization in the URL.
|
|
682
|
+
|
|
622
683
|
### Common Signature Headers by Service
|
|
623
684
|
|
|
624
685
|
| Service | Signature Header | Event Key Expression |
|
|
625
686
|
|---------|-----------------|---------------------|
|
|
626
687
|
| Linear | `Linear-Signature` | `type` |
|
|
688
|
+
| Jira Cloud | `X-Hub-Signature` | `webhookEvent` |
|
|
627
689
|
| Stripe | `Stripe-Signature` | `type` |
|
|
628
690
|
| Slack | `X-Slack-Signature` | `type` |
|
|
629
691
|
| Twilio | `X-Twilio-Signature` | `type` |
|
|
@@ -223,7 +223,7 @@ Key points:
|
|
|
223
223
|
- **Your script and its conversations share the same agent server.** There is no network hop to a remote service.
|
|
224
224
|
- **Conversations are asynchronous.** You can fire one and continue, fire several concurrently, or start none at all (e.g. if your script fetches external data and decides no action is needed).
|
|
225
225
|
- **The completion callback** is sent by `OpenHandsCloudWorkspace.__exit__` when the `with` block exits, telling the automation service the run is done. For async patterns, defer exiting until the conversation is in the desired state.
|
|
226
|
-
- **Secrets**
|
|
226
|
+
- **Secrets** are fetched over REST with `X-Session-API-Key: {SESSION_API_KEY}`. When `AGENT_SERVER_URL` is set (local Agent Canvas): `GET {AGENT_SERVER_URL}/api/settings/secrets/{name}`. Otherwise (OpenHands Cloud and Enterprise): `GET {OPENHANDS_CLOUD_API_URL}/api/v1/sandboxes/{SANDBOX_ID}/settings/secrets/{name}`. SDK scripts can also call `workspace.get_llm()` to get the configured LLM.
|
|
227
227
|
|
|
228
228
|
**SDK Documentation:** https://docs.openhands.dev/sdk
|
|
229
229
|
|
|
@@ -421,7 +421,7 @@ with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url
|
|
|
421
421
|
|
|
422
422
|
For tasks that don't need AI reasoning — sending a Slack message, calling an API, rotating from a fixed list — skip the SDK entirely. Use pure Python stdlib with `python3 main.py` as the entrypoint and no `setup.sh`.
|
|
423
423
|
|
|
424
|
-
**Accessing secrets** — custom secrets are not injected into the subprocess environment automatically. Fetch them
|
|
424
|
+
**Accessing secrets** — custom secrets are not injected into the subprocess environment automatically. Fetch them over REST (from the agent server when `AGENT_SERVER_URL` is set, otherwise from the OpenHands API):
|
|
425
425
|
|
|
426
426
|
```python
|
|
427
427
|
import os, urllib.request
|
|
@@ -429,8 +429,15 @@ import os, urllib.request
|
|
|
429
429
|
def get_secret(name: str) -> str:
|
|
430
430
|
url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
|
|
431
431
|
key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
432
|
+
if url:
|
|
433
|
+
# Local Agent Canvas: secrets live in the agent server.
|
|
434
|
+
url = f"{url}/api/settings/secrets/{name}"
|
|
435
|
+
else:
|
|
436
|
+
# OpenHands Cloud / Enterprise: secrets live in the OpenHands API.
|
|
437
|
+
api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
|
|
438
|
+
url = f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}"
|
|
432
439
|
req = urllib.request.Request(
|
|
433
|
-
|
|
440
|
+
url,
|
|
434
441
|
headers={"X-Session-API-Key": key},
|
|
435
442
|
)
|
|
436
443
|
with urllib.request.urlopen(req) as r:
|
|
@@ -627,11 +634,13 @@ def save_state(state: dict) -> None:
|
|
|
627
634
|
|
|
628
635
|
## Environment Variables
|
|
629
636
|
|
|
630
|
-
The automation service injects these environment variables into every run:
|
|
637
|
+
The automation service injects these environment variables into every run, except where a row says otherwise:
|
|
631
638
|
|
|
632
639
|
| Variable | Alt name | Description |
|
|
633
640
|
|----------|----------|-------------|
|
|
634
|
-
| `AGENT_SERVER_URL` | — | Agent server URL. Used as `cloud_api_url` for `OpenHandsCloudWorkspace`, and as the base URL for secret lookups |
|
|
641
|
+
| `AGENT_SERVER_URL` | — | Agent server URL. Local Agent Canvas only; not set on OpenHands Cloud or Enterprise. Used as `cloud_api_url` for `OpenHandsCloudWorkspace`, and as the base URL for secret lookups |
|
|
642
|
+
| `OPENHANDS_CLOUD_API_URL` | — | OpenHands API base URL. OpenHands Cloud and Enterprise only. Base URL for secret lookups |
|
|
643
|
+
| `SANDBOX_ID` | — | ID of the sandbox the run executes in. OpenHands Cloud and Enterprise only. Part of the secret lookup URL |
|
|
635
644
|
| `OH_SESSION_API_KEYS_0` | `SESSION_API_KEY` | Session API key. Used as `cloud_api_key` for `OpenHandsCloudWorkspace`, and as `X-Session-API-Key` for REST API calls |
|
|
636
645
|
| `AUTOMATION_CALLBACK_URL` | — | POST here to mark the run complete (done automatically by `OpenHandsCloudWorkspace.__exit__`, or manually in no-LLM scripts) |
|
|
637
646
|
| `AUTOMATION_CALLBACK_API_KEY` | — | Bearer token for the completion callback POST |
|
|
@@ -673,7 +682,12 @@ CHANNEL = "C12345678"
|
|
|
673
682
|
def get_secret(name):
|
|
674
683
|
url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
|
|
675
684
|
key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
676
|
-
|
|
685
|
+
if url: # local Agent Canvas
|
|
686
|
+
url = f"{url}/api/settings/secrets/{name}"
|
|
687
|
+
else: # OpenHands Cloud / Enterprise
|
|
688
|
+
api = os.environ["OPENHANDS_CLOUD_API_URL"].rstrip("/")
|
|
689
|
+
url = f"{api}/api/v1/sandboxes/{os.environ['SANDBOX_ID']}/settings/secrets/{name}"
|
|
690
|
+
req = urllib.request.Request(url,
|
|
677
691
|
headers={"X-Session-API-Key": key})
|
|
678
692
|
with urllib.request.urlopen(req) as r:
|
|
679
693
|
return r.read().decode().strip()
|
|
@@ -823,7 +837,7 @@ The upload limit is 1MB. Reduce your tarball size by:
|
|
|
823
837
|
### Run fails instantly with `error_detail: null`
|
|
824
838
|
The script sent `fire_callback("FAILED")` immediately — before doing meaningful work. Common causes:
|
|
825
839
|
- A required secret was empty: the `get_secret()` call failed or returned nothing
|
|
826
|
-
- A missing/wrong `
|
|
840
|
+
- A missing/wrong `SESSION_API_KEY`, or a missing `OPENHANDS_CLOUD_API_URL` / `SANDBOX_ID` (`AGENT_SERVER_URL` on a local Agent Canvas)
|
|
827
841
|
- An import error in the entrypoint
|
|
828
842
|
|
|
829
843
|
Add `"error": str(exc)` to your `fire_callback("FAILED", ...)` call so `error_detail` is populated.
|
|
@@ -201,6 +201,7 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
|
|
|
201
201
|
- [`56_structured_output.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/56_structured_output.py)
|
|
202
202
|
- [`57_prompt_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/57_prompt_hooks)
|
|
203
203
|
- [`58_ask_oracle_tool`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/58_ask_oracle_tool)
|
|
204
|
+
- [`59_route_task_to_model.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/59_route_task_to_model.py)
|
|
204
205
|
|
|
205
206
|
### [`02_remote_agent_server/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server)
|
|
206
207
|
|
|
@@ -57,12 +57,22 @@ gh api -X POST repos/{owner}/{repo}/pulls/{pr_number}/reviews --input /tmp/revie
|
|
|
57
57
|
| Parameter | Description |
|
|
58
58
|
|-----------|-------------|
|
|
59
59
|
| `commit_id` | Commit SHA to comment on (use `git rev-parse HEAD`) |
|
|
60
|
-
| `event` | `
|
|
60
|
+
| `event` | `APPROVE`, `COMMENT`, or `REQUEST_CHANGES` (see below) |
|
|
61
61
|
| `path` | File path as shown in the diff |
|
|
62
62
|
| `line` | Line number in the NEW version (right side of diff) |
|
|
63
63
|
| `side` | `RIGHT` for new/added lines, `LEFT` for deleted lines |
|
|
64
64
|
| `body` | Comment text with priority label |
|
|
65
65
|
|
|
66
|
+
### Choosing the `event` Value
|
|
67
|
+
|
|
68
|
+
| Event | When to use |
|
|
69
|
+
|-------|-------------|
|
|
70
|
+
| `APPROVE` | No issues found, or only 🟡 suggestions that are non-blocking |
|
|
71
|
+
| `COMMENT` | You have 🟠 Important feedback but it's not a hard blocker |
|
|
72
|
+
| `REQUEST_CHANGES` | There are 🔴 Critical issues that **must** be fixed before merge |
|
|
73
|
+
|
|
74
|
+
**Default to `APPROVE`** when the code is correct and merge-ready. Use `COMMENT` or `REQUEST_CHANGES` only when there are actionable issues that warrant it.
|
|
75
|
+
|
|
66
76
|
### Multi-Line Comments
|
|
67
77
|
|
|
68
78
|
For comments spanning multiple lines, add `start_line` to specify the range:
|
|
@@ -184,4 +194,4 @@ curl -X POST \
|
|
|
184
194
|
5. Do NOT post comments for code that is acceptable — only comment when action is needed
|
|
185
195
|
6. Use suggestion syntax for concrete code changes, but only after verifying the resulting code matches your description (see "How Suggestions Actually Work")
|
|
186
196
|
7. Keep the review body brief (details go in inline comments)
|
|
187
|
-
8. If no issues:
|
|
197
|
+
8. If no issues: use `"event": "APPROVE"` with a short approval message and no inline comments
|
|
@@ -57,12 +57,22 @@ gh api -X POST repos/{owner}/{repo}/pulls/{pr_number}/reviews --input /tmp/revie
|
|
|
57
57
|
| Parameter | Description |
|
|
58
58
|
|-----------|-------------|
|
|
59
59
|
| `commit_id` | Commit SHA to comment on (use `git rev-parse HEAD`) |
|
|
60
|
-
| `event` | `
|
|
60
|
+
| `event` | `APPROVE`, `COMMENT`, or `REQUEST_CHANGES` (see below) |
|
|
61
61
|
| `path` | File path as shown in the diff |
|
|
62
62
|
| `line` | Line number in the NEW version (right side of diff) |
|
|
63
63
|
| `side` | `RIGHT` for new/added lines, `LEFT` for deleted lines |
|
|
64
64
|
| `body` | Comment text with priority label |
|
|
65
65
|
|
|
66
|
+
### Choosing the `event` Value
|
|
67
|
+
|
|
68
|
+
| Event | When to use |
|
|
69
|
+
|-------|-------------|
|
|
70
|
+
| `APPROVE` | No issues found, or only 🟡 suggestions that are non-blocking |
|
|
71
|
+
| `COMMENT` | You have 🟠 Important feedback but it's not a hard blocker |
|
|
72
|
+
| `REQUEST_CHANGES` | There are 🔴 Critical issues that **must** be fixed before merge |
|
|
73
|
+
|
|
74
|
+
**Default to `APPROVE`** when the code is correct and merge-ready. Use `COMMENT` or `REQUEST_CHANGES` only when there are actionable issues that warrant it.
|
|
75
|
+
|
|
66
76
|
### Multi-Line Comments
|
|
67
77
|
|
|
68
78
|
For comments spanning multiple lines, add `start_line` to specify the range:
|
|
@@ -184,4 +194,4 @@ curl -X POST \
|
|
|
184
194
|
5. Do NOT post comments for code that is acceptable — only comment when action is needed
|
|
185
195
|
6. Use suggestion syntax for concrete code changes, but only after verifying the resulting code matches your description (see "How Suggestions Actually Work")
|
|
186
196
|
7. Keep the review body brief (details go in inline comments)
|
|
187
|
-
8. If no issues:
|
|
197
|
+
8. If no issues: use `"event": "APPROVE"` with a short approval message and no inline comments
|