@openhands/extensions 0.25.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.
Files changed (118) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.codex-plugin/marketplace.json +24 -0
  3. package/.github/pull_request_template.md +17 -2
  4. package/.github/scripts/check_pr_description.py +64 -5
  5. package/.github/scripts/markdown_sections.py +95 -0
  6. package/.github/workflows/build-vscode-app.yml +30 -0
  7. package/.github/workflows/check-extensions.yml +3 -0
  8. package/.github/workflows/tests.yml +26 -0
  9. package/.plugin/marketplace.json +24 -0
  10. package/.release-please-manifest.json +1 -1
  11. package/AGENTS.md +3 -2
  12. package/README.md +9 -3
  13. package/apps/README.md +20 -0
  14. package/apps/catalog.json +10 -0
  15. package/apps/index.d.ts +11 -0
  16. package/apps/index.js +5 -0
  17. package/apps/vscode/README.md +13 -0
  18. package/apps/vscode/canvas-extension.json +19 -0
  19. package/apps/vscode/extension.js +114 -0
  20. package/apps/vscode/scripts/package.mjs +84 -0
  21. package/automations/bundle-index.js +15 -15
  22. package/automations/catalog/github-agents-md-maintainer/manifest.json +1 -1
  23. package/automations/catalog/github-pr-reviewer/manifest.json +5 -5
  24. package/automations/catalog/github-stale-ci-pr-closer/manifest.json +3 -3
  25. package/automations/catalog/gitlab-issue-to-mr/manifest.json +1 -1
  26. package/automations/catalog/news-digest/manifest.json +2 -2
  27. package/index.d.ts +2 -0
  28. package/index.js +1 -0
  29. package/marketplaces/openhands-extensions.json +24 -0
  30. package/package.json +9 -1
  31. package/plugins/openhands/skills/openhands-api/README.md +1 -1
  32. package/plugins/openhands/skills/openhands-api/SKILL.md +3 -3
  33. package/plugins/openhands/skills/openhands-api/scripts/openhands_api.py +13 -0
  34. package/plugins/openhands/skills/openhands-automation/SKILL.md +66 -4
  35. package/plugins/openhands/skills/openhands-automation/references/custom-automation.md +21 -7
  36. package/plugins/runtime-api-configs/.claude-plugin/plugin.json +20 -0
  37. package/plugins/runtime-api-configs/.codex-plugin/plugin.json +20 -0
  38. package/plugins/runtime-api-configs/.plugin/plugin.json +20 -0
  39. package/plugins/runtime-api-configs/README.md +59 -0
  40. package/plugins/runtime-api-configs/SKILL.md +240 -0
  41. package/plugins/runtime-api-configs/scripts/warm_runtime_configs.py +331 -0
  42. package/plugins/runtime-api-configs/tests/test_warm_runtime_configs.py +338 -0
  43. package/plugins/vulnerability-remediation/skills/github/scripts/agent_conversation.py +223 -43
  44. package/plugins/vulnerability-remediation/skills/github/scripts/github_client.py +67 -15
  45. package/pyproject.toml +1 -1
  46. package/python/openhands_extensions/_version.py +1 -1
  47. package/scripts/check_plugin_resources.py +116 -0
  48. package/scripts/sync_extensions.py +32 -5
  49. package/skills/canvas-extension-api/SKILL.md +34 -3
  50. package/skills/canvas-extension-api/references/backend-safety.md +14 -0
  51. package/skills/canvas-extension-api/references/connections.md +24 -0
  52. package/skills/canvas-extension-api/references/sidecar-pattern.md +49 -10
  53. package/skills/canvas-extension-api/references/v1-contract.md +53 -3
  54. package/skills/canvas-extension-api/scripts/validate-extension.mjs +52 -0
  55. package/skills/canvas-extension-api/scripts/validate-extension.test.mjs +192 -1
  56. package/skills/github/scripts/agent_conversation.py +223 -43
  57. package/skills/github/scripts/github_client.py +67 -15
  58. package/skills/github-agents-md-maintainer/README.md +7 -0
  59. package/skills/github-agents-md-maintainer/SKILL.md +13 -6
  60. package/skills/github-agents-md-maintainer/scripts/github_client.py +67 -15
  61. package/skills/github-agents-md-maintainer/scripts/main.py +199 -29
  62. package/skills/github-delivery-watchdog/scripts/github_client.py +67 -15
  63. package/skills/github-issue-to-pr/scripts/agent_conversation.py +223 -43
  64. package/skills/github-issue-to-pr/scripts/github_client.py +67 -15
  65. package/skills/github-issue-to-pr/scripts/worker.py +10 -1
  66. package/skills/github-issue-triage/scripts/agent_conversation.py +223 -43
  67. package/skills/github-issue-triage/scripts/github_client.py +67 -15
  68. package/skills/github-pr-reviewer/README.md +5 -4
  69. package/skills/github-pr-reviewer/SKILL.md +8 -17
  70. package/skills/github-pr-reviewer/references/state-schema.md +0 -15
  71. package/skills/github-pr-reviewer/scripts/agent_conversation.py +223 -43
  72. package/skills/github-pr-reviewer/scripts/github_client.py +67 -15
  73. package/skills/github-pr-reviewer/scripts/worker.py +131 -108
  74. package/skills/github-repo-monitor/scripts/github_client.py +67 -15
  75. package/skills/github-stale-ci-pr-closer/scripts/worker.py +223 -50
  76. package/skills/github-stale-ci-pr-closer/tests/test_worker.py +155 -15
  77. package/skills/gitlab-issue-to-mr/README.md +12 -0
  78. package/skills/gitlab-issue-to-mr/SKILL.md +13 -6
  79. package/skills/gitlab-issue-to-mr/scripts/main.py +217 -42
  80. package/skills/incident-retrospective/SKILL.md +5 -5
  81. package/skills/index.js +23 -14
  82. package/skills/jira-issue-to-pr/README.md +12 -4
  83. package/skills/jira-issue-to-pr/SKILL.md +129 -18
  84. package/skills/jira-issue-to-pr/references/setup.md +8 -0
  85. package/skills/jira-issue-to-pr/scripts/main.py +181 -54
  86. package/skills/jira-issue-to-pr/scripts/setup_webhook.py +290 -0
  87. package/skills/linear-triage/SKILL.md +5 -5
  88. package/skills/news-digest/README.md +6 -0
  89. package/skills/news-digest/SKILL.md +19 -9
  90. package/skills/news-digest/commands/news-digest-setup.md +1 -1
  91. package/skills/news-digest/scripts/main.py +133 -15
  92. package/skills/openhands-api/README.md +1 -1
  93. package/skills/openhands-api/SKILL.md +3 -3
  94. package/skills/openhands-api/scripts/openhands_api.py +13 -0
  95. package/skills/openhands-automation/SKILL.md +66 -4
  96. package/skills/openhands-automation/references/custom-automation.md +21 -7
  97. package/skills/research-brief/SKILL.md +5 -5
  98. package/skills/slack-standup-digest/SKILL.md +5 -5
  99. package/skills/ticket-to-code-change/SKILL.md +6 -3
  100. package/skills/use-jev/.claude-plugin/plugin.json +8 -0
  101. package/skills/use-jev/.codex-plugin/plugin.json +8 -0
  102. package/skills/use-jev/.plugin/plugin.json +8 -0
  103. package/skills/use-jev/README.md +66 -0
  104. package/skills/use-jev/SKILL.md +66 -0
  105. package/tests/fixtures/automations/github-pr-reviewer.json +9 -9
  106. package/tests/test_agent_conversation_dispatch.py +212 -0
  107. package/tests/test_apps_catalog.py +26 -0
  108. package/tests/test_automation_setup.py +44 -0
  109. package/tests/test_check_pr_description.py +89 -6
  110. package/tests/test_github_reviewer_delivery.py +254 -118
  111. package/tests/test_github_stale_ci_pr_closer.py +56 -0
  112. package/tests/test_jira_issue_to_pr.py +312 -0
  113. package/tests/test_jira_setup_webhook.py +309 -0
  114. package/tests/test_openhands_api_python.py +37 -0
  115. package/tests/test_skill_plugin_loading.py +25 -15
  116. package/tests/test_sync_extensions.py +102 -0
  117. package/tests/test_vscode_app.py +33 -0
  118. 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 Cloud URL: `https://app.all-hands.dev/conversations/<app_conversation_id>`.
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.base_url}/conversations/{conversation_id}")
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 — they use `AGENT_SERVER_URL` and `SESSION_API_KEY` injected by the automation service.
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 stored in the agent server."""
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
- f"{url}/api/settings/secrets/{name}", headers={"X-Session-API-Key": key}
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** stored in the agent server are accessed via its REST API: `GET {AGENT_SERVER_URL}/api/settings/secrets/{name}` with `X-Session-API-Key: {SESSION_API_KEY}`. SDK scripts can also call `workspace.get_llm()` to get the configured LLM.
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 via the agent server's REST API:
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
- f"{url}/api/settings/secrets/{name}",
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
- req = urllib.request.Request(f"{url}/api/settings/secrets/{name}",
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 `AGENT_SERVER_URL` or `SESSION_API_KEY`
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.
@@ -71,14 +71,14 @@ Construct a prompt that includes:
71
71
 
72
72
  ### Step 4 — Create the automation
73
73
 
74
- Read the Automation backend URL and auth from `<RUNTIME_SERVICES>`:
75
- - Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`
76
- - Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`
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 "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
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}` / `$OPENHANDS_AUTOMATION_API_KEY` with `$env:OPENHANDS_HOST` / `$env:OPENHANDS_AUTOMATION_API_KEY` if running it natively.
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
- Read the Automation backend URL and auth from `<RUNTIME_SERVICES>`:
68
- - Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`
69
- - Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`
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 "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
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}` / `$OPENHANDS_AUTOMATION_API_KEY` with `$env:OPENHANDS_HOST` / `$env:OPENHANDS_AUTOMATION_API_KEY` if running it natively.
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 described in
44
- `<RUNTIME_SERVICES>`. Use its prompt-preset endpoint and authenticate with
45
- the runtime-provided automation API key.
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,8 @@
1
+ {
2
+ "name": "use-jev",
3
+ "version": "1.0.0",
4
+ "description": "Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "use-jev",
3
+ "version": "1.0.0",
4
+ "description": "Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "use-jev",
3
+ "version": "1.0.0",
4
+ "description": "Use Jev, a general classifier, for typed choices, scores, and yes/no probabilities in software.",
5
+ "author": {
6
+ "name": "OpenHands"
7
+ }
8
+ }
@@ -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
+ ```
@@ -68,7 +68,7 @@
68
68
  "timeout": 300,
69
69
  "template": {
70
70
  "id": "github-pr-reviewer",
71
- "version": "1.8.0",
71
+ "version": "1.8.1",
72
72
  "config": {
73
73
  "repos": [
74
74
  "OpenHands/agent-server-gui"
@@ -108,7 +108,7 @@
108
108
  "timeout": 300,
109
109
  "template": {
110
110
  "id": "github-pr-reviewer",
111
- "version": "1.8.0",
111
+ "version": "1.8.1",
112
112
  "config": {
113
113
  "repos": [
114
114
  "OpenHands/agent-server-gui"
@@ -213,7 +213,7 @@
213
213
  "timeout": 300,
214
214
  "template": {
215
215
  "id": "github-pr-reviewer",
216
- "version": "1.8.0",
216
+ "version": "1.8.1",
217
217
  "config": {
218
218
  "repos": [
219
219
  "OpenHands/agent-server-gui",
@@ -254,7 +254,7 @@
254
254
  "timeout": 300,
255
255
  "template": {
256
256
  "id": "github-pr-reviewer",
257
- "version": "1.8.0",
257
+ "version": "1.8.1",
258
258
  "config": {
259
259
  "repos": [
260
260
  "OpenHands/agent-server-gui",
@@ -296,7 +296,7 @@
296
296
  "preset_metadata": {
297
297
  "template": {
298
298
  "id": "github-pr-reviewer",
299
- "version": "1.8.0",
299
+ "version": "1.8.1",
300
300
  "config": {
301
301
  "repos": [
302
302
  "OpenHands/agent-server-gui",
@@ -351,7 +351,7 @@
351
351
  "timeout": 300,
352
352
  "template": {
353
353
  "id": "github-pr-reviewer",
354
- "version": "1.8.0",
354
+ "version": "1.8.1",
355
355
  "config": {
356
356
  "repos": [
357
357
  "OpenHands/agent-server-gui"
@@ -444,7 +444,7 @@
444
444
  "timeout": 300,
445
445
  "template": {
446
446
  "id": "github-pr-reviewer",
447
- "version": "1.8.0",
447
+ "version": "1.8.1",
448
448
  "config": {
449
449
  "repos": [
450
450
  "OpenHands/agent-server-gui"
@@ -540,7 +540,7 @@
540
540
  "timeout": 300,
541
541
  "template": {
542
542
  "id": "github-pr-reviewer",
543
- "version": "1.8.0",
543
+ "version": "1.8.1",
544
544
  "config": {
545
545
  "repos": [
546
546
  "OpenHands/agent-server-gui"
@@ -616,7 +616,7 @@
616
616
  "timeout": 300,
617
617
  "template": {
618
618
  "id": "github-pr-reviewer",
619
- "version": "1.8.0",
619
+ "version": "1.8.1",
620
620
  "config": {
621
621
  "repos": [
622
622
  "OpenHands/automation"