@openhands/extensions 0.0.1-alpha → 0.2.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/.agents/skills/custom-codereview-guide.md +25 -0
- package/.github/pull_request_template.md +38 -0
- package/.github/release.yml +14 -0
- package/.github/workflows/check-extensions.yml +72 -0
- package/.github/workflows/npm-publish.yml +89 -0
- package/.github/workflows/pr.yml +30 -0
- package/.github/workflows/release.yml +24 -0
- package/.github/workflows/tests.yml +25 -0
- package/.github/workflows/vulnerability-scan.yml +87 -0
- package/.release-please-manifest.json +3 -0
- package/AGENTS.md +132 -0
- package/README.md +10 -0
- package/analysis_results.md +162 -0
- package/marketplaces/large-codebase.json +66 -0
- package/marketplaces/openhands-extensions.json +682 -0
- package/package.json +4 -10
- package/plugins/README.md +30 -0
- package/plugins/city-weather/.plugin/plugin.json +13 -0
- package/plugins/city-weather/README.md +145 -0
- package/plugins/city-weather/commands/now.md +56 -0
- package/plugins/cobol-modernization/.plugin/plugin.json +19 -0
- package/plugins/cobol-modernization/README.md +201 -0
- package/plugins/cobol-modernization/references/troubleshooting.md +18 -0
- package/plugins/cobol-modernization/skills/build-setup/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/build-setup/scripts/install-gnucobol.sh +32 -0
- package/plugins/cobol-modernization/skills/cobol-modernization-overview/SKILL.md +113 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/SKILL.md +62 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/references/cics-transformation-examples.md +45 -0
- package/plugins/cobol-modernization/skills/mainframe-planning/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/to-java-migration/SKILL.md +59 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/cobol-to-java-example.md +58 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/datatype-mappings.md +19 -0
- package/plugins/issue-duplicate-checker/.plugin/plugin.json +13 -0
- package/plugins/issue-duplicate-checker/README.md +51 -0
- package/plugins/issue-duplicate-checker/action.yml +349 -0
- package/plugins/issue-duplicate-checker/scripts/auto_close_duplicate_issues.py +569 -0
- package/plugins/issue-duplicate-checker/scripts/issue_duplicate_check_openhands.py +681 -0
- package/plugins/issue-duplicate-checker/scripts/post_duplicate_notice.js +220 -0
- package/plugins/issue-duplicate-checker/scripts/remove_duplicate_candidate_label.js +27 -0
- package/plugins/magic-test/.plugin/plugin.json +13 -0
- package/plugins/magic-test/skills/magic-word/SKILL.md +33 -0
- package/plugins/migration-scoring/.plugin/plugin.json +19 -0
- package/plugins/migration-scoring/README.md +244 -0
- package/plugins/migration-scoring/skills/migration-mapping/SKILL.md +72 -0
- package/plugins/migration-scoring/skills/migration-report/SKILL.md +118 -0
- package/plugins/migration-scoring/skills/migration-scoring-overview/SKILL.md +126 -0
- package/plugins/migration-scoring/skills/score-quality/SKILL.md +54 -0
- package/plugins/migration-scoring/skills/score-quality/references/scoring-criteria.md +30 -0
- package/plugins/migration-scoring/skills/score-style/SKILL.md +106 -0
- package/plugins/onboarding/.plugin/plugin.json +20 -0
- package/plugins/onboarding/README.md +30 -0
- package/plugins/onboarding/references/criteria.md +144 -0
- package/plugins/onboarding/skills/agent-readiness-report/README.md +23 -0
- package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +122 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_agent_instructions.sh +88 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_build_env.sh +114 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_feedback_loops.sh +133 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_policy.sh +113 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_workflows.sh +127 -0
- package/plugins/onboarding/skills/improve-agent-readiness/README.md +19 -0
- package/plugins/onboarding/skills/improve-agent-readiness/SKILL.md +167 -0
- package/plugins/onboarding/skills/setup-agents-md/README.md +15 -0
- package/plugins/onboarding/skills/setup-agents-md/SKILL.md +150 -0
- package/plugins/onboarding/skills/setup-openhands/README.md +20 -0
- package/plugins/onboarding/skills/setup-openhands/SKILL.md +56 -0
- package/plugins/onboarding/skills/setup-pr-review/README.md +23 -0
- package/plugins/onboarding/skills/setup-pr-review/SKILL.md +72 -0
- package/plugins/openhands/.plugin/plugin.json +13 -0
- package/plugins/openhands/README.md +52 -0
- package/plugins/openhands/SKILL.md +61 -0
- package/plugins/openhands/commands/create.md +55 -0
- package/plugins/openhands/commands/openhands-cloud.md +8 -0
- package/plugins/openhands/scripts/run.sh +69 -0
- package/plugins/pr-review/.plugin/plugin.json +13 -0
- package/plugins/pr-review/README.md +393 -0
- package/plugins/pr-review/action.yml +298 -0
- package/plugins/pr-review/scripts/agent_script.py +1282 -0
- package/plugins/pr-review/scripts/evaluate_review.py +655 -0
- package/plugins/pr-review/scripts/prompt.py +260 -0
- package/plugins/pr-review/workflows/pr-review-by-openhands.yml +51 -0
- package/plugins/pr-review/workflows/pr-review-evaluation.yml +85 -0
- package/plugins/qa-changes/.plugin/plugin.json +11 -0
- package/plugins/qa-changes/README.md +185 -0
- package/plugins/qa-changes/action.yml +181 -0
- package/plugins/qa-changes/scripts/agent_script.py +406 -0
- package/plugins/qa-changes/scripts/evaluate_qa_changes.py +385 -0
- package/plugins/qa-changes/scripts/prompt.py +174 -0
- package/plugins/qa-changes/workflows/qa-changes-by-openhands.yml +50 -0
- package/plugins/qa-changes/workflows/qa-changes-evaluation.yml +85 -0
- package/plugins/release-notes/.plugin/plugin.json +19 -0
- package/plugins/release-notes/README.md +283 -0
- package/plugins/release-notes/SKILL.md +83 -0
- package/plugins/release-notes/action.yml +117 -0
- package/plugins/release-notes/commands/release-notes.md +8 -0
- package/plugins/release-notes/scripts/agent_script.py +292 -0
- package/plugins/release-notes/scripts/generate_release_notes.py +733 -0
- package/plugins/release-notes/scripts/prompt.py +90 -0
- package/plugins/release-notes/scripts/validate_release_notes.py +328 -0
- package/plugins/release-notes/workflows/release-notes.yml +76 -0
- package/plugins/vulnerability-remediation/.plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/README.md +217 -0
- package/plugins/vulnerability-remediation/action.yml +187 -0
- package/plugins/vulnerability-remediation/scripts/scan_and_remediate.py +561 -0
- package/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml +87 -0
- package/pyproject.toml +12 -0
- package/release-please-config.json +16 -0
- package/scripts/sync_extensions.py +494 -0
- package/scripts/sync_openhands_sdk_skill.py +264 -0
- package/skills/README.md +159 -0
- package/skills/add-javadoc/.plugin/plugin.json +18 -0
- package/skills/add-javadoc/README.md +40 -0
- package/skills/add-javadoc/SKILL.md +35 -0
- package/skills/add-javadoc/references/example.md +32 -0
- package/skills/add-skill/.plugin/plugin.json +18 -0
- package/skills/add-skill/README.md +67 -0
- package/skills/add-skill/SKILL.md +47 -0
- package/skills/add-skill/scripts/fetch_skill.py +259 -0
- package/skills/agent-creator/.plugin/plugin.json +20 -0
- package/skills/agent-creator/README.md +104 -0
- package/skills/agent-creator/SKILL.md +190 -0
- package/skills/agent-creator/commands/agent-creator.md +8 -0
- package/skills/agent-creator/references/fallback.md +117 -0
- package/skills/agent-memory/.plugin/plugin.json +18 -0
- package/skills/agent-memory/README.md +35 -0
- package/skills/agent-memory/SKILL.md +30 -0
- package/skills/agent-memory/commands/remember.md +8 -0
- package/skills/agent-sdk-builder/.plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/README.md +40 -0
- package/skills/agent-sdk-builder/SKILL.md +37 -0
- package/skills/agent-sdk-builder/commands/agent-builder.md +8 -0
- package/skills/azure-devops/.plugin/plugin.json +18 -0
- package/skills/azure-devops/README.md +55 -0
- package/skills/azure-devops/SKILL.md +50 -0
- package/skills/bitbucket/.plugin/plugin.json +17 -0
- package/skills/bitbucket/README.md +50 -0
- package/skills/bitbucket/SKILL.md +45 -0
- package/skills/code-review/.plugin/plugin.json +19 -0
- package/skills/code-review/README.md +18 -0
- package/skills/code-review/SKILL.md +208 -0
- package/skills/code-review/commands/codereview-roasted.md +8 -0
- package/skills/code-review/commands/codereview.md +8 -0
- package/skills/code-review/references/risk-evaluation.md +41 -0
- package/skills/code-review/references/supply-chain-security.md +31 -0
- package/skills/code-simplifier/.plugin/plugin.json +21 -0
- package/skills/code-simplifier/README.md +30 -0
- package/skills/code-simplifier/SKILL.md +91 -0
- package/skills/code-simplifier/commands/simplify.md +8 -0
- package/skills/code-simplifier/references/code-quality-review.md +86 -0
- package/skills/code-simplifier/references/code-reuse-review.md +63 -0
- package/skills/code-simplifier/references/efficiency-review.md +81 -0
- package/skills/datadog/.plugin/plugin.json +19 -0
- package/skills/datadog/README.md +100 -0
- package/skills/datadog/SKILL.md +95 -0
- package/skills/deno/.plugin/plugin.json +18 -0
- package/skills/deno/README.md +5 -0
- package/skills/deno/SKILL.md +99 -0
- package/skills/deno/references/README.md +6 -0
- package/skills/discord/.plugin/plugin.json +18 -0
- package/skills/discord/README.md +31 -0
- package/skills/discord/SKILL.md +109 -0
- package/skills/discord/__init__.py +0 -0
- package/skills/discord/references/REFERENCE.md +78 -0
- package/skills/discord/scripts/__init__.py +0 -0
- package/skills/discord/scripts/_http.py +127 -0
- package/skills/discord/scripts/post_webhook.py +106 -0
- package/skills/discord/scripts/send_message.py +102 -0
- package/skills/docker/.plugin/plugin.json +17 -0
- package/skills/docker/README.md +34 -0
- package/skills/docker/SKILL.md +29 -0
- package/skills/evidence-based-citations/.plugin/plugin.json +20 -0
- package/skills/evidence-based-citations/README.md +31 -0
- package/skills/evidence-based-citations/SKILL.md +59 -0
- package/skills/flarglebargle/.plugin/plugin.json +16 -0
- package/skills/flarglebargle/README.md +14 -0
- package/skills/flarglebargle/SKILL.md +9 -0
- package/skills/frontend-design/.plugin/plugin.json +21 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/README.md +42 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/github/.plugin/plugin.json +19 -0
- package/skills/github/README.md +42 -0
- package/skills/github/SKILL.md +106 -0
- package/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/skills/github-pr-review/README.md +145 -0
- package/skills/github-pr-review/SKILL.md +148 -0
- package/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/skills/github-pr-reviewer/.plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/README.md +34 -0
- package/skills/github-pr-reviewer/SKILL.md +89 -0
- package/skills/github-pr-reviewer/commands/pr-reviewer:setup.md +8 -0
- package/skills/github-repo-monitor/.plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/README.md +70 -0
- package/skills/github-repo-monitor/SKILL.md +316 -0
- package/skills/github-repo-monitor/commands/github-monitor:poll.md +8 -0
- package/skills/github-repo-monitor/references/github-api.md +241 -0
- package/skills/github-repo-monitor/references/state-schema.md +160 -0
- package/skills/github-repo-monitor/scripts/main.py +915 -0
- package/skills/github-repo-monitor/tests/test_main.py +400 -0
- package/skills/gitlab/.plugin/plugin.json +17 -0
- package/skills/gitlab/README.md +37 -0
- package/skills/gitlab/SKILL.md +32 -0
- package/skills/incident-retrospective/.plugin/plugin.json +21 -0
- package/skills/incident-retrospective/README.md +34 -0
- package/skills/incident-retrospective/SKILL.md +98 -0
- package/skills/incident-retrospective/commands/incident-retro:setup.md +8 -0
- package/skills/iterate/.plugin/plugin.json +13 -0
- package/skills/iterate/README.md +25 -0
- package/skills/iterate/SKILL.md +399 -0
- package/skills/iterate/commands/babysit.md +8 -0
- package/skills/iterate/commands/iterate.md +8 -0
- package/skills/iterate/commands/verify.md +8 -0
- package/skills/iterate/references/heuristics.md +58 -0
- package/skills/iterate/references/verification.md +96 -0
- package/skills/jupyter/.plugin/plugin.json +18 -0
- package/skills/jupyter/README.md +55 -0
- package/skills/jupyter/SKILL.md +50 -0
- package/skills/kubernetes/.plugin/plugin.json +18 -0
- package/skills/kubernetes/README.md +53 -0
- package/skills/kubernetes/SKILL.md +48 -0
- package/skills/learn-from-code-review/.plugin/plugin.json +19 -0
- package/skills/learn-from-code-review/README.md +64 -0
- package/skills/learn-from-code-review/SKILL.md +186 -0
- package/skills/learn-from-code-review/commands/learn-from-reviews.md +8 -0
- package/skills/linear/.plugin/plugin.json +19 -0
- package/skills/linear/README.md +58 -0
- package/skills/linear/SKILL.md +213 -0
- package/skills/linear-triage/.plugin/plugin.json +21 -0
- package/skills/linear-triage/README.md +34 -0
- package/skills/linear-triage/SKILL.md +91 -0
- package/skills/linear-triage/commands/linear-triage:setup.md +8 -0
- package/skills/notion/.plugin/plugin.json +17 -0
- package/skills/notion/README.md +114 -0
- package/skills/notion/SKILL.md +109 -0
- package/skills/npm/.plugin/plugin.json +17 -0
- package/skills/npm/README.md +14 -0
- package/skills/npm/SKILL.md +9 -0
- package/skills/openhands-api/.plugin/plugin.json +22 -0
- package/skills/openhands-api/README.md +48 -0
- package/skills/openhands-api/SKILL.md +399 -0
- package/skills/openhands-api/references/README.md +33 -0
- package/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
- package/skills/openhands-api/references/example_prompt.md +12 -0
- package/skills/openhands-api/scripts/openhands_api.py +606 -0
- package/skills/openhands-api/scripts/openhands_api.ts +252 -0
- package/skills/openhands-automation/.plugin/plugin.json +19 -0
- package/skills/openhands-automation/README.md +89 -0
- package/skills/openhands-automation/SKILL.md +875 -0
- package/skills/openhands-automation/commands/automation:create.md +8 -0
- package/skills/openhands-automation/references/ab-testing.md +185 -0
- package/skills/openhands-automation/references/custom-automation.md +644 -0
- package/skills/openhands-sdk/.plugin/plugin.json +20 -0
- package/skills/openhands-sdk/README.md +22 -0
- package/skills/openhands-sdk/SKILL.md +229 -0
- package/skills/openhands-sdk/commands/sdk.md +8 -0
- package/skills/pdflatex/.plugin/plugin.json +18 -0
- package/skills/pdflatex/README.md +39 -0
- package/skills/pdflatex/SKILL.md +34 -0
- package/skills/prd/.plugin/plugin.json +19 -0
- package/skills/prd/README.md +28 -0
- package/skills/prd/SKILL.md +237 -0
- package/skills/prd/commands/prd.md +8 -0
- package/skills/qa-changes/README.md +18 -0
- package/skills/qa-changes/SKILL.md +229 -0
- package/skills/qa-changes/commands/qa-changes.md +8 -0
- package/skills/release-notes/README.md +24 -0
- package/skills/release-notes/SKILL.md +19 -0
- package/skills/release-notes/commands/release-notes.md +8 -0
- package/skills/research-brief/.plugin/plugin.json +20 -0
- package/skills/research-brief/README.md +34 -0
- package/skills/research-brief/SKILL.md +99 -0
- package/skills/research-brief/commands/research-brief:setup.md +8 -0
- package/skills/security/.plugin/plugin.json +18 -0
- package/skills/security/README.md +38 -0
- package/skills/security/SKILL.md +33 -0
- package/skills/skill-creator/.plugin/plugin.json +17 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +182 -0
- package/skills/skill-creator/SKILL.md +545 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/skills/slack-channel-monitor/.plugin/plugin.json +21 -0
- package/skills/slack-channel-monitor/README.md +91 -0
- package/skills/slack-channel-monitor/SKILL.md +276 -0
- package/skills/slack-channel-monitor/commands/slack-monitor:poll.md +8 -0
- package/skills/slack-channel-monitor/references/slack-api.md +207 -0
- package/skills/slack-channel-monitor/references/state-schema.md +180 -0
- package/skills/slack-channel-monitor/scripts/main.py +962 -0
- package/skills/slack-standup-digest/.plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/README.md +34 -0
- package/skills/slack-standup-digest/SKILL.md +92 -0
- package/skills/slack-standup-digest/commands/standup-digest:setup.md +8 -0
- package/skills/spark-version-upgrade/.plugin/plugin.json +20 -0
- package/skills/spark-version-upgrade/README.md +54 -0
- package/skills/spark-version-upgrade/SKILL.md +233 -0
- package/skills/ssh/.plugin/plugin.json +18 -0
- package/skills/ssh/README.md +140 -0
- package/skills/ssh/SKILL.md +135 -0
- package/skills/swift-linux/.plugin/plugin.json +17 -0
- package/skills/swift-linux/README.md +86 -0
- package/skills/swift-linux/SKILL.md +81 -0
- package/skills/theme-factory/.plugin/plugin.json +19 -0
- package/skills/theme-factory/LICENSE.txt +202 -0
- package/skills/theme-factory/README.md +58 -0
- package/skills/theme-factory/SKILL.md +59 -0
- package/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/uv/.plugin/plugin.json +18 -0
- package/skills/uv/README.md +5 -0
- package/skills/uv/SKILL.md +95 -0
- package/skills/uv/references/README.md +5 -0
- package/skills/vercel/.plugin/plugin.json +18 -0
- package/skills/vercel/README.md +108 -0
- package/skills/vercel/SKILL.md +103 -0
- package/tests/test_add_skill_installs_to_agents_dir.py +42 -0
- package/tests/test_catalogs.py +109 -0
- package/tests/test_code_review_risk_evaluation.py +94 -0
- package/tests/test_issue_duplicate_checker.py +240 -0
- package/tests/test_openhands_api_python.py +152 -0
- package/tests/test_plugin_manifest.py +83 -0
- package/tests/test_pr_review_diff_payload.py +202 -0
- package/tests/test_pr_review_feedback.py +263 -0
- package/tests/test_pr_review_prompt.py +152 -0
- package/tests/test_pr_review_review_context.py +253 -0
- package/tests/test_qa_changes.py +232 -0
- package/tests/test_qa_changes_evaluation.py +259 -0
- package/tests/test_release_notes_generator.py +990 -0
- package/tests/test_sdk_loading.py +150 -0
- package/tests/test_skill_plugin_loading.py +149 -0
- package/tests/test_skills_have_readme.py +66 -0
- package/tests/test_sync_extensions.py +292 -0
- package/tests/test_workflow_sync.py +46 -0
- package/utils/analysis/README.md +7 -0
- package/utils/analysis/laminar_signals/README.md +211 -0
- package/utils/analysis/laminar_signals/analyze.py +780 -0
- package/utils/analysis/laminar_signals/templates/default.j2 +49 -0
- package/utils/analysis/laminar_signals/templates/pr_review.j2 +61 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
# Custom Automation Reference
|
|
2
|
+
|
|
3
|
+
> **⚠️ Do NOT use this reference unless the user has explicitly requested a custom automation.** Always use the preset/prompt endpoint from the main SKILL.md first. If the preset approach cannot satisfy the requirement, explain the options to the user and let them decide.
|
|
4
|
+
|
|
5
|
+
This file contains detailed documentation for creating custom automations with user-provided code, uploads, and entrypoints.
|
|
6
|
+
|
|
7
|
+
**When to use custom automation (only if the user explicitly chooses this):**
|
|
8
|
+
- Full control over the automation code structure is needed
|
|
9
|
+
- Custom dependencies or a specific runtime are required
|
|
10
|
+
- The user has confirmed that the prompt preset does not meet their requirements
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [Tarball Uploads](#uploading-a-tarball)
|
|
15
|
+
2. [Creating Custom Automations](#creating-an-automation)
|
|
16
|
+
3. [Managing Automations](#managing-automations)
|
|
17
|
+
4. [Writing Automation Code](#writing-automation-code)
|
|
18
|
+
- [SDK-based Scripts](#sdk-based-scripts) — AI agent conversations
|
|
19
|
+
- [Deterministic Script (No LLM)](#deterministic-script-no-llm) — pure Python stdlib
|
|
20
|
+
5. [Environment Variables](#environment-variables)
|
|
21
|
+
6. [Validation Rules](#validation-rules)
|
|
22
|
+
7. [Complete Examples](#complete-examples)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Uploading a Tarball
|
|
27
|
+
|
|
28
|
+
Before creating a custom automation, you need to upload your code as a tarball. The upload endpoint streams directly to cloud storage with a **1MB size limit**.
|
|
29
|
+
|
|
30
|
+
### Create a Tarball
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
tar -czf automation.tar.gz -C /path/to/your/code .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Tarball Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
automation.tar.gz
|
|
40
|
+
├── main.py # Your entrypoint script
|
|
41
|
+
├── setup.sh # Optional: install dependencies before entrypoint runs
|
|
42
|
+
└── requirements.txt # Optional: additional dependencies
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Validate Before Packaging
|
|
46
|
+
|
|
47
|
+
**Always validate syntax before creating the tarball.** This catches errors immediately and avoids uploading broken code that fails silently at runtime.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python3 -m py_compile main.py # fails with a clear error on any syntax problem
|
|
51
|
+
bash -n setup.sh # validates shell syntax without executing
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Fix any errors reported before proceeding to the next step.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Upload the Tarball
|
|
58
|
+
|
|
59
|
+
First, determine the API host. Look for a `<HOST>` value in the system prompt. If present, use that URL. Otherwise, default to `https://app.all-hands.dev`.
|
|
60
|
+
|
|
61
|
+
Then upload:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1/uploads?name=my-automation&description=Weekly%20report%20generator" \
|
|
65
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
66
|
+
-H "Content-Type: application/gzip" \
|
|
67
|
+
--data-binary @automation.tar.gz
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Upload Response
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
75
|
+
"tarball_path": "oh-internal://uploads/550e8400-e29b-41d4-a716-446655440000",
|
|
76
|
+
"status": "COMPLETED",
|
|
77
|
+
"size_bytes": 12345
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Important:** Save the `tarball_path` value - you'll need it when creating the automation.
|
|
82
|
+
|
|
83
|
+
### Upload Status Values
|
|
84
|
+
|
|
85
|
+
| Status | Description |
|
|
86
|
+
|--------|-------------|
|
|
87
|
+
| `UPLOADING` | Upload in progress |
|
|
88
|
+
| `COMPLETED` | Upload successful, `tarball_path` is available |
|
|
89
|
+
| `FAILED` | Upload failed, check `error_message` |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Creating an Automation
|
|
94
|
+
|
|
95
|
+
Once you have a tarball uploaded (or an external URL), create the automation:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1" \
|
|
99
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d '{
|
|
102
|
+
"name": "Weekly Report Generator",
|
|
103
|
+
"trigger": {
|
|
104
|
+
"type": "cron",
|
|
105
|
+
"schedule": "0 9 * * 1",
|
|
106
|
+
"timezone": "UTC"
|
|
107
|
+
},
|
|
108
|
+
"tarball_path": "oh-internal://uploads/550e8400-e29b-41d4-a716-446655440000",
|
|
109
|
+
"entrypoint": "python main.py",
|
|
110
|
+
"timeout": 300
|
|
111
|
+
}'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Request Fields
|
|
115
|
+
|
|
116
|
+
| Field | Required | Description |
|
|
117
|
+
|-------|----------|-------------|
|
|
118
|
+
| `name` | Yes | Name of the automation (1-500 characters) |
|
|
119
|
+
| `trigger.type` | Yes | Must be `"cron"` |
|
|
120
|
+
| `trigger.schedule` | Yes | Cron expression (5 fields: min hour day month weekday) |
|
|
121
|
+
| `trigger.timezone` | No | IANA timezone (default: `"UTC"`) |
|
|
122
|
+
| `tarball_path` | Yes | Path to code tarball (see Tarball Path Formats below) |
|
|
123
|
+
| `entrypoint` | Yes | Command to execute (e.g., `"python main.py"`, `"uv run script.py"`) |
|
|
124
|
+
| `setup_script_path` | No | Relative path to setup script inside tarball |
|
|
125
|
+
| `timeout` | No | Max execution time in seconds (1-600, default: 600) |
|
|
126
|
+
|
|
127
|
+
### Tarball Path Formats
|
|
128
|
+
|
|
129
|
+
| Format | Example | Description |
|
|
130
|
+
|--------|---------|-------------|
|
|
131
|
+
| Internal upload | `oh-internal://uploads/{uuid}` | Uploaded via `/api/v1/uploads` |
|
|
132
|
+
| S3 | `s3://bucket/path/file.tar.gz` | AWS S3 bucket |
|
|
133
|
+
| GCS | `gs://bucket/path/file.tar.gz` | Google Cloud Storage |
|
|
134
|
+
| HTTPS | `https://example.com/file.tar.gz` | Public HTTPS URL |
|
|
135
|
+
|
|
136
|
+
### Response (HTTP 201)
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
141
|
+
"name": "Weekly Report Generator",
|
|
142
|
+
"trigger": {
|
|
143
|
+
"type": "cron",
|
|
144
|
+
"schedule": "0 9 * * 1",
|
|
145
|
+
"timezone": "UTC"
|
|
146
|
+
},
|
|
147
|
+
"tarball_path": "oh-internal://uploads/550e8400-e29b-41d4-a716-446655440000",
|
|
148
|
+
"entrypoint": "python main.py",
|
|
149
|
+
"enabled": true,
|
|
150
|
+
"created_at": "2025-03-25T10:00:00Z"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Managing Automations
|
|
157
|
+
|
|
158
|
+
### List Automations
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
curl "${OPENHANDS_HOST}/api/automation/v1?limit=20" \
|
|
162
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Get Automation Details
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
curl "${OPENHANDS_HOST}/api/automation/v1/{automation_id}" \
|
|
169
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Update Automation
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
curl -X PATCH "${OPENHANDS_HOST}/api/automation/v1/{automation_id}" \
|
|
176
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
177
|
+
-H "Content-Type: application/json" \
|
|
178
|
+
-d '{"enabled": false}'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Delete Automation
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
curl -X DELETE "${OPENHANDS_HOST}/api/automation/v1/{automation_id}" \
|
|
185
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Manually Trigger a Run
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1/{automation_id}/dispatch" \
|
|
192
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### List Automation Runs
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
curl "${OPENHANDS_HOST}/api/automation/v1/{automation_id}/runs?limit=20" \
|
|
199
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Run Status Values:**
|
|
203
|
+
| Status | Description |
|
|
204
|
+
|--------|-------------|
|
|
205
|
+
| `PENDING` | Run scheduled, waiting for dispatch |
|
|
206
|
+
| `RUNNING` | Execution in progress |
|
|
207
|
+
| `COMPLETED` | Run finished successfully |
|
|
208
|
+
| `FAILED` | Run failed, check `error_detail` |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Writing Automation Code
|
|
213
|
+
|
|
214
|
+
### How Execution Works
|
|
215
|
+
|
|
216
|
+
When a run is triggered, the automation service uploads your tarball to the agent server, which unpacks it, runs `setup.sh` to install dependencies, then executes your entrypoint. Your script therefore runs **inside the agent server** — not in a separate process.
|
|
217
|
+
|
|
218
|
+
The agent server exposes an HTTP API (at `AGENT_SERVER_URL`) for managing conversations. A **conversation** is an AI agent interaction that can use tools: bash commands, file editing, web browsing, and so on. Your script uses the SDK's `OpenHandsCloudWorkspace` (pointing to `AGENT_SERVER_URL`) to start, monitor, and stop conversations running in that same agent server.
|
|
219
|
+
|
|
220
|
+
Key points:
|
|
221
|
+
- **Your script and its conversations share the same agent server.** There is no network hop to a remote service.
|
|
222
|
+
- **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).
|
|
223
|
+
- **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.
|
|
224
|
+
- **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.
|
|
225
|
+
|
|
226
|
+
**SDK Documentation:** https://docs.openhands.dev/sdk
|
|
227
|
+
|
|
228
|
+
### SDK-based Scripts
|
|
229
|
+
|
|
230
|
+
For scripts that start AI agent conversations, install the SDK packages in `setup.sh`:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
#!/bin/bash
|
|
234
|
+
set -e
|
|
235
|
+
|
|
236
|
+
# Install uv for fast dependency management (recommended)
|
|
237
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
238
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
239
|
+
|
|
240
|
+
# Install the OpenHands SDK packages from PyPI using uv
|
|
241
|
+
uv pip install -q openhands-sdk openhands-workspace openhands-tools
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Basic Automation Structure
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
"""Example automation using the OpenHands SDK."""
|
|
248
|
+
import os
|
|
249
|
+
|
|
250
|
+
from openhands.sdk import Conversation
|
|
251
|
+
from openhands.tools.preset.default import get_default_agent
|
|
252
|
+
from openhands.workspace import OpenHandsCloudWorkspace
|
|
253
|
+
|
|
254
|
+
# AGENT_SERVER_URL is set by the automation service for the agent server URL.
|
|
255
|
+
# SESSION_API_KEY / OH_SESSION_API_KEYS_0 authenticate against that server.
|
|
256
|
+
api_key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
257
|
+
api_url = os.environ.get("AGENT_SERVER_URL", "")
|
|
258
|
+
|
|
259
|
+
# OpenHandsCloudWorkspace connects back to the agent server to manage conversations.
|
|
260
|
+
# __exit__ sends the completion callback to the automation service.
|
|
261
|
+
with OpenHandsCloudWorkspace(
|
|
262
|
+
local_agent_server_mode=True,
|
|
263
|
+
cloud_api_url=api_url,
|
|
264
|
+
cloud_api_key=api_key,
|
|
265
|
+
) as workspace:
|
|
266
|
+
llm = workspace.get_llm()
|
|
267
|
+
agent = get_default_agent(llm=llm, cli_mode=True)
|
|
268
|
+
conversation = Conversation(agent=agent, workspace=workspace)
|
|
269
|
+
conversation.send_message("Your automation prompt here")
|
|
270
|
+
conversation.run()
|
|
271
|
+
conversation.close()
|
|
272
|
+
# OpenHandsCloudWorkspace.__exit__ fires the completion callback here.
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Conversation Persistence
|
|
276
|
+
|
|
277
|
+
Conversations started during a run remain accessible in the OpenHands UI after the run completes — users can view the history and continue interacting. By default, `Conversation` does not delete the conversation on close:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# Default: conversation persists after close (users can view/continue it)
|
|
281
|
+
conversation = Conversation(agent=agent, workspace=workspace)
|
|
282
|
+
|
|
283
|
+
# Explicitly persist (same as default)
|
|
284
|
+
conversation = Conversation(agent=agent, workspace=workspace, delete_on_close=False)
|
|
285
|
+
|
|
286
|
+
# Delete conversation resources on close
|
|
287
|
+
conversation = Conversation(agent=agent, workspace=workspace, delete_on_close=True)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The agent server itself persists until it times out or is manually deleted; this is managed by the automation service, not by the workspace.
|
|
291
|
+
|
|
292
|
+
### Conversation Patterns
|
|
293
|
+
|
|
294
|
+
#### Pattern 1: Synchronous (run and wait)
|
|
295
|
+
|
|
296
|
+
The simplest pattern — start a conversation, block until it finishes, then exit (firing the callback).
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
conversation.send_message("Analyze the latest deployment logs and summarise any errors")
|
|
300
|
+
conversation.run() # blocks until the agent finishes or times out
|
|
301
|
+
conversation.close()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Pattern 2: Conditional (fetch data first, then decide)
|
|
305
|
+
|
|
306
|
+
A common pattern where the script queries an external source and only starts a conversation if needed.
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
import httpx
|
|
310
|
+
|
|
311
|
+
response = httpx.get("https://api.example.com/alerts", headers={"Authorization": f"Bearer {token}"})
|
|
312
|
+
alerts = response.json().get("alerts", [])
|
|
313
|
+
|
|
314
|
+
if not alerts:
|
|
315
|
+
print("No alerts — nothing to do.")
|
|
316
|
+
else:
|
|
317
|
+
# Only now do we spin up an agent conversation
|
|
318
|
+
with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
|
|
319
|
+
llm = workspace.get_llm()
|
|
320
|
+
agent = get_default_agent(llm=llm, cli_mode=True)
|
|
321
|
+
conversation = Conversation(agent=agent, workspace=workspace)
|
|
322
|
+
conversation.send_message(f"Investigate these alerts and open GitHub issues: {alerts}")
|
|
323
|
+
conversation.run()
|
|
324
|
+
conversation.close()
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### Pattern 3: Wait for conversation completion (polling)
|
|
328
|
+
|
|
329
|
+
Start a conversation without blocking, do other work, then poll until the conversation reaches a terminal state before exiting. The callback fires only after the conversation is done.
|
|
330
|
+
|
|
331
|
+
`ConversationExecutionStatus.is_terminal()` returns `True` for `FINISHED`, `ERROR`, and `STUCK`. Call `refresh_from_server()` before checking status — `execution_status` uses a cached value and won't update automatically.
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
import time
|
|
335
|
+
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
336
|
+
|
|
337
|
+
with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
|
|
338
|
+
llm = workspace.get_llm()
|
|
339
|
+
agent = get_default_agent(llm=llm, cli_mode=True)
|
|
340
|
+
conversation = Conversation(agent=agent, workspace=workspace)
|
|
341
|
+
conversation.send_message("Run a long analysis task")
|
|
342
|
+
# Conversation is now running asynchronously in the agent server.
|
|
343
|
+
|
|
344
|
+
# Do other work here while conversation runs...
|
|
345
|
+
|
|
346
|
+
# Wait until the conversation reaches a terminal state.
|
|
347
|
+
while True:
|
|
348
|
+
time.sleep(5)
|
|
349
|
+
conversation.refresh_from_server()
|
|
350
|
+
if conversation.execution_status.is_terminal():
|
|
351
|
+
break
|
|
352
|
+
|
|
353
|
+
conversation.close()
|
|
354
|
+
# Callback fires here — after the conversation has finished.
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Pattern 4: Deferred callback via stop hook
|
|
358
|
+
|
|
359
|
+
For cases where the automation script needs to exit while a conversation is still running, use a `stop` hook to fire the completion callback from within the agent server when the conversation finishes.
|
|
360
|
+
|
|
361
|
+
The `stop` hook runs a shell command when the agent stops. The agent server's environment includes `AUTOMATION_CALLBACK_URL`, `AUTOMATION_CALLBACK_API_KEY`, and `AUTOMATION_RUN_ID`, so the hook can call the automation service directly.
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from openhands.sdk.hooks import HookConfig, HookDefinition, HookMatcher
|
|
365
|
+
|
|
366
|
+
# Shell command that fires the completion callback when the agent stops.
|
|
367
|
+
# Runs inside the agent server — env vars are available at hook execution time.
|
|
368
|
+
stop_hook = HookConfig(
|
|
369
|
+
stop=[
|
|
370
|
+
HookMatcher(hooks=[
|
|
371
|
+
HookDefinition(
|
|
372
|
+
command=(
|
|
373
|
+
'curl -sf -X POST "$AUTOMATION_CALLBACK_URL" '
|
|
374
|
+
'-H "Authorization: Bearer $AUTOMATION_CALLBACK_API_KEY" '
|
|
375
|
+
'-H "Content-Type: application/json" '
|
|
376
|
+
'-d \'{"status":"COMPLETED","run_id":"$AUTOMATION_RUN_ID"}\' || true'
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
])
|
|
380
|
+
]
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
with OpenHandsCloudWorkspace(local_agent_server_mode=True, cloud_api_url=api_url, cloud_api_key=api_key) as workspace:
|
|
384
|
+
llm = workspace.get_llm()
|
|
385
|
+
agent = get_default_agent(llm=llm, cli_mode=True)
|
|
386
|
+
conversation = Conversation(agent=agent, workspace=workspace, hook_config=stop_hook)
|
|
387
|
+
conversation.send_message("Do some long-running work")
|
|
388
|
+
# Don't call run() — the conversation runs asynchronously.
|
|
389
|
+
# When the agent stops, the stop hook will fire the callback.
|
|
390
|
+
# OpenHandsCloudWorkspace.__exit__ also fires a callback here (on script exit).
|
|
391
|
+
# The automation service should handle receiving two callbacks for the same run.
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
> **Note:** When using the stop hook pattern, the automation service receives two completion callbacks — one from `OpenHandsCloudWorkspace.__exit__` when the script exits, and one from the stop hook when the conversation finishes. Ensure your automation service handles duplicate callbacks gracefully.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
### Deterministic Script (No LLM)
|
|
399
|
+
|
|
400
|
+
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`.
|
|
401
|
+
|
|
402
|
+
**Accessing secrets** — custom secrets are not injected into the subprocess environment automatically. Fetch them via the agent server's REST API:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
import os, urllib.request
|
|
406
|
+
|
|
407
|
+
def get_secret(name: str) -> str:
|
|
408
|
+
url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
|
|
409
|
+
key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
410
|
+
req = urllib.request.Request(
|
|
411
|
+
f"{url}/api/settings/secrets/{name}",
|
|
412
|
+
headers={"X-Session-API-Key": key},
|
|
413
|
+
)
|
|
414
|
+
with urllib.request.urlopen(req) as r:
|
|
415
|
+
return r.read().decode().strip()
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Firing the callback** — without the SDK, POST to `AUTOMATION_CALLBACK_URL` before exiting. If you never fire the callback the run stays `RUNNING` until the watchdog marks it `FAILED`.
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
import json, os, urllib.request
|
|
422
|
+
|
|
423
|
+
def fire_callback(status: str = "COMPLETED", error: str | None = None) -> None:
|
|
424
|
+
url = os.environ.get("AUTOMATION_CALLBACK_URL", "")
|
|
425
|
+
if not url:
|
|
426
|
+
return
|
|
427
|
+
body = {"status": status, "run_id": os.environ.get("AUTOMATION_RUN_ID", "")}
|
|
428
|
+
if error:
|
|
429
|
+
body["error"] = error
|
|
430
|
+
req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={
|
|
431
|
+
"Content-Type": "application/json",
|
|
432
|
+
"Authorization": f"Bearer {os.environ.get('AUTOMATION_CALLBACK_API_KEY', '')}",
|
|
433
|
+
})
|
|
434
|
+
try:
|
|
435
|
+
urllib.request.urlopen(req)
|
|
436
|
+
except Exception as e:
|
|
437
|
+
print(f"Callback error (non-fatal): {e}")
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Environment Variables
|
|
443
|
+
|
|
444
|
+
The automation service injects these environment variables into every run:
|
|
445
|
+
|
|
446
|
+
| Variable | Alt name | Description |
|
|
447
|
+
|----------|----------|-------------|
|
|
448
|
+
| `AGENT_SERVER_URL` | — | Agent server URL. Used as `cloud_api_url` for `OpenHandsCloudWorkspace`, and as the base URL for secret lookups |
|
|
449
|
+
| `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 |
|
|
450
|
+
| `AUTOMATION_CALLBACK_URL` | — | POST here to mark the run complete (done automatically by `OpenHandsCloudWorkspace.__exit__`, or manually in no-LLM scripts) |
|
|
451
|
+
| `AUTOMATION_CALLBACK_API_KEY` | — | Bearer token for the completion callback POST |
|
|
452
|
+
| `AUTOMATION_RUN_ID` | — | Run ID to include in the completion callback payload |
|
|
453
|
+
| `AUTOMATION_EVENT_PAYLOAD` | — | JSON with trigger context: `automation_id`, `automation_name`, `trigger` type, and (for webhook runs) the raw event payload |
|
|
454
|
+
|
|
455
|
+
> **Note:** The session API key has two names: `SESSION_API_KEY` (cloud) and `OH_SESSION_API_KEYS_0` (local/dev). Always read both — see the code examples above.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Validation Rules
|
|
460
|
+
|
|
461
|
+
- **Name**: 1-500 characters
|
|
462
|
+
- **Cron schedule**: Valid 5-field cron expression
|
|
463
|
+
- **Entrypoint**: Relative path, no shell metacharacters (`;`, `&`, `|`, etc.)
|
|
464
|
+
- **Setup script path**: Relative path, no path traversal (`..`)
|
|
465
|
+
- **Timeout**: 1-600 seconds (10 minutes max)
|
|
466
|
+
- **Tarball size**: 1MB max for uploads
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Complete Examples
|
|
471
|
+
|
|
472
|
+
### No LLM (deterministic)
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
OPENHANDS_HOST="https://app.all-hands.dev"
|
|
476
|
+
mkdir my-automation && cd my-automation
|
|
477
|
+
|
|
478
|
+
cat > main.py << 'PYEOF'
|
|
479
|
+
"""Post a random quote to Slack — no LLM, no SDK."""
|
|
480
|
+
import json, os, random, sys, urllib.request
|
|
481
|
+
|
|
482
|
+
QUOTES = ["Stay hungry, stay foolish.", "Done is better than perfect."]
|
|
483
|
+
CHANNEL = "C12345678"
|
|
484
|
+
|
|
485
|
+
def get_secret(name):
|
|
486
|
+
url = os.environ.get("AGENT_SERVER_URL", "").rstrip("/")
|
|
487
|
+
key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
488
|
+
req = urllib.request.Request(f"{url}/api/settings/secrets/{name}",
|
|
489
|
+
headers={"X-Session-API-Key": key})
|
|
490
|
+
with urllib.request.urlopen(req) as r:
|
|
491
|
+
return r.read().decode().strip()
|
|
492
|
+
|
|
493
|
+
def fire_callback(status="COMPLETED", error=None):
|
|
494
|
+
url = os.environ.get("AUTOMATION_CALLBACK_URL", "")
|
|
495
|
+
if not url: return
|
|
496
|
+
body = {"status": status, "run_id": os.environ.get("AUTOMATION_RUN_ID", "")}
|
|
497
|
+
if error: body["error"] = error
|
|
498
|
+
req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={
|
|
499
|
+
"Content-Type": "application/json",
|
|
500
|
+
"Authorization": f"Bearer {os.environ.get('AUTOMATION_CALLBACK_API_KEY', '')}",
|
|
501
|
+
})
|
|
502
|
+
try: urllib.request.urlopen(req)
|
|
503
|
+
except Exception as e: print(f"Callback error: {e}")
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
token = get_secret("SLACK_BOT_TOKEN")
|
|
507
|
+
msg = random.choice(QUOTES)
|
|
508
|
+
req = urllib.request.Request("https://slack.com/api/chat.postMessage",
|
|
509
|
+
data=json.dumps({"channel": CHANNEL, "text": msg}).encode(),
|
|
510
|
+
headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"})
|
|
511
|
+
result = json.loads(urllib.request.urlopen(req).read())
|
|
512
|
+
if not result.get("ok"): raise RuntimeError(result.get("error"))
|
|
513
|
+
print(f"Posted: {msg}")
|
|
514
|
+
fire_callback("COMPLETED")
|
|
515
|
+
except Exception as e:
|
|
516
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
517
|
+
fire_callback("FAILED", str(e))
|
|
518
|
+
sys.exit(1)
|
|
519
|
+
PYEOF
|
|
520
|
+
|
|
521
|
+
tar -czf ../my-automation.tar.gz .
|
|
522
|
+
|
|
523
|
+
TARBALL_PATH=$(curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/uploads?name=my-automation" \
|
|
524
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
525
|
+
-H "Content-Type: application/gzip" \
|
|
526
|
+
--data-binary @../my-automation.tar.gz | jq -r '.tarball_path')
|
|
527
|
+
|
|
528
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1" \
|
|
529
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
530
|
+
-H "Content-Type: application/json" \
|
|
531
|
+
-d "{
|
|
532
|
+
\"name\": \"Daily Quote\",
|
|
533
|
+
\"trigger\": {\"type\": \"cron\", \"schedule\": \"0 9 * * *\", \"timezone\": \"UTC\"},
|
|
534
|
+
\"tarball_path\": \"$TARBALL_PATH\",
|
|
535
|
+
\"entrypoint\": \"python3 main.py\"
|
|
536
|
+
}"
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### SDK Script (AI conversations)
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
# 0. Set the API host (use value from <HOST> in system prompt, or default)
|
|
543
|
+
OPENHANDS_HOST="https://app.all-hands.dev"
|
|
544
|
+
|
|
545
|
+
# 1. Create your automation code
|
|
546
|
+
mkdir my-automation && cd my-automation
|
|
547
|
+
|
|
548
|
+
# Create setup.sh
|
|
549
|
+
cat > setup.sh << 'EOF'
|
|
550
|
+
#!/bin/bash
|
|
551
|
+
set -e
|
|
552
|
+
uv venv .venv --quiet
|
|
553
|
+
uv pip install --quiet \
|
|
554
|
+
openhands-sdk \
|
|
555
|
+
openhands-workspace \
|
|
556
|
+
openhands-tools
|
|
557
|
+
EOF
|
|
558
|
+
chmod +x setup.sh
|
|
559
|
+
|
|
560
|
+
# Create main.py using the SDK
|
|
561
|
+
cat > main.py << 'EOF'
|
|
562
|
+
"""Weekly report automation using OpenHands SDK."""
|
|
563
|
+
import os
|
|
564
|
+
import json
|
|
565
|
+
|
|
566
|
+
from openhands.sdk import Conversation
|
|
567
|
+
from openhands.tools.preset.default import get_default_agent
|
|
568
|
+
from openhands.workspace import OpenHandsCloudWorkspace
|
|
569
|
+
|
|
570
|
+
payload = json.loads(os.environ.get('AUTOMATION_EVENT_PAYLOAD', '{}'))
|
|
571
|
+
print(f"Running: {payload.get('automation_name')}")
|
|
572
|
+
|
|
573
|
+
api_key = os.environ.get("SESSION_API_KEY") or os.environ.get("OH_SESSION_API_KEYS_0", "")
|
|
574
|
+
api_url = os.environ.get("AGENT_SERVER_URL", "")
|
|
575
|
+
|
|
576
|
+
with OpenHandsCloudWorkspace(
|
|
577
|
+
local_agent_server_mode=True,
|
|
578
|
+
cloud_api_url=api_url,
|
|
579
|
+
cloud_api_key=api_key,
|
|
580
|
+
) as workspace:
|
|
581
|
+
llm = workspace.get_llm()
|
|
582
|
+
agent = get_default_agent(llm=llm, cli_mode=True)
|
|
583
|
+
conversation = Conversation(agent=agent, workspace=workspace)
|
|
584
|
+
conversation.send_message("Generate a weekly status report")
|
|
585
|
+
conversation.run()
|
|
586
|
+
conversation.close()
|
|
587
|
+
|
|
588
|
+
print("Automation completed!")
|
|
589
|
+
EOF
|
|
590
|
+
|
|
591
|
+
# 2. Validate syntax before packaging
|
|
592
|
+
python3 -m py_compile main.py
|
|
593
|
+
bash -n setup.sh
|
|
594
|
+
|
|
595
|
+
# 3. Create the tarball
|
|
596
|
+
tar -czf ../my-automation.tar.gz .
|
|
597
|
+
|
|
598
|
+
# 4. Upload the tarball
|
|
599
|
+
UPLOAD_RESPONSE=$(curl -s -X POST \
|
|
600
|
+
"${OPENHANDS_HOST}/api/automation/v1/uploads?name=my-automation" \
|
|
601
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
602
|
+
-H "Content-Type: application/gzip" \
|
|
603
|
+
--data-binary @my-automation.tar.gz)
|
|
604
|
+
|
|
605
|
+
TARBALL_PATH=$(echo "$UPLOAD_RESPONSE" | jq -r '.tarball_path')
|
|
606
|
+
|
|
607
|
+
# 5. Create the automation
|
|
608
|
+
curl -X POST "${OPENHANDS_HOST}/api/automation/v1" \
|
|
609
|
+
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
|
|
610
|
+
-H "Content-Type: application/json" \
|
|
611
|
+
-d "{
|
|
612
|
+
\"name\": \"Weekly Report Generator\",
|
|
613
|
+
\"trigger\": {\"type\": \"cron\", \"schedule\": \"0 9 * * 1\", \"timezone\": \"UTC\"},
|
|
614
|
+
\"tarball_path\": \"$TARBALL_PATH\",
|
|
615
|
+
\"entrypoint\": \".venv/bin/python main.py\",
|
|
616
|
+
\"setup_script_path\": \"setup.sh\",
|
|
617
|
+
\"timeout\": 300
|
|
618
|
+
}"
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Troubleshooting
|
|
624
|
+
|
|
625
|
+
### Upload Failed: File too large
|
|
626
|
+
The upload limit is 1MB. Reduce your tarball size by:
|
|
627
|
+
- Excluding unnecessary files
|
|
628
|
+
- Not including `node_modules`, `.venv`, or other dependency directories
|
|
629
|
+
|
|
630
|
+
### Automation Not Running
|
|
631
|
+
1. Check if the automation is enabled (`enabled: true`)
|
|
632
|
+
2. Verify the cron schedule is correct
|
|
633
|
+
3. Check for validation errors in the response
|
|
634
|
+
|
|
635
|
+
### Run fails instantly with `error_detail: null`
|
|
636
|
+
The script sent `fire_callback("FAILED")` immediately — before doing meaningful work. Common causes:
|
|
637
|
+
- A required secret was empty: the `get_secret()` call failed or returned nothing
|
|
638
|
+
- A missing/wrong `AGENT_SERVER_URL` or `SESSION_API_KEY`
|
|
639
|
+
- An import error in the entrypoint
|
|
640
|
+
|
|
641
|
+
Add `"error": str(exc)` to your `fire_callback("FAILED", ...)` call so `error_detail` is populated.
|
|
642
|
+
|
|
643
|
+
### Run stays `RUNNING` indefinitely, then fails
|
|
644
|
+
The completion callback was never fired. Every code path in your script must call `fire_callback()` — including exception handlers.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openhands-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reference skill for the OpenHands Software Agent SDK - build AI agents with custom tools, LLM configuration, conversations, sub-agent delegation, MCP integration, security, and persistence.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "OpenHands",
|
|
7
|
+
"email": "contact@all-hands.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/OpenHands/extensions",
|
|
10
|
+
"repository": "https://github.com/OpenHands/extensions",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sdk",
|
|
14
|
+
"agent",
|
|
15
|
+
"openhands",
|
|
16
|
+
"tools",
|
|
17
|
+
"llm",
|
|
18
|
+
"conversation"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# openhands-sdk
|
|
2
|
+
|
|
3
|
+
Reference skill for the **OpenHands Software Agent SDK** - the Python framework
|
|
4
|
+
for building AI agents that write software.
|
|
5
|
+
|
|
6
|
+
This skill is a **thin pointer** to the canonical SDK documentation. All detailed
|
|
7
|
+
content lives on the docs site and is not duplicated here.
|
|
8
|
+
|
|
9
|
+
- Skill entry point: [`SKILL.md`](./SKILL.md)
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
- [Full SDK docs](https://docs.openhands.dev/sdk)
|
|
14
|
+
- [LLMs.txt (structured doc index)](https://docs.openhands.dev/llms.txt)
|
|
15
|
+
- [SDK source code](https://github.com/OpenHands/software-agent-sdk)
|
|
16
|
+
- [Examples](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk)
|
|
17
|
+
|
|
18
|
+
## Contributing SDK documentation
|
|
19
|
+
|
|
20
|
+
**Do not add SDK-specific documentation to this repo.** The source of truth is
|
|
21
|
+
[OpenHands/docs](https://github.com/OpenHands/docs). If you want to improve SDK
|
|
22
|
+
docs, submit changes there. This skill links directly to the docs site.
|