@openhands/extensions 0.4.2 → 0.6.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/.github/workflows/pypi-publish.yml +104 -0
- package/.github/workflows/tests.yml +6 -0
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +1 -1
- package/README.md +31 -3
- package/automations/catalog/slack-channel-monitor.json +2 -2
- package/integrations/README.md +17 -7
- package/integrations/catalog/airtable.json +40 -10
- package/integrations/catalog/apify.json +0 -2
- package/integrations/catalog/asana.json +38 -0
- package/integrations/catalog/atlassian.json +5 -8
- package/integrations/catalog/bitbucket.json +39 -0
- package/integrations/catalog/box.json +39 -0
- package/integrations/catalog/brave-search.json +1 -2
- package/integrations/catalog/browser-mcp.json +0 -3
- package/integrations/catalog/canva.json +35 -0
- package/integrations/catalog/clickhouse.json +1 -2
- package/integrations/catalog/clickup.json +36 -0
- package/integrations/catalog/cloudflare-bindings.json +5 -8
- package/integrations/catalog/cloudflare-browser-rendering.json +5 -8
- package/integrations/catalog/cloudflare-builds.json +6 -9
- package/integrations/catalog/cloudflare-docs.json +4 -7
- package/integrations/catalog/cloudflare-observability.json +5 -8
- package/integrations/catalog/confluence.json +38 -0
- package/integrations/catalog/datadog.json +34 -0
- package/integrations/catalog/deepwiki.json +3 -7
- package/integrations/catalog/discord.json +39 -0
- package/integrations/catalog/dropbox.json +38 -0
- package/integrations/catalog/elevenlabs.json +27 -28
- package/integrations/catalog/everything.json +0 -2
- package/integrations/catalog/exa.json +0 -2
- package/integrations/catalog/fetch.json +0 -2
- package/integrations/catalog/figma.json +41 -9
- package/integrations/catalog/filesystem.json +0 -3
- package/integrations/catalog/firecrawl.json +0 -2
- package/integrations/catalog/freshdesk.json +38 -0
- package/integrations/catalog/git.json +0 -3
- package/integrations/catalog/github.json +51 -34
- package/integrations/catalog/gitlab.json +38 -0
- package/integrations/catalog/gmail.json +38 -0
- package/integrations/catalog/google-calendar.json +38 -0
- package/integrations/catalog/google-docs.json +38 -0
- package/integrations/catalog/google-drive.json +38 -0
- package/integrations/catalog/google-sheets.json +38 -0
- package/integrations/catalog/hubspot.json +34 -0
- package/integrations/catalog/huggingface.json +1 -2
- package/integrations/catalog/intercom.json +39 -0
- package/integrations/catalog/jira.json +38 -0
- package/integrations/catalog/kagi.json +2 -3
- package/integrations/catalog/linear.json +47 -16
- package/integrations/catalog/mailchimp.json +38 -0
- package/integrations/catalog/memory.json +0 -2
- package/integrations/catalog/microsoft-outlook.json +38 -0
- package/integrations/catalog/microsoft-teams.json +38 -0
- package/integrations/catalog/miro.json +38 -0
- package/integrations/catalog/monday.json +38 -0
- package/integrations/catalog/mongodb.json +1 -2
- package/integrations/catalog/neon.json +0 -2
- package/integrations/catalog/netlify.json +38 -0
- package/integrations/catalog/notion.json +37 -10
- package/integrations/catalog/obsidian.json +1 -3
- package/integrations/catalog/okta.json +38 -0
- package/integrations/catalog/onedrive.json +38 -0
- package/integrations/catalog/ordinal.json +33 -0
- package/integrations/catalog/paypal.json +5 -8
- package/integrations/catalog/pipedrive.json +39 -0
- package/integrations/catalog/plaid.json +31 -0
- package/integrations/catalog/playwright.json +0 -2
- package/integrations/catalog/posthog.json +38 -0
- package/integrations/catalog/quickbooks.json +38 -0
- package/integrations/catalog/redis.json +1 -3
- package/integrations/catalog/resend.json +1 -2
- package/integrations/catalog/salesforce.json +38 -0
- package/integrations/catalog/sentry.json +52 -14
- package/integrations/catalog/sequential-thinking.json +0 -2
- package/integrations/catalog/servicenow.json +39 -0
- package/integrations/catalog/sharepoint.json +38 -0
- package/integrations/catalog/shopify.json +38 -0
- package/integrations/catalog/slack.json +49 -10
- package/integrations/catalog/stripe.json +44 -14
- package/integrations/catalog/supabase.json +36 -9
- package/integrations/catalog/tavily.json +0 -2
- package/integrations/catalog/time.json +0 -2
- package/integrations/catalog/trello.json +31 -0
- package/integrations/catalog/vercel.json +38 -0
- package/integrations/catalog/webflow.json +34 -0
- package/integrations/catalog/xero.json +38 -0
- package/integrations/catalog/zendesk.json +38 -0
- package/integrations/catalog/zoom.json +38 -0
- package/integrations/catalog-index.js +171 -0
- package/integrations/index.d.ts +26 -60
- package/integrations/index.js +29 -207
- package/marketplaces/openhands-extensions.json +14 -1
- package/package.json +4 -7
- package/plugins/cobol-modernization/README.md +3 -3
- package/plugins/cobol-modernization/skills/{cobol-modernization-overview → cobol-modernization}/SKILL.md +1 -1
- package/plugins/magic-test/skills/magic-word/SKILL.md +1 -0
- package/plugins/migration-scoring/README.md +1 -1
- package/pyproject.toml +17 -3
- package/python/openhands_extensions/__init__.py +24 -0
- package/python/openhands_extensions/_version.py +24 -0
- package/python/openhands_extensions/integrations.py +92 -0
- package/release-please-config.json +2 -1
- package/scripts/build-integration-catalog.mjs +45 -0
- package/skills/agent-canvas-environment/.plugin/plugin.json +19 -0
- package/skills/agent-canvas-environment/README.md +5 -0
- package/skills/agent-canvas-environment/SKILL.md +141 -0
- package/skills/github-pr-reviewer/references/state-schema.md +18 -4
- package/skills/github-pr-reviewer/scripts/main.py +66 -12
- package/skills/github-repo-monitor/references/state-schema.md +11 -4
- package/skills/github-repo-monitor/scripts/main.py +67 -19
- package/skills/github-repo-monitor/tests/test_main.py +67 -8
- package/skills/index.js +19 -5
- package/skills/openhands-api/README.md +5 -1
- package/skills/openhands-api/SKILL.md +74 -5
- package/skills/openhands-automation/SKILL.md +3 -1
- package/skills/openhands-automation/references/custom-automation.md +166 -0
- package/skills/openhands-sdk/SKILL.md +8 -2
- package/skills/slack-channel-monitor/README.md +9 -5
- package/skills/slack-channel-monitor/SKILL.md +16 -6
- package/skills/slack-channel-monitor/references/slack-api.md +5 -3
- package/skills/slack-channel-monitor/references/state-schema.md +70 -36
- package/skills/slack-channel-monitor/scripts/main.py +335 -63
- package/tests/test_catalogs.py +21 -3
- package/tests/test_integration_catalog_in_sync.py +204 -0
- package/tests/test_slack_channel_monitor.py +142 -0
- package/tests/test_version_alignment.py +64 -0
- package/integrations/logos.d.ts +0 -5
- package/integrations/logos.js +0 -94
- package/integrations/oauth-provider-catalog.js +0 -553
- package/integrations/oauth-provider-registration-defaults.js +0 -610
- /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/SKILL.md +0 -0
- /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/references/cics-transformation-examples.md +0 -0
- /package/plugins/migration-scoring/skills/{migration-scoring-overview → migration-scoring}/SKILL.md +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# openhands-api
|
|
2
2
|
|
|
3
|
-
Reference skill + minimal clients for the **OpenHands Cloud API** (V1)
|
|
3
|
+
Reference skill + minimal clients for the **OpenHands Cloud API** (V1) and common **agent-server APIs**.
|
|
4
4
|
|
|
5
5
|
This skill now also covers the **multi-conversation delegation pattern**: start additional Cloud conversations when you want fresh context windows, background work, or parallel tasks.
|
|
6
6
|
|
|
7
|
+
It also covers direct local Agent Canvas backend conversations when you need to call a local agent server with `X-Session-API-Key` instead of creating a Cloud app conversation.
|
|
8
|
+
|
|
7
9
|
- Skill instructions and endpoint overview: [`SKILL.md`](./SKILL.md)
|
|
8
10
|
- Minimal Python client: [`scripts/openhands_api.py`](./scripts/openhands_api.py)
|
|
9
11
|
- Minimal TypeScript client: [`scripts/openhands_api.ts`](./scripts/openhands_api.ts)
|
|
@@ -42,6 +44,8 @@ If `app_conversation_id` is missing from the initial response, fetch it via:
|
|
|
42
44
|
This skill is aligned against:
|
|
43
45
|
|
|
44
46
|
- `OpenHands/docs/openhands/usage/cloud/cloud-api.mdx`
|
|
47
|
+
- `OpenHands/docs/openhands/usage/agent-canvas/backend-setup/local.mdx`
|
|
48
|
+
- `OpenHands/docs/sdk/arch/agent-server.mdx`
|
|
45
49
|
- `OpenHands/docs/openhands/usage/api/v1.mdx`
|
|
46
50
|
- `OpenHands/OpenHands/openhands/app_server/v1_router.py`
|
|
47
51
|
- `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_router.py`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openhands-api
|
|
3
|
-
description: Reference skill for the OpenHands Cloud REST API (V1), including how to start additional cloud conversations for fresh-context or delegated work.
|
|
3
|
+
description: Reference skill for the OpenHands Cloud REST API (V1) and agent-server APIs, including how to start additional cloud or local backend conversations for fresh-context or delegated work.
|
|
4
4
|
triggers:
|
|
5
5
|
- openhands-api
|
|
6
6
|
- openhands-api-v1
|
|
@@ -10,14 +10,15 @@ triggers:
|
|
|
10
10
|
- oh-cloud-api-v1
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
This skill documents the **OpenHands Cloud API** (V1) and
|
|
13
|
+
This skill documents the **OpenHands Cloud API** (V1), commonly used **agent-server APIs**, and small, easy-to-copy clients.
|
|
14
14
|
|
|
15
|
-
It is intentionally focused on common OpenHands
|
|
15
|
+
It is intentionally focused on common OpenHands API workflows:
|
|
16
16
|
|
|
17
17
|
- Defaults to OpenHands Cloud (`https://app.all-hands.dev`).
|
|
18
18
|
- Targets the **V1 app server REST API** under `/api/v1/...`.
|
|
19
19
|
- Includes a few **agent server** endpoints (inside a sandbox) that use `X-Session-API-Key`.
|
|
20
20
|
- Covers the **multi-conversation delegation pattern**: start separate Cloud conversations when you want fresh context windows or background work.
|
|
21
|
+
- Covers **local Agent Canvas backend conversations**: start or inspect conversations by calling a local agent server directly.
|
|
21
22
|
|
|
22
23
|
## When to use this skill
|
|
23
24
|
|
|
@@ -28,6 +29,7 @@ Use this skill when you need to:
|
|
|
28
29
|
- monitor execution status for long-running jobs
|
|
29
30
|
- create separate Cloud conversations for parallel or background work
|
|
30
31
|
- access sandbox agent-server endpoints once a conversation is running
|
|
32
|
+
- start or inspect conversations on a local Agent Canvas backend or local agent server
|
|
31
33
|
|
|
32
34
|
## Auth
|
|
33
35
|
|
|
@@ -73,6 +75,72 @@ agent_server_url = conversation_url.rsplit("/api/conversations", 1)[0]
|
|
|
73
75
|
|
|
74
76
|
If those fields are not present on the conversation record, list/search sandboxes (`GET /api/v1/sandboxes/search`) and use the sandbox referenced by the conversation to locate the agent server URL + session key.
|
|
75
77
|
|
|
78
|
+
### Local Agent Canvas backend
|
|
79
|
+
|
|
80
|
+
Use the local backend flow only for local Agent Canvas / agent-server development, such as `agent-canvas`, `agent-canvas --backend-only`, or `npm run dev` with ingress at `http://localhost:8000`. This calls the agent server directly with `X-Session-API-Key`. It is not an automation, and it is different from OpenHands Cloud delegation through `POST /api/v1/app-conversations`, which uses Bearer auth against the Cloud app API and may return asynchronous start-task records.
|
|
81
|
+
|
|
82
|
+
When Agent Canvas runs locally, the launcher uses `LOCAL_BACKEND_API_KEY` when it is set. Otherwise it generates and persists the session API key at `~/.openhands/agent-canvas/api-key.txt`. Set `OH_SESSION_API_KEY_PATH` to override the persisted key path. Never print, log, or paste the actual key; use command substitution or an environment variable in examples and scripts.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
LOCAL_AGENT_SERVER_URL="${LOCAL_AGENT_SERVER_URL:-http://localhost:8000}"
|
|
86
|
+
SESSION_API_KEY="${LOCAL_BACKEND_API_KEY:-$(cat "${OH_SESSION_API_KEY_PATH:-$HOME/.openhands/agent-canvas/api-key.txt}")}"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Check the local server before creating a backend conversation:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
curl -sS "${LOCAL_AGENT_SERVER_URL}/server_info" \
|
|
93
|
+
-H "X-Session-API-Key: ${SESSION_API_KEY}"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Start a backend conversation with `POST /api/conversations`. Include the agent settings and workspace expected by that backend. Local agent-server calls use an explicit `workspace` such as `{"kind": "LocalWorkspace", "working_dir": "/workspace"}`; Cloud app-conversation delegation instead uses app-server fields such as `selected_repository` and `selected_branch`. If you are starting the conversation from an existing Agent Canvas session, pass through the current configured settings or encrypted settings rather than hard-coding secrets into scripts.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
CONVERSATION_JSON=$(curl -sS -X POST "${LOCAL_AGENT_SERVER_URL}/api/conversations" \
|
|
100
|
+
-H "X-Session-API-Key: ${SESSION_API_KEY}" \
|
|
101
|
+
-H "Content-Type: application/json" \
|
|
102
|
+
-d @- <<'JSON'
|
|
103
|
+
{
|
|
104
|
+
"agent": {
|
|
105
|
+
"kind": "Agent",
|
|
106
|
+
"llm": {
|
|
107
|
+
"model": "your-model-provider/your-model-name",
|
|
108
|
+
"api_key": "**********"
|
|
109
|
+
},
|
|
110
|
+
"tools": [
|
|
111
|
+
{"name": "terminal"},
|
|
112
|
+
{"name": "file_editor"},
|
|
113
|
+
{"name": "task_tracker"}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
"workspace": {"kind": "LocalWorkspace", "working_dir": "/workspace"},
|
|
117
|
+
"initial_message": {
|
|
118
|
+
"content": [{"text": "Summarize the current workspace."}],
|
|
119
|
+
"run": true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
JSON
|
|
123
|
+
)
|
|
124
|
+
CONVERSATION_ID=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"${CONVERSATION_JSON}")
|
|
125
|
+
printf 'Conversation: %s/api/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Poll status and inspect recent events:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}" \
|
|
132
|
+
-H "X-Session-API-Key: ${SESSION_API_KEY}"
|
|
133
|
+
|
|
134
|
+
curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}/events/search?limit=20&sort_order=TIMESTAMP_DESC" \
|
|
135
|
+
-H "X-Session-API-Key: ${SESSION_API_KEY}"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If the same base URL serves the Agent Canvas UI, the browser route is:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
printf '%s/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
|
|
142
|
+
```
|
|
143
|
+
|
|
76
144
|
## Common V1 app server endpoints
|
|
77
145
|
|
|
78
146
|
The following are the main endpoints implemented in the minimal client:
|
|
@@ -389,11 +457,12 @@ See also:
|
|
|
389
457
|
|
|
390
458
|
## Source of truth
|
|
391
459
|
|
|
392
|
-
This skill is aligned against the current
|
|
460
|
+
This skill is aligned against the current OpenHands API docs and implementation:
|
|
393
461
|
|
|
394
462
|
- `OpenHands/docs/openhands/usage/cloud/cloud-api.mdx`
|
|
463
|
+
- `OpenHands/docs/openhands/usage/agent-canvas/backend-setup/local.mdx`
|
|
464
|
+
- `OpenHands/docs/sdk/arch/agent-server.mdx`
|
|
395
465
|
- `OpenHands/docs/openhands/usage/api/v1.mdx`
|
|
396
466
|
- `OpenHands/OpenHands/openhands/app_server/v1_router.py`
|
|
397
467
|
- `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_router.py`
|
|
398
468
|
- `OpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_models.py`
|
|
399
|
-
|
|
@@ -106,6 +106,8 @@ def fire_callback(status="COMPLETED", error=None):
|
|
|
106
106
|
|
|
107
107
|
Entrypoint must be `python3 main.py` (no `setup.sh` needed). Wrap your main logic in `try/except` and call `fire_callback("FAILED", str(e))` in the except block.
|
|
108
108
|
|
|
109
|
+
**State persistence between runs** — polling automations that track a "last processed" timestamp or active conversation IDs must use the built-in KV store rather than local files. Local files are lost when a run ends on a cloud pod. The KV store is available when `AUTOMATION_KV_TOKEN` is injected into the run environment. See `references/custom-automation.md#state-persistence-kv-store` for ready-to-copy `kv_get` / `kv_set` / `load_state` / `save_state` helpers.
|
|
110
|
+
|
|
109
111
|
---
|
|
110
112
|
|
|
111
113
|
## Authentication
|
|
@@ -871,5 +873,5 @@ The **prompt preset** is the right default for genuinely agent-shaped work — a
|
|
|
871
873
|
|
|
872
874
|
## Reference Files
|
|
873
875
|
|
|
874
|
-
- **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate or recommend the custom path (including for deterministic / cost-sensitive tasks per rule 0). Only *implement* a custom automation after the user agrees to that path.
|
|
876
|
+
- **`references/custom-automation.md`** — Detailed guide for custom automations: tarball uploads, code structure (SDK and no-LLM), state persistence via the KV store, environment variables, validation rules, and complete examples. Consult this whenever you need to evaluate or recommend the custom path (including for deterministic / cost-sensitive tasks per rule 0). Only *implement* a custom automation after the user agrees to that path.
|
|
875
877
|
- **`references/ab-testing.md`** — A/B testing for plugin automations: defining variants with weights, experiment configuration, variant selection logic, observability via conversation tags, and complete examples. Consult this when a user wants to compare plugin versions or configurations.
|
|
@@ -439,6 +439,170 @@ def fire_callback(status: str = "COMPLETED", error: str | None = None) -> None:
|
|
|
439
439
|
|
|
440
440
|
---
|
|
441
441
|
|
|
442
|
+
## State Persistence (KV Store)
|
|
443
|
+
|
|
444
|
+
Polling automations that run on a schedule need to remember state between runs — for example, the timestamp of the last processed event, or which conversation IDs are currently active. Storing this in a local file does not work on cloud deployments where each run may land on a fresh pod.
|
|
445
|
+
|
|
446
|
+
The automation service provides a built-in key-value store scoped per-automation. It is available in every run when the service is configured with `AUTOMATION_KV_SECRET`. Detect availability by checking for `AUTOMATION_KV_TOKEN` in the environment.
|
|
447
|
+
|
|
448
|
+
### KV Store API
|
|
449
|
+
|
|
450
|
+
| Endpoint | Method | Description |
|
|
451
|
+
|----------|--------|-------------|
|
|
452
|
+
| `/v1/kv/{key}` | GET | Get value (404 if not found) |
|
|
453
|
+
| `/v1/kv/{key}` | PUT | Set value (201 on create, 200 on update) |
|
|
454
|
+
| `/v1/kv/{key}` | DELETE | Delete key |
|
|
455
|
+
| `/v1/kv/{key}/incr` | POST | Atomic integer increment |
|
|
456
|
+
| `/v1/kv/{key}/decr` | POST | Atomic integer decrement |
|
|
457
|
+
| `/v1/kv/{key}/rpush` | POST | Append item to a list |
|
|
458
|
+
| `/v1/kv/{key}/lpop` | POST | Pop item from front of a list |
|
|
459
|
+
|
|
460
|
+
**Authentication:** `Authorization: Bearer $AUTOMATION_KV_TOKEN`
|
|
461
|
+
|
|
462
|
+
**Base URL:** `$AUTOMATION_API_URL` (e.g., `https://app.all-hands.dev/api/automation`)
|
|
463
|
+
|
|
464
|
+
Values are arbitrary JSON (dict, list, number, string). All keys are isolated per-automation — different automations cannot access each other's data.
|
|
465
|
+
|
|
466
|
+
### KV Store Helpers
|
|
467
|
+
|
|
468
|
+
Copy these helpers into any deterministic script that needs state persistence:
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
import json, os, urllib.error, urllib.request
|
|
472
|
+
|
|
473
|
+
_KV_TOKEN = os.environ.get("AUTOMATION_KV_TOKEN", "")
|
|
474
|
+
_KV_BASE = os.environ.get("AUTOMATION_API_URL", "").rstrip("/")
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def kv_available() -> bool:
|
|
478
|
+
"""Return True when the KV store is reachable in this run."""
|
|
479
|
+
return bool(_KV_TOKEN and _KV_BASE)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def kv_get(key: str):
|
|
483
|
+
"""Fetch a value from the KV store. Returns None if the key does not exist."""
|
|
484
|
+
req = urllib.request.Request(
|
|
485
|
+
f"{_KV_BASE}/v1/kv/{key}",
|
|
486
|
+
headers={"Authorization": f"Bearer {_KV_TOKEN}"},
|
|
487
|
+
)
|
|
488
|
+
try:
|
|
489
|
+
with urllib.request.urlopen(req) as r:
|
|
490
|
+
return json.loads(r.read())["value"]
|
|
491
|
+
except urllib.error.HTTPError as exc:
|
|
492
|
+
if exc.code == 404:
|
|
493
|
+
return None
|
|
494
|
+
raise
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def kv_set(key: str, value) -> None:
|
|
498
|
+
"""Write a value to the KV store."""
|
|
499
|
+
req = urllib.request.Request(
|
|
500
|
+
f"{_KV_BASE}/v1/kv/{key}",
|
|
501
|
+
data=json.dumps(value).encode(),
|
|
502
|
+
headers={
|
|
503
|
+
"Authorization": f"Bearer {_KV_TOKEN}",
|
|
504
|
+
"Content-Type": "application/json",
|
|
505
|
+
},
|
|
506
|
+
method="PUT",
|
|
507
|
+
)
|
|
508
|
+
with urllib.request.urlopen(req) as r:
|
|
509
|
+
r.read()
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Load / Save Pattern
|
|
513
|
+
|
|
514
|
+
For polling scripts that maintain a single state document, use a KV-first pattern with a local-file fallback so the script also works in local/dev environments where the KV store is not configured:
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
import json, os, urllib.error, urllib.request
|
|
518
|
+
from pathlib import Path
|
|
519
|
+
|
|
520
|
+
_KV_TOKEN = os.environ.get("AUTOMATION_KV_TOKEN", "")
|
|
521
|
+
_KV_BASE = os.environ.get("AUTOMATION_API_URL", "").rstrip("/")
|
|
522
|
+
_STATE_KEY = "state"
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def kv_available() -> bool:
|
|
526
|
+
return bool(_KV_TOKEN and _KV_BASE)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def kv_get(key: str):
|
|
530
|
+
req = urllib.request.Request(
|
|
531
|
+
f"{_KV_BASE}/v1/kv/{key}",
|
|
532
|
+
headers={"Authorization": f"Bearer {_KV_TOKEN}"},
|
|
533
|
+
)
|
|
534
|
+
try:
|
|
535
|
+
with urllib.request.urlopen(req) as r:
|
|
536
|
+
return json.loads(r.read())["value"]
|
|
537
|
+
except urllib.error.HTTPError as exc:
|
|
538
|
+
if exc.code == 404:
|
|
539
|
+
return None
|
|
540
|
+
raise
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def kv_set(key: str, value) -> None:
|
|
544
|
+
req = urllib.request.Request(
|
|
545
|
+
f"{_KV_BASE}/v1/kv/{key}",
|
|
546
|
+
data=json.dumps(value).encode(),
|
|
547
|
+
headers={
|
|
548
|
+
"Authorization": f"Bearer {_KV_TOKEN}",
|
|
549
|
+
"Content-Type": "application/json",
|
|
550
|
+
},
|
|
551
|
+
method="PUT",
|
|
552
|
+
)
|
|
553
|
+
with urllib.request.urlopen(req) as r:
|
|
554
|
+
r.read()
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _state_file_path() -> Path:
|
|
558
|
+
workspace = os.environ.get("WORKSPACE_BASE", "")
|
|
559
|
+
if workspace:
|
|
560
|
+
root = Path(workspace).resolve().parent.parent
|
|
561
|
+
else:
|
|
562
|
+
root = Path.home() / ".openhands" / "workspaces"
|
|
563
|
+
state_dir = root / "automation-state"
|
|
564
|
+
state_dir.mkdir(parents=True, exist_ok=True)
|
|
565
|
+
payload = json.loads(os.environ.get("AUTOMATION_EVENT_PAYLOAD", "{}"))
|
|
566
|
+
automation_id = payload.get("automation_id", "default")
|
|
567
|
+
return state_dir / f"my_poller_{automation_id}.json"
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _default_state() -> dict:
|
|
571
|
+
return {"version": 1, "last_poll": None}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def load_state() -> dict:
|
|
575
|
+
if kv_available():
|
|
576
|
+
data = kv_get(_STATE_KEY)
|
|
577
|
+
if data is not None:
|
|
578
|
+
print("State loaded from KV store")
|
|
579
|
+
return data
|
|
580
|
+
return _default_state()
|
|
581
|
+
path = _state_file_path()
|
|
582
|
+
if path.exists():
|
|
583
|
+
try:
|
|
584
|
+
return json.loads(path.read_text())
|
|
585
|
+
except Exception as exc:
|
|
586
|
+
print(f"Warning: state file unreadable ({exc}); starting fresh")
|
|
587
|
+
return _default_state()
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def save_state(state: dict) -> None:
|
|
591
|
+
if kv_available():
|
|
592
|
+
kv_set(_STATE_KEY, state)
|
|
593
|
+
print("State saved to KV store")
|
|
594
|
+
return
|
|
595
|
+
path = _state_file_path()
|
|
596
|
+
tmp = path.with_suffix(".tmp")
|
|
597
|
+
tmp.write_text(json.dumps(state, indent=2))
|
|
598
|
+
tmp.replace(path)
|
|
599
|
+
print(f"State saved to {path}")
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
> **Why a single document?** The KV store uses a single-document model under the hood (all keys for an automation share one encrypted row). Storing your entire state under a single key like `"state"` is the most efficient pattern — it avoids multiple round-trips and ensures atomic reads and writes.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
442
606
|
## Environment Variables
|
|
443
607
|
|
|
444
608
|
The automation service injects these environment variables into every run:
|
|
@@ -451,6 +615,8 @@ The automation service injects these environment variables into every run:
|
|
|
451
615
|
| `AUTOMATION_CALLBACK_API_KEY` | — | Bearer token for the completion callback POST |
|
|
452
616
|
| `AUTOMATION_RUN_ID` | — | Run ID to include in the completion callback payload |
|
|
453
617
|
| `AUTOMATION_EVENT_PAYLOAD` | — | JSON with trigger context: `automation_id`, `automation_name`, `trigger` type, and (for webhook runs) the raw event payload |
|
|
618
|
+
| `AUTOMATION_API_URL` | — | Base URL of the automation service (e.g., `https://app.all-hands.dev/api/automation`). Used to reach the KV store API |
|
|
619
|
+
| `AUTOMATION_KV_TOKEN` | — | Bearer token for the KV store API. Present whenever the service has `AUTOMATION_KV_SECRET` configured. Check for this variable to detect KV availability |
|
|
454
620
|
|
|
455
621
|
> **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
622
|
|
|
@@ -86,18 +86,21 @@ print("All done!")
|
|
|
86
86
|
- [Browser Session Recording](https://docs.openhands.dev/sdk/guides/browser-session-recording.md): Record and replay your agent's browser sessions using rrweb.
|
|
87
87
|
- [Browser Use](https://docs.openhands.dev/sdk/guides/agent-browser-use.md): Enable web browsing and interaction capabilities for your agent.
|
|
88
88
|
- [Context Condenser](https://docs.openhands.dev/sdk/guides/context-condenser.md): Manage agent memory by condensing conversation history to save tokens.
|
|
89
|
+
- [Conversation Goals](https://docs.openhands.dev/sdk/guides/agent-server/conversation-goals.md): Add a resumable goal strategy to a normal agent-server conversation.
|
|
89
90
|
- [Conversation with Async](https://docs.openhands.dev/sdk/guides/convo-async.md): Use async/await for concurrent agent operations and non-blocking execution.
|
|
90
91
|
- [Creating Custom Agent](https://docs.openhands.dev/sdk/guides/agent-custom.md): Learn how to design specialized agents with custom tool sets
|
|
91
92
|
- [Critic (Experimental)](https://docs.openhands.dev/sdk/guides/critic.md): Real-time evaluation of agent actions using an LLM-based critic model, with built-in iterative refinement.
|
|
92
93
|
- [Custom Tools](https://docs.openhands.dev/sdk/guides/custom-tools.md): Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs.
|
|
93
94
|
- [Custom Tools with Remote Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/custom-tools.md): Learn how to use custom tools with a remote agent server by building a custom base image that includes your tool implementations.
|
|
94
95
|
- [Custom Visualizer](https://docs.openhands.dev/sdk/guides/convo-custom-visualizer.md): Customize conversation visualization by creating custom visualizers or configuring the default visualizer.
|
|
96
|
+
- [Deferred Init (Warm-Pool)](https://docs.openhands.dev/sdk/guides/agent-server/deferred-init.md): Pre-warm agent-server pods before a user is matched, then activate them at runtime with POST /api/init.
|
|
95
97
|
- [Docker Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox.md): Run agent server in isolated Docker containers for security and reproducibility.
|
|
96
98
|
- [Exception Handling](https://docs.openhands.dev/sdk/guides/llm-error-handling.md): Provider‑agnostic exceptions raised by the SDK and recommended patterns for handling them.
|
|
97
99
|
- [FAQ](https://docs.openhands.dev/sdk/faq.md): Frequently asked questions about the OpenHands SDK
|
|
98
100
|
- [File-Based Agents](https://docs.openhands.dev/sdk/guides/agent-file-based.md): Define specialized sub-agents as simple Markdown files with YAML frontmatter — no Python code required.
|
|
99
101
|
- [Fork a Conversation](https://docs.openhands.dev/sdk/guides/convo-fork.md): Branch off an existing conversation for follow-up exploration without contaminating the original.
|
|
100
102
|
- [Getting Started](https://docs.openhands.dev/sdk/getting-started.md): Install the OpenHands SDK and build AI agents that write software.
|
|
103
|
+
- [Goal Completion Loop](https://docs.openhands.dev/sdk/guides/convo-goal.md): Drive a conversation toward a verifiable objective with a judge-driven, self-continuing completion loop.
|
|
101
104
|
- [GPT-5 Preset (ApplyPatchTool)](https://docs.openhands.dev/sdk/guides/llm-gpt5-preset.md): Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool.
|
|
102
105
|
- [Hello World](https://docs.openhands.dev/sdk/guides/hello-world.md): The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task.
|
|
103
106
|
- [Hooks](https://docs.openhands.dev/sdk/guides/hooks.md): Use lifecycle hooks to observe, log, and customize agent execution.
|
|
@@ -109,11 +112,12 @@ print("All done!")
|
|
|
109
112
|
- [LLM Registry](https://docs.openhands.dev/sdk/guides/llm-registry.md): Dynamically select and configure language models using the LLM registry.
|
|
110
113
|
- [LLM Streaming](https://docs.openhands.dev/sdk/guides/llm-streaming.md): Stream LLM responses token-by-token for real-time display and interactive user experiences.
|
|
111
114
|
- [LLM Subscriptions](https://docs.openhands.dev/sdk/guides/llm-subscriptions.md): Use your ChatGPT Plus/Pro subscription to access Codex models without consuming API credits.
|
|
112
|
-
- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md):
|
|
115
|
+
- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Install and run an OpenHands Agent Server on your machine, then connect to it from the SDK.
|
|
113
116
|
- [Metrics Tracking](https://docs.openhands.dev/sdk/guides/metrics.md): Track token usage, costs, and latency metrics for your agents.
|
|
114
117
|
- [Model Context Protocol](https://docs.openhands.dev/sdk/guides/mcp.md): Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically.
|
|
115
118
|
- [Model Routing](https://docs.openhands.dev/sdk/guides/llm-routing.md): Route agent's LLM requests to different models.
|
|
116
119
|
- [Observability & Tracing](https://docs.openhands.dev/sdk/guides/observability.md): Enable OpenTelemetry tracing to monitor and debug your agent's execution with tools like Laminar, MLflow, Honeycomb, or any OTLP-compatible backend.
|
|
120
|
+
- [OpenAI-Compatible Endpoint](https://docs.openhands.dev/sdk/guides/agent-server/openai-gateway.md): Call an OpenHands agent-server through the OpenAI Chat Completions protocol.
|
|
117
121
|
- [OpenHands Cloud Workspace](https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace.md): Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.
|
|
118
122
|
- [Overview](https://docs.openhands.dev/sdk/guides/agent-server/overview.md): Run agents on remote servers with isolated workspaces for production deployments.
|
|
119
123
|
- [Parallel Tool Execution](https://docs.openhands.dev/sdk/guides/parallel-tool-execution.md): Execute multiple tools concurrently within a single LLM response to improve throughput for independent operations.
|
|
@@ -180,7 +184,6 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
|
|
|
180
184
|
- [`40_acp_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/40_acp_agent_example.py)
|
|
181
185
|
- [`41_task_tool_set.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/41_task_tool_set.py)
|
|
182
186
|
- [`42_file_based_subagents.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/42_file_based_subagents.py)
|
|
183
|
-
- [`43_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/43_mixed_marketplace_skills)
|
|
184
187
|
- [`44_model_switching_in_convo.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/44_model_switching_in_convo.py)
|
|
185
188
|
- [`45_parallel_tool_execution.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/45_parallel_tool_execution.py)
|
|
186
189
|
- [`46_agent_settings.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/46_agent_settings.py)
|
|
@@ -191,6 +194,7 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
|
|
|
191
194
|
- [`51_agent_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/51_agent_hooks)
|
|
192
195
|
- [`52_dynamic_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/52_dynamic_workflow.py)
|
|
193
196
|
- [`53_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/53_client_defined_tools.py)
|
|
197
|
+
- [`54_goal_completion_loop.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/54_goal_completion_loop.py)
|
|
194
198
|
|
|
195
199
|
### [`02_remote_agent_server/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server)
|
|
196
200
|
|
|
@@ -209,6 +213,7 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
|
|
|
209
213
|
- [`13_workspace_get_llm.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py)
|
|
210
214
|
- [`14_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/14_client_defined_tools.py)
|
|
211
215
|
- [`15_openai_compatible_gateway.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/15_openai_compatible_gateway.py)
|
|
216
|
+
- [`16_deferred_init.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/16_deferred_init.py)
|
|
212
217
|
- [`hook_scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/hook_scripts)
|
|
213
218
|
- [`scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/scripts)
|
|
214
219
|
|
|
@@ -230,4 +235,5 @@ Source: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/
|
|
|
230
235
|
- [`01_loading_agentskills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/01_loading_agentskills)
|
|
231
236
|
- [`02_loading_plugins`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/02_loading_plugins)
|
|
232
237
|
- [`03_managing_installed_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/03_managing_installed_skills)
|
|
238
|
+
- [`04_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/04_mixed_marketplace_skills)
|
|
233
239
|
|
|
@@ -27,8 +27,10 @@ This skill is activated by keywords:
|
|
|
27
27
|
- **Efficient polling**: single `search.messages` call for multi-channel user
|
|
28
28
|
tokens with `search:read`; falls back to one `conversations.history` call
|
|
29
29
|
per channel for bot tokens
|
|
30
|
-
- **
|
|
31
|
-
|
|
30
|
+
- **Triggered follow-ups**: Slack thread replies that repeat the trigger phrase
|
|
31
|
+
are forwarded to the existing OpenHands conversation
|
|
32
|
+
- **Watch window**: completed conversations keep watching their Slack thread for
|
|
33
|
+
short follow-up periods, with quiet threads backed off automatically
|
|
32
34
|
- **Reaction acknowledgement**: adds a 👀 to every message containing the
|
|
33
35
|
trigger phrase
|
|
34
36
|
- **Conversation link**: posts a link to the new conversation in the Slack
|
|
@@ -79,9 +81,11 @@ Each cron run (every minute):
|
|
|
79
81
|
3. Creates an OpenHands conversation with the message and recent channel
|
|
80
82
|
context as the initial prompt; posts a link to the conversation in the
|
|
81
83
|
Slack thread
|
|
82
|
-
4. Forwards
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
4. Forwards triggered replies in tracked threads to the existing conversation,
|
|
85
|
+
while ignoring replies that do not contain the trigger phrase
|
|
86
|
+
5. Checks active conversations - posts the agent's final response with
|
|
87
|
+
Slack's `markdown_text` field so Markdown renders correctly, then watches
|
|
88
|
+
briefly for triggered follow-up replies
|
|
85
89
|
|
|
86
90
|
## See Also
|
|
87
91
|
|
|
@@ -23,9 +23,12 @@ detected it:
|
|
|
23
23
|
3. Posts a reply in the Slack thread with a link to the conversation.
|
|
24
24
|
|
|
25
25
|
On every subsequent run:
|
|
26
|
-
-
|
|
26
|
+
- New Slack thread replies are forwarded only when they contain the trigger
|
|
27
|
+
phrase, so unrelated conversation in the thread is ignored.
|
|
27
28
|
- When the conversation finishes (or errors), the agent's final response is
|
|
28
29
|
posted back to the Slack thread.
|
|
30
|
+
- Completed conversations stay in a short follow-up watch window, allowing
|
|
31
|
+
triggered Slack replies to continue the same OpenHands conversation.
|
|
29
32
|
|
|
30
33
|
> **Local mode only.** This automation targets the local OpenHands setup
|
|
31
34
|
> (`dev:automation` stack). A cloud/webhook-based variant is out of scope here.
|
|
@@ -235,20 +238,27 @@ Each cron run executes `main.py`, which runs **10 polling iterations** (every
|
|
|
235
238
|
- User token + `search:read` + > 1 channel → single `search.messages` call
|
|
236
239
|
(searches for the trigger phrase across all channels).
|
|
237
240
|
- Otherwise → one `conversations.history` call per channel.
|
|
238
|
-
4. **Fetches thread replies** -
|
|
241
|
+
4. **Fetches due thread replies** - polls at most one tracked thread per
|
|
242
|
+
iteration using per-thread exponential backoff to stay within Slack rate
|
|
243
|
+
limits.
|
|
239
244
|
5. **Processes messages** in chronological order:
|
|
240
245
|
- Skips messages already in `processed_ts` (dedup across the overlap window).
|
|
241
246
|
- Skips bot messages and any `ts` in `bot_message_ts`.
|
|
242
|
-
- Reply in a tracked thread
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
- Reply in a tracked thread whose text contains the trigger phrase → forwards
|
|
248
|
+
a follow-up request to the existing conversation and resets the follow-up
|
|
249
|
+
watch window. Replies without the trigger phrase are marked processed and
|
|
250
|
+
ignored.
|
|
251
|
+
- Contains trigger phrase outside a tracked conversation → 👀 reaction, create
|
|
252
|
+
a new conversation, post link.
|
|
245
253
|
- Thread replies: agent receives full thread history for context.
|
|
246
254
|
- Root messages: agent receives the trigger text only.
|
|
247
255
|
6. **Checks conversation statuses** - for each active conversation where
|
|
248
256
|
`time.time() - last_activity > 15 s`:
|
|
249
257
|
- If status is `idle`, `finished`, `error`, or `stuck` → fetch the agent's
|
|
250
258
|
final response via `/api/conversations/{id}/agent_final_response` and post
|
|
251
|
-
it to the Slack thread
|
|
259
|
+
it to the Slack thread using Slack's `markdown_text` field so Markdown
|
|
260
|
+
formatting renders correctly. Mark the record `watching` for five minutes
|
|
261
|
+
so triggered follow-up replies can continue the same conversation.
|
|
252
262
|
7. **Advances `last_poll`** to `now - 10 s` (overlap window prevents boundary
|
|
253
263
|
races). If a conversation creation failed, pins `last_poll` further back to
|
|
254
264
|
retry on the next iteration.
|
|
@@ -198,10 +198,12 @@ With < 10 channels polled every minute:
|
|
|
198
198
|
| Method | Tier | Calls/min | Headroom |
|
|
199
199
|
|--------|------|-----------|---------|
|
|
200
200
|
| `conversations.history` | Tier 3 | ≤ 10 | Comfortable |
|
|
201
|
-
| `conversations.replies` | Tier 3 | ≤
|
|
201
|
+
| `conversations.replies` | Tier 3 | ≤ 1 due tracked thread per iteration | Throttled with per-thread backoff |
|
|
202
202
|
| `search.messages` | Tier 2 | 1 | Fine |
|
|
203
203
|
| `reactions.add` | Tier 2 | ≤ triggers/min | Fine |
|
|
204
204
|
| `chat.postMessage` | Tier 3 | ≤ triggers + summaries | Fine |
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
The script surfaces Slack HTTP 429 responses with `Retry-After`. Thread reply
|
|
207
|
+
polling catches that signal, delays the affected thread, and increases its
|
|
208
|
+
per-thread backoff. Other Slack methods still fail fast so the next cron tick can
|
|
209
|
+
retry with the persisted state.
|