@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
|
@@ -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
|
|
|
@@ -71,14 +71,14 @@ Construct a prompt that includes:
|
|
|
71
71
|
|
|
72
72
|
### Step 4 — Create the automation
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
74
|
+
Set `OPENHANDS_HOST` and `AUTH_HEADER` for the request below:
|
|
75
|
+
- **OpenHands Cloud or Enterprise** - a `<HOST>` value is in your system prompt: use it as `OPENHANDS_HOST`, with `AUTH_HEADER="Authorization: Bearer $OPENHANDS_API_KEY"`
|
|
76
|
+
- **Local Agent Canvas** - an **Automation backend** is listed in `<RUNTIME_SERVICES>`: use its `url_from_agent` as `OPENHANDS_HOST`, with `AUTH_HEADER="X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY"`
|
|
77
77
|
|
|
78
78
|
Use the **prompt preset** endpoint:
|
|
79
79
|
```bash
|
|
80
80
|
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
81
|
-
-H "
|
|
81
|
+
-H "$AUTH_HEADER" \
|
|
82
82
|
-H "Content-Type: application/json" \
|
|
83
83
|
-d '{
|
|
84
84
|
"name": "Research Brief Writer",
|
|
@@ -87,7 +87,7 @@ curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
|
87
87
|
}'
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
PowerShell note: use `curl.exe` for this exact flag syntax, and replace `${OPENHANDS_HOST}` / `$
|
|
90
|
+
PowerShell note: use `curl.exe` for this exact flag syntax, and replace `${OPENHANDS_HOST}` / `$AUTH_HEADER` with `$env:OPENHANDS_HOST` / `$env:AUTH_HEADER` if running it natively.
|
|
91
91
|
|
|
92
92
|
### Step 5 — Confirm
|
|
93
93
|
|
|
@@ -64,14 +64,14 @@ Construct a prompt that includes:
|
|
|
64
64
|
|
|
65
65
|
### Step 4 — Create the automation
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
67
|
+
Set `OPENHANDS_HOST` and `AUTH_HEADER` for the request below:
|
|
68
|
+
- **OpenHands Cloud or Enterprise** - a `<HOST>` value is in your system prompt: use it as `OPENHANDS_HOST`, with `AUTH_HEADER="Authorization: Bearer $OPENHANDS_API_KEY"`
|
|
69
|
+
- **Local Agent Canvas** - an **Automation backend** is listed in `<RUNTIME_SERVICES>`: use its `url_from_agent` as `OPENHANDS_HOST`, with `AUTH_HEADER="X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY"`
|
|
70
70
|
|
|
71
71
|
Use the **prompt preset** endpoint:
|
|
72
72
|
```bash
|
|
73
73
|
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
74
|
-
-H "
|
|
74
|
+
-H "$AUTH_HEADER" \
|
|
75
75
|
-H "Content-Type: application/json" \
|
|
76
76
|
-d '{
|
|
77
77
|
"name": "Slack Standup Digest",
|
|
@@ -80,7 +80,7 @@ curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
|
|
|
80
80
|
}'
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
PowerShell note: use `curl.exe` for this exact flag syntax, and replace `${OPENHANDS_HOST}` / `$
|
|
83
|
+
PowerShell note: use `curl.exe` for this exact flag syntax, and replace `${OPENHANDS_HOST}` / `$AUTH_HEADER` with `$env:OPENHANDS_HOST` / `$env:AUTH_HEADER` if running it natively.
|
|
84
84
|
|
|
85
85
|
### Step 5 — Confirm
|
|
86
86
|
|
|
@@ -40,9 +40,12 @@ Ask the user for:
|
|
|
40
40
|
dependency status, repository, base branch, and provider-specific request
|
|
41
41
|
terminology. Require the agent to run the repository's tests before opening
|
|
42
42
|
the pull request or merge request.
|
|
43
|
-
5. Create the automation through the Automation backend
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
5. Create the automation through the Automation backend's prompt-preset
|
|
44
|
+
endpoint. On OpenHands Cloud or Enterprise the backend is the `<HOST>` value
|
|
45
|
+
in your system prompt, authenticated with
|
|
46
|
+
`Authorization: Bearer $OPENHANDS_API_KEY`. On a local Agent Canvas it is the
|
|
47
|
+
Automation backend described in `<RUNTIME_SERVICES>`, authenticated with the
|
|
48
|
+
runtime-provided automation API key.
|
|
46
49
|
6. Configure the automation to post the OpenHands conversation URL immediately
|
|
47
50
|
and the resulting pull-request or merge-request URL back to the ticket.
|
|
48
51
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-jev
|
|
3
|
+
description: Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.
|
|
4
|
+
triggers:
|
|
5
|
+
- jev
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[Jev](https://typesafe.ai) is a newly released general classifier.
|
|
9
|
+
Give it text or application state and typed questions; it returns decisions and
|
|
10
|
+
probabilities your code can use for routing, ranking, extraction, or verification.
|
|
11
|
+
|
|
12
|
+
Read the upstream [llms.txt](https://docs.typesafe.ai/llms.txt) to find current
|
|
13
|
+
documentation, then follow the pages relevant to the task:
|
|
14
|
+
|
|
15
|
+
- [Use cases](https://docs.typesafe.ai/concepts/use-case-map.md): explore applications;
|
|
16
|
+
see worked examples for [function calling](https://docs.typesafe.ai/cookbooks/function_calling.md),
|
|
17
|
+
[reranking](https://docs.typesafe.ai/cookbooks/rerank_typesafe.md), and
|
|
18
|
+
[citation checks](https://docs.typesafe.ai/cookbooks/citation_check.md).
|
|
19
|
+
- [Best practices](https://docs.typesafe.ai/concepts/how-to-build-with-system-one.md):
|
|
20
|
+
provide relevant state, ask focused questions, define clear criteria, and batch
|
|
21
|
+
independent questions. Keep workflow logic in code.
|
|
22
|
+
- [Confidence](https://docs.typesafe.ai/confidence.md): use uncertainty to decide
|
|
23
|
+
when to escalate, and validate thresholds on representative data.
|
|
24
|
+
- [Python SDK](https://docs.typesafe.ai/sdk/python.md),
|
|
25
|
+
[JavaScript SDK](https://docs.typesafe.ai/sdk/javascript.md), or
|
|
26
|
+
[HTTP API](https://docs.typesafe.ai/api.md): read before integrating.
|
|
27
|
+
|
|
28
|
+
## Choice, Score, and Noul
|
|
29
|
+
|
|
30
|
+
| Primitive | Example use | Result |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| [Choice](https://docs.typesafe.ai/primitives/choice.md) | Route a support request to a team | One option, option probabilities, and confidence |
|
|
33
|
+
| [Score](https://docs.typesafe.ai/primitives/score.md) | Rate a bug's impact | Probability-weighted position on ordered levels, plus confidence |
|
|
34
|
+
| [Noul](https://docs.typesafe.ai/primitives/noul.md) | Check whether a refund is requested | Probability of yes; 0.5 means uncertainty, not medium intensity |
|
|
35
|
+
|
|
36
|
+
Install `typesafe-sdk` and check that `JEV_API_KEY` is set in the environment,
|
|
37
|
+
otherwise ask the user to provide it.
|
|
38
|
+
This Python example asks one question of each type in a single call:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import os
|
|
42
|
+
|
|
43
|
+
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
|
|
44
|
+
|
|
45
|
+
with TypeSafeClient(api_key=os.environ["JEV_API_KEY"]) as client:
|
|
46
|
+
result = client.system_one(
|
|
47
|
+
state={"ticket": "PDF export fails, but CSV works. Please refund this month."},
|
|
48
|
+
questions={
|
|
49
|
+
"team": Choice(
|
|
50
|
+
instructions="Which team should handle the main issue in the ticket?",
|
|
51
|
+
criteria={"billing": "Charges and payments",
|
|
52
|
+
"technical": "Broken product features", "other": "Neither"},
|
|
53
|
+
),
|
|
54
|
+
"impact": Score(
|
|
55
|
+
instructions="How much does the reported bug affect functionality?",
|
|
56
|
+
criteria=["Cosmetic only", "Feature broken; workaround available",
|
|
57
|
+
"Work blocked; no workaround available"],
|
|
58
|
+
),
|
|
59
|
+
"refund": Noul(instructions="Does the ticket explicitly request a refund?"),
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
print(result.choices["team"].choice)
|
|
64
|
+
print(result.scores["impact"].score)
|
|
65
|
+
print(result.nouls["refund"].noul)
|
|
66
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-jev
|
|
3
|
+
description: Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.
|
|
4
|
+
triggers:
|
|
5
|
+
- jev
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[Jev](https://typesafe.ai) is a newly released general classifier.
|
|
9
|
+
Give it text or application state and typed questions; it returns decisions and
|
|
10
|
+
probabilities your code can use for routing, ranking, extraction, or verification.
|
|
11
|
+
|
|
12
|
+
Read the upstream [llms.txt](https://docs.typesafe.ai/llms.txt) to find current
|
|
13
|
+
documentation, then follow the pages relevant to the task:
|
|
14
|
+
|
|
15
|
+
- [Use cases](https://docs.typesafe.ai/concepts/use-case-map.md): explore applications;
|
|
16
|
+
see worked examples for [function calling](https://docs.typesafe.ai/cookbooks/function_calling.md),
|
|
17
|
+
[reranking](https://docs.typesafe.ai/cookbooks/rerank_typesafe.md), and
|
|
18
|
+
[citation checks](https://docs.typesafe.ai/cookbooks/citation_check.md).
|
|
19
|
+
- [Best practices](https://docs.typesafe.ai/concepts/how-to-build-with-system-one.md):
|
|
20
|
+
provide relevant state, ask focused questions, define clear criteria, and batch
|
|
21
|
+
independent questions. Keep workflow logic in code.
|
|
22
|
+
- [Confidence](https://docs.typesafe.ai/confidence.md): use uncertainty to decide
|
|
23
|
+
when to escalate, and validate thresholds on representative data.
|
|
24
|
+
- [Python SDK](https://docs.typesafe.ai/sdk/python.md),
|
|
25
|
+
[JavaScript SDK](https://docs.typesafe.ai/sdk/javascript.md), or
|
|
26
|
+
[HTTP API](https://docs.typesafe.ai/api.md): read before integrating.
|
|
27
|
+
|
|
28
|
+
## Choice, Score, and Noul
|
|
29
|
+
|
|
30
|
+
| Primitive | Example use | Result |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| [Choice](https://docs.typesafe.ai/primitives/choice.md) | Route a support request to a team | One option, option probabilities, and confidence |
|
|
33
|
+
| [Score](https://docs.typesafe.ai/primitives/score.md) | Rate a bug's impact | Probability-weighted position on ordered levels, plus confidence |
|
|
34
|
+
| [Noul](https://docs.typesafe.ai/primitives/noul.md) | Check whether a refund is requested | Probability of yes; 0.5 means uncertainty, not medium intensity |
|
|
35
|
+
|
|
36
|
+
Install `typesafe-sdk` and check that `JEV_API_KEY` is set in the environment,
|
|
37
|
+
otherwise ask the user to provide it.
|
|
38
|
+
This Python example asks one question of each type in a single call:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import os
|
|
42
|
+
|
|
43
|
+
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
|
|
44
|
+
|
|
45
|
+
with TypeSafeClient(api_key=os.environ["JEV_API_KEY"]) as client:
|
|
46
|
+
result = client.system_one(
|
|
47
|
+
state={"ticket": "PDF export fails, but CSV works. Please refund this month."},
|
|
48
|
+
questions={
|
|
49
|
+
"team": Choice(
|
|
50
|
+
instructions="Which team should handle the main issue in the ticket?",
|
|
51
|
+
criteria={"billing": "Charges and payments",
|
|
52
|
+
"technical": "Broken product features", "other": "Neither"},
|
|
53
|
+
),
|
|
54
|
+
"impact": Score(
|
|
55
|
+
instructions="How much does the reported bug affect functionality?",
|
|
56
|
+
criteria=["Cosmetic only", "Feature broken; workaround available",
|
|
57
|
+
"Work blocked; no workaround available"],
|
|
58
|
+
),
|
|
59
|
+
"refund": Noul(instructions="Does the ticket explicitly request a refund?"),
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
print(result.choices["team"].choice)
|
|
64
|
+
print(result.scores["impact"].score)
|
|
65
|
+
print(result.nouls["refund"].noul)
|
|
66
|
+
```
|