@openhands/extensions 0.6.0 → 0.7.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/.release-please-manifest.json +1 -1
- package/README.md +8 -6
- package/integrations/README.md +8 -2
- package/integrations/catalog/airtable.json +0 -24
- package/integrations/catalog/asana.json +1 -6
- package/integrations/catalog/bitbucket.json +1 -6
- package/integrations/catalog/box.json +1 -6
- package/integrations/catalog/canva.json +3 -7
- package/integrations/catalog/confluence.json +1 -6
- package/integrations/catalog/discord.json +1 -6
- package/integrations/catalog/figma.json +21 -27
- package/integrations/catalog/github.json +0 -9
- package/integrations/catalog/gmail.json +1 -6
- package/integrations/catalog/google-calendar.json +1 -6
- package/integrations/catalog/google-docs.json +1 -6
- package/integrations/catalog/google-drive.json +1 -6
- package/integrations/catalog/google-sheets.json +1 -6
- package/integrations/catalog/intercom.json +1 -6
- package/integrations/catalog/jira.json +1 -6
- package/integrations/catalog/linear.json +1 -24
- package/integrations/catalog/microsoft-outlook.json +1 -6
- package/integrations/catalog/microsoft-teams.json +1 -6
- package/integrations/catalog/miro.json +14 -10
- package/integrations/catalog/monday.json +14 -10
- package/integrations/catalog/netlify.json +1 -6
- package/integrations/catalog/notion.json +16 -23
- package/integrations/catalog/okta.json +1 -6
- package/integrations/catalog/onedrive.json +1 -6
- package/integrations/catalog/posthog.json +20 -20
- package/integrations/catalog/quickbooks.json +70 -22
- package/integrations/catalog/salesforce.json +18 -12
- package/integrations/catalog/sentry.json +12 -17
- package/integrations/catalog/sharepoint.json +1 -6
- package/integrations/catalog/stripe.json +15 -20
- package/integrations/catalog/supabase.json +16 -21
- package/integrations/catalog/trello.json +11 -8
- package/integrations/catalog/vercel.json +1 -6
- package/integrations/catalog/xero.json +1 -6
- package/integrations/catalog/zoom.json +1 -6
- package/integrations/catalog-index.js +64 -84
- package/integrations/catalog.schema.json +255 -0
- package/integrations/index.d.ts +26 -10
- package/integrations/index.js +18 -10
- package/marketplaces/openhands-extensions.json +26 -1
- package/package.json +1 -1
- package/plugins/pr-review/scripts/prompt.py +1 -1
- package/pyproject.toml +2 -1
- package/python/openhands_extensions/_version.py +2 -1
- package/skills/agent-canvas-environment/SKILL.md +74 -13
- package/skills/bitbucket/README.md +23 -40
- package/skills/bitbucket/SKILL.md +43 -31
- package/skills/bitbucket-cloud/.plugin/plugin.json +18 -0
- package/skills/bitbucket-cloud/README.md +15 -0
- package/skills/bitbucket-cloud/SKILL.md +46 -0
- package/skills/bitbucket-data-center/.plugin/plugin.json +19 -0
- package/skills/bitbucket-data-center/README.md +20 -0
- package/skills/bitbucket-data-center/SKILL.md +53 -0
- package/skills/code-review/SKILL.md +1 -1
- package/skills/code-review/references/supply-chain-security.md +2 -2
- package/skills/index.js +17 -5
- package/skills/openhands-sdk/SKILL.md +0 -1
- package/tests/test_catalog_schema.py +47 -0
- package/tests/test_catalogs.py +26 -0
- package/tests/test_integration_catalog_in_sync.py +73 -25
- package/integrations/catalog/clickup.json +0 -36
- package/integrations/catalog/dropbox.json +0 -38
- package/integrations/catalog/freshdesk.json +0 -38
- package/integrations/catalog/gitlab.json +0 -38
- package/integrations/catalog/mailchimp.json +0 -38
- package/integrations/catalog/pipedrive.json +0 -39
- package/integrations/catalog/plaid.json +0 -31
- package/integrations/catalog/servicenow.json +0 -39
- package/integrations/catalog/shopify.json +0 -38
- package/integrations/catalog/zendesk.json +0 -38
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/OpenHands/extensions/integrations/catalog.schema.json",
|
|
4
|
+
"title": "IntegrationCatalogEntry",
|
|
5
|
+
"description": "Strict schema for integrations/catalog/*.json. Mirrors integrations/index.d.ts and encodes the cross-field connectability rules the catalog contract depends on.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["id", "name", "description", "connectionOptions"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": { "type": "string", "minLength": 1 },
|
|
11
|
+
"name": { "type": "string", "minLength": 1 },
|
|
12
|
+
"description": { "type": "string", "minLength": 1 },
|
|
13
|
+
"categories": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"minItems": 1,
|
|
16
|
+
"items": { "type": "string", "minLength": 1 },
|
|
17
|
+
"uniqueItems": true
|
|
18
|
+
},
|
|
19
|
+
"appUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
20
|
+
"docsUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
21
|
+
"notes": { "type": "string", "minLength": 1 },
|
|
22
|
+
"iconBg": { "type": "string", "minLength": 1 },
|
|
23
|
+
"iconColor": { "type": "string", "minLength": 1 },
|
|
24
|
+
"logoUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
25
|
+
"keywords": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": { "type": "string", "minLength": 1 },
|
|
28
|
+
"uniqueItems": true
|
|
29
|
+
},
|
|
30
|
+
"popularityRank": { "type": "integer", "minimum": 0 },
|
|
31
|
+
"installHint": { "type": "string", "minLength": 1 },
|
|
32
|
+
"connectionOptions": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"minItems": 1,
|
|
35
|
+
"items": { "$ref": "#/$defs/connectionOption" }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"$defs": {
|
|
39
|
+
"marketplaceField": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"required": ["key", "label"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"key": { "type": "string", "minLength": 1 },
|
|
45
|
+
"label": { "type": "string", "minLength": 1 },
|
|
46
|
+
"type": { "enum": ["text", "password"] },
|
|
47
|
+
"placeholder": { "type": "string" },
|
|
48
|
+
"helperText": { "type": "string" },
|
|
49
|
+
"helperLink": { "type": "string", "pattern": "^https://" },
|
|
50
|
+
"required": { "type": "boolean" }
|
|
51
|
+
},
|
|
52
|
+
"allOf": [
|
|
53
|
+
{
|
|
54
|
+
"if": { "properties": { "type": { "const": "password" } } },
|
|
55
|
+
"then": {
|
|
56
|
+
"required": ["helperText"],
|
|
57
|
+
"properties": { "helperText": { "minLength": 1 } }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"oauthConfig": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"properties": {
|
|
66
|
+
"authorizationUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
67
|
+
"tokenUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
68
|
+
"scopes": {
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": { "type": "string", "minLength": 1 }
|
|
71
|
+
},
|
|
72
|
+
"optionalScopes": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": { "type": "string", "minLength": 1 }
|
|
75
|
+
},
|
|
76
|
+
"toolScopes": {
|
|
77
|
+
"type": "array",
|
|
78
|
+
"items": { "type": "string", "minLength": 1 }
|
|
79
|
+
},
|
|
80
|
+
"scopeSeparator": { "enum": ["space", "comma"] },
|
|
81
|
+
"pkce": { "type": "boolean" },
|
|
82
|
+
"clientAuthentication": { "enum": ["basic", "body", "none"] },
|
|
83
|
+
"registrationUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
84
|
+
"additionalAuthorizationParams": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": { "type": "string" }
|
|
87
|
+
},
|
|
88
|
+
"additionalTokenParams": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"additionalProperties": { "type": "string" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"authConfig": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"additionalProperties": false,
|
|
97
|
+
"required": ["strategy"],
|
|
98
|
+
"properties": {
|
|
99
|
+
"strategy": { "enum": ["none", "api_key", "bearer", "basic", "oauth2"] },
|
|
100
|
+
"authModes": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"items": { "enum": ["none", "api_key", "bearer", "basic", "oauth2"] },
|
|
103
|
+
"minItems": 1
|
|
104
|
+
},
|
|
105
|
+
"credentialLabel": { "type": "string", "minLength": 1 },
|
|
106
|
+
"credentialPlaceholder": { "type": "string", "minLength": 1 },
|
|
107
|
+
"credentialHelp": { "type": "string", "minLength": 1 },
|
|
108
|
+
"credentialSecretName": { "type": "string", "minLength": 1 },
|
|
109
|
+
"saveCredentialAsSecretByDefault": { "type": "boolean" },
|
|
110
|
+
"apiKeyHeaderName": { "type": "string", "minLength": 1 },
|
|
111
|
+
"apiKeyOptional": { "type": "boolean" },
|
|
112
|
+
"oauth": { "$ref": "#/$defs/oauthConfig" }
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"httpConfig": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"additionalProperties": false,
|
|
118
|
+
"properties": {
|
|
119
|
+
"apiBaseUrl": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
120
|
+
"openApiUrl": { "type": "string", "pattern": "^https://", "minLength": 8 }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"transport": {
|
|
124
|
+
"oneOf": [
|
|
125
|
+
{
|
|
126
|
+
"type": "object",
|
|
127
|
+
"additionalProperties": false,
|
|
128
|
+
"required": ["kind", "url"],
|
|
129
|
+
"properties": {
|
|
130
|
+
"kind": { "const": "shttp" },
|
|
131
|
+
"url": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
132
|
+
"apiKeyOptional": { "type": "boolean" },
|
|
133
|
+
"headerFields": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"items": { "$ref": "#/$defs/marketplaceField" }
|
|
136
|
+
},
|
|
137
|
+
"urlEditable": { "type": "boolean" }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "object",
|
|
142
|
+
"additionalProperties": false,
|
|
143
|
+
"required": ["kind", "url"],
|
|
144
|
+
"properties": {
|
|
145
|
+
"kind": { "const": "sse" },
|
|
146
|
+
"url": { "type": "string", "pattern": "^https://", "minLength": 8 },
|
|
147
|
+
"apiKeyOptional": { "type": "boolean" },
|
|
148
|
+
"headerFields": {
|
|
149
|
+
"type": "array",
|
|
150
|
+
"items": { "$ref": "#/$defs/marketplaceField" }
|
|
151
|
+
},
|
|
152
|
+
"urlEditable": { "type": "boolean" }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"type": "object",
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"required": ["kind", "serverName", "command", "args"],
|
|
159
|
+
"properties": {
|
|
160
|
+
"kind": { "const": "stdio" },
|
|
161
|
+
"serverName": { "type": "string", "minLength": 1 },
|
|
162
|
+
"command": { "type": "string", "minLength": 1 },
|
|
163
|
+
"args": {
|
|
164
|
+
"type": "array",
|
|
165
|
+
"items": { "type": "string" },
|
|
166
|
+
"minItems": 0
|
|
167
|
+
},
|
|
168
|
+
"envFields": {
|
|
169
|
+
"type": "array",
|
|
170
|
+
"items": { "$ref": "#/$defs/marketplaceField" }
|
|
171
|
+
},
|
|
172
|
+
"argFields": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": { "$ref": "#/$defs/marketplaceField" }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
"connectionOption": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"additionalProperties": false,
|
|
183
|
+
"required": ["id", "provider", "auth"],
|
|
184
|
+
"properties": {
|
|
185
|
+
"id": { "type": "string", "minLength": 1 },
|
|
186
|
+
"provider": { "enum": ["mcp", "http"] },
|
|
187
|
+
"transport": { "$ref": "#/$defs/transport" },
|
|
188
|
+
"http": { "$ref": "#/$defs/httpConfig" },
|
|
189
|
+
"auth": { "$ref": "#/$defs/authConfig" }
|
|
190
|
+
},
|
|
191
|
+
"allOf": [
|
|
192
|
+
{
|
|
193
|
+
"if": { "properties": { "provider": { "const": "mcp" } } },
|
|
194
|
+
"then": {
|
|
195
|
+
"required": ["transport"],
|
|
196
|
+
"not": { "required": ["http"] }
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"if": { "properties": { "provider": { "const": "http" } } },
|
|
201
|
+
"then": {
|
|
202
|
+
"required": ["http"],
|
|
203
|
+
"not": { "required": ["transport"] },
|
|
204
|
+
"properties": {
|
|
205
|
+
"http": {
|
|
206
|
+
"required": ["apiBaseUrl", "openApiUrl"],
|
|
207
|
+
"properties": {
|
|
208
|
+
"apiBaseUrl": { "type": "string", "minLength": 8 },
|
|
209
|
+
"openApiUrl": { "type": "string", "minLength": 8 }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"if": {
|
|
217
|
+
"properties": {
|
|
218
|
+
"provider": { "const": "http" },
|
|
219
|
+
"auth": { "properties": { "strategy": { "const": "api_key" } } }
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"then": {
|
|
223
|
+
"properties": {
|
|
224
|
+
"auth": { "required": ["apiKeyHeaderName"] }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"if": {
|
|
230
|
+
"properties": {
|
|
231
|
+
"provider": { "const": "http" },
|
|
232
|
+
"auth": { "properties": { "strategy": { "const": "oauth2" } } }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"then": {
|
|
236
|
+
"properties": {
|
|
237
|
+
"auth": {
|
|
238
|
+
"required": ["oauth"],
|
|
239
|
+
"properties": {
|
|
240
|
+
"oauth": {
|
|
241
|
+
"required": ["authorizationUrl", "tokenUrl"],
|
|
242
|
+
"properties": {
|
|
243
|
+
"authorizationUrl": { "type": "string", "minLength": 8 },
|
|
244
|
+
"tokenUrl": { "type": "string", "minLength": 8 }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
package/integrations/index.d.ts
CHANGED
|
@@ -15,11 +15,35 @@ export type IntegrationTransport =
|
|
|
15
15
|
kind: "shttp";
|
|
16
16
|
url: string;
|
|
17
17
|
apiKeyOptional?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Named request headers the user must supply (e.g. Datadog's
|
|
20
|
+
* `DD-API-KEY` / `DD-APPLICATION-KEY`). Values are sent verbatim as
|
|
21
|
+
* headers on every MCP request. The direct analog of stdio's
|
|
22
|
+
* `envFields`: each entry renders one input in the install modal,
|
|
23
|
+
* `type: "password"` entries are secret-saved by default, and
|
|
24
|
+
* `required` entries are validated before submit. Composes with the
|
|
25
|
+
* `api_key`/`bearer`/`basic` auth strategies (whose Bearer token is
|
|
26
|
+
* folded into `Authorization` separately); a header field with
|
|
27
|
+
* `key: "Authorization"` is redundant and should be avoided.
|
|
28
|
+
*/
|
|
29
|
+
headerFields?: MarketplaceField[];
|
|
30
|
+
/**
|
|
31
|
+
* When true, the install modal renders the URL as an editable input
|
|
32
|
+
* pre-filled with `url` instead of read-only. Use for servers whose
|
|
33
|
+
* host is region/account-specific (e.g. Datadog's
|
|
34
|
+
* `https://mcp.<site>.datadoghq.com/v1/mcp`); pair with the entry's
|
|
35
|
+
* `installHint` to tell users what to change.
|
|
36
|
+
*/
|
|
37
|
+
urlEditable?: boolean;
|
|
18
38
|
}
|
|
19
39
|
| {
|
|
20
40
|
kind: "sse";
|
|
21
41
|
url: string;
|
|
22
42
|
apiKeyOptional?: boolean;
|
|
43
|
+
/** See {@link IntegrationTransport} `shttp` `headerFields`. */
|
|
44
|
+
headerFields?: MarketplaceField[];
|
|
45
|
+
/** See {@link IntegrationTransport} `shttp` `urlEditable`. */
|
|
46
|
+
urlEditable?: boolean;
|
|
23
47
|
}
|
|
24
48
|
| {
|
|
25
49
|
kind: "stdio";
|
|
@@ -66,18 +90,9 @@ export interface IntegrationAuthConfig {
|
|
|
66
90
|
oauth?: IntegrationOAuthConfig;
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
export interface IntegrationHttpDefaultTool {
|
|
70
|
-
name: string;
|
|
71
|
-
description?: string;
|
|
72
|
-
method?: string;
|
|
73
|
-
path?: string;
|
|
74
|
-
scopes?: string[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
93
|
export interface IntegrationHttpConfig {
|
|
78
94
|
apiBaseUrl?: string;
|
|
79
95
|
openApiUrl?: string;
|
|
80
|
-
defaultTool?: IntegrationHttpDefaultTool;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
export interface IntegrationConnectionOption {
|
|
@@ -122,11 +137,12 @@ export const INTEGRATION_CATALOG: IntegrationCatalogEntry[];
|
|
|
122
137
|
/**
|
|
123
138
|
* Return the full integration catalog, optionally filtered by connector type.
|
|
124
139
|
* Reads the generated static import index over `integrations/catalog/<id>.json`.
|
|
125
|
-
* Returns
|
|
140
|
+
* Returns an independent copy, matching the Python read API.
|
|
126
141
|
*/
|
|
127
142
|
export function listIntegrationCatalog(
|
|
128
143
|
filter?: IntegrationCatalogFilter,
|
|
129
144
|
): IntegrationCatalogEntry[];
|
|
145
|
+
/** Return one integration catalog entry by id as an independent copy. */
|
|
130
146
|
export function getIntegrationCatalogEntry(
|
|
131
147
|
id: string,
|
|
132
148
|
): IntegrationCatalogEntry | undefined;
|
package/integrations/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { INTEGRATION_CATALOG_ENTRIES } from "./catalog-index.js";
|
|
9
9
|
|
|
10
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
10
11
|
const INTEGRATIONS = INTEGRATION_CATALOG_ENTRIES;
|
|
11
12
|
const INTEGRATION_BY_ID = new Map(INTEGRATIONS.map((entry) => [entry.id, entry]));
|
|
12
13
|
|
|
@@ -17,18 +18,25 @@ const entrySupportsOauth = (entry) =>
|
|
|
17
18
|
entry.connectionOptions.some((option) => option.auth?.strategy === "oauth2");
|
|
18
19
|
|
|
19
20
|
export const listIntegrationCatalog = (filter) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const entries =
|
|
22
|
+
!filter || (filter.mcp === undefined && filter.oauth === undefined)
|
|
23
|
+
? INTEGRATIONS
|
|
24
|
+
: INTEGRATIONS.filter((entry) => {
|
|
25
|
+
const mcpOk =
|
|
26
|
+
filter.mcp === undefined || entrySupportsMcp(entry) === filter.mcp;
|
|
27
|
+
const oauthOk =
|
|
28
|
+
filter.oauth === undefined ||
|
|
29
|
+
entrySupportsOauth(entry) === filter.oauth;
|
|
30
|
+
return mcpOk && oauthOk;
|
|
31
|
+
});
|
|
32
|
+
return clone(entries);
|
|
28
33
|
};
|
|
29
34
|
|
|
30
|
-
export const getIntegrationCatalogEntry = (id) =>
|
|
35
|
+
export const getIntegrationCatalogEntry = (id) => {
|
|
36
|
+
const entry = INTEGRATION_BY_ID.get(id);
|
|
37
|
+
return entry ? clone(entry) : undefined;
|
|
38
|
+
};
|
|
31
39
|
|
|
32
|
-
export const INTEGRATION_CATALOG = INTEGRATIONS;
|
|
40
|
+
export const INTEGRATION_CATALOG = clone(INTEGRATIONS);
|
|
33
41
|
|
|
34
42
|
export default INTEGRATION_CATALOG;
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
{
|
|
131
131
|
"name": "bitbucket",
|
|
132
132
|
"source": "./skills/bitbucket",
|
|
133
|
-
"description": "
|
|
133
|
+
"description": "Bitbucket integration hub. Detects whether the repository is on Bitbucket Cloud or Bitbucket Data Center and directs you to the matching detailed skill (bitbucket-cloud or bitbucket-data-center). Use for any Bitbucket repository or pull request task.",
|
|
134
134
|
"category": "integration",
|
|
135
135
|
"keywords": [
|
|
136
136
|
"bitbucket",
|
|
@@ -138,6 +138,31 @@
|
|
|
138
138
|
"pull-request"
|
|
139
139
|
]
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
"name": "bitbucket-cloud",
|
|
143
|
+
"source": "./skills/bitbucket-cloud",
|
|
144
|
+
"description": "Bitbucket Cloud (bitbucket.org) specifics — authenticate with BITBUCKET_TOKEN, use the REST API v2, workspace/repo_slug repositories, and the create_bitbucket_pr tool. Loaded on demand by the bitbucket skill once a Cloud environment is detected.",
|
|
145
|
+
"category": "integration",
|
|
146
|
+
"keywords": [
|
|
147
|
+
"bitbucket",
|
|
148
|
+
"bitbucket-cloud",
|
|
149
|
+
"git",
|
|
150
|
+
"pull-request"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "bitbucket-data-center",
|
|
155
|
+
"source": "./skills/bitbucket-data-center",
|
|
156
|
+
"description": "Bitbucket Data Center (self-hosted Bitbucket Server) specifics — authenticate with BITBUCKET_DATA_CENTER_TOKEN, use the REST API 1.0, PROJECT/repo_slug repositories, scm/ git remotes, and the create_bitbucket_data_center_pr tool. Loaded on demand by the bitbucket skill once a Data Center environment is detected.",
|
|
157
|
+
"category": "integration",
|
|
158
|
+
"keywords": [
|
|
159
|
+
"bitbucket",
|
|
160
|
+
"bitbucket-data-center",
|
|
161
|
+
"bitbucket-server",
|
|
162
|
+
"git",
|
|
163
|
+
"pull-request"
|
|
164
|
+
]
|
|
165
|
+
},
|
|
141
166
|
{
|
|
142
167
|
"name": "city-weather",
|
|
143
168
|
"source": "./plugins/city-weather",
|
package/package.json
CHANGED
|
@@ -76,7 +76,7 @@ PROMPT = """{skill_trigger}
|
|
|
76
76
|
|
|
77
77
|
When posting a review, keep the review body brief unless your active review instructions require a longer structured format.
|
|
78
78
|
|
|
79
|
-
For dependency update PRs, do **NOT** approve a target version that was published less than 7 days ago.
|
|
79
|
+
For dependency update PRs, do **NOT** approve a target version that was published less than 7 days ago. First-party packages maintained by the same organization as the reviewed repository are intentionally excluded from this 7-day waiting rule, but still scrutinize them for supply-chain risk.
|
|
80
80
|
|
|
81
81
|
Review the PR changes below and identify issues that need to be addressed.
|
|
82
82
|
|
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openhands-extensions"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "OpenHands extensions, plugins, and skills (Python bindings for the integration catalog)"
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
|
|
@@ -19,6 +19,7 @@ test = [
|
|
|
19
19
|
"pytest>=8.0",
|
|
20
20
|
"requests>=2.31",
|
|
21
21
|
"openhands-sdk>=0.3",
|
|
22
|
+
"jsonschema>=4.23",
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[tool.pytest.ini_options]
|
|
@@ -16,7 +16,8 @@ from importlib.metadata import PackageNotFoundError, version
|
|
|
16
16
|
|
|
17
17
|
#: Fallback used only when the package is not installed (no dist metadata).
|
|
18
18
|
#: Kept in lock-step with pyproject.toml/package.json by the version test.
|
|
19
|
-
|
|
19
|
+
# x-release-please-update-version
|
|
20
|
+
_FALLBACK_VERSION = "0.7.0"
|
|
20
21
|
|
|
21
22
|
try:
|
|
22
23
|
__version__: str = version("openhands-extensions")
|
|
@@ -50,12 +50,28 @@ HTTP `200` means the backend and key work.
|
|
|
50
50
|
|
|
51
51
|
Use `POST /api/conversations` with:
|
|
52
52
|
|
|
53
|
-
-
|
|
53
|
+
- the **encrypted** `agent_settings` from `GET /api/settings` (with `X-Expose-Secrets: encrypted`), which carries the real Fernet-encrypted `llm.api_key`, the existing `agent_context`, and the agent kind — so you never handle plaintext credentials and you don't drop the caller's skill/context config
|
|
54
|
+
- `secrets_encrypted: true` so the agent-server decrypts that `api_key` server-side
|
|
55
|
+
- the exec tool set merged into `agent_settings.tools` (and `task_tool_set` when you enable sub-agents)
|
|
56
|
+
- `tool_module_qualnames` for any non-SDK tools (e.g. `canvas_ui`)
|
|
57
|
+
- `agent_context.load_public_skills`/`load_user_skills`/`load_project_skills` set to `true` if the delegated agent should inherit bundled/user/project skills
|
|
54
58
|
- a fresh absolute workspace directory
|
|
55
59
|
- `initial_message.run: true`
|
|
56
60
|
- `worktree: false` when the workspace is already isolated
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
### Credential handling — important
|
|
63
|
+
|
|
64
|
+
`GET /api/settings` (default) **masks** every credential — `llm.api_key` comes back as the literal string `"**********"`. If you forward that verbatim, the new conversation authenticates with the placeholder and fails immediately with `LLMAuthenticationError` (`You must provide an API key`).
|
|
65
|
+
|
|
66
|
+
The supported way to obtain forwardable credentials is the **`X-Expose-Secrets: encrypted`** request header. With it, `/api/settings` returns the real `llm.api_key` as a **Fernet-encrypted token** (starts with `gAAAAA`) intended to be sent back to the server with `secrets_encrypted: true`; the agent-server's `decrypt_incoming_llm_secrets` decrypts it server-side. Do **not** read `~/.openhands/profiles/*.json` directly — that is brittle (the caller may not share the backend's home directory, `active_profile` may be null, the profile store may live elsewhere).
|
|
67
|
+
|
|
68
|
+
Two working approaches:
|
|
69
|
+
|
|
70
|
+
1. **`agent_profile_id` (simplest, but no tools)** — send only `agent_profile_id: "<uuid>"` (from `GET /api/agent-profiles` → the profile whose `id` equals `active_agent_profile_id` from `/api/settings`). The server resolves the LLM key + agent kind from the profile. Mutually exclusive with `agent`/`agent_settings`, and the `openhands` agent-profile schema forbids `tools`/`include_default_tools`, so the conversation gets **zero exec tools** this way. Use only when the task needs no tools.
|
|
71
|
+
|
|
72
|
+
2. **Encrypted `agent_settings` (full tools, preserves context)** — start from the encrypted `/api/settings` `agent_settings` payload, drop `schema_version` and `mcp_config` (to avoid MCP-connection failures at creation time), merge in the exec tool set and `load_*_skills` flags, and send with `secrets_encrypted: true`. This is the pattern for real delegated work.
|
|
73
|
+
|
|
74
|
+
Template (full tools, preserves context):
|
|
59
75
|
|
|
60
76
|
```bash
|
|
61
77
|
set -euo pipefail
|
|
@@ -70,26 +86,57 @@ test -n "$KEY" || { echo "No Agent Canvas session API key found" >&2; exit 1; }
|
|
|
70
86
|
WORKDIR="${WORKDIR:-$HOME/workspace/delegated/$(date +%Y%m%d-%H%M%S)}"
|
|
71
87
|
mkdir -p "$WORKDIR"
|
|
72
88
|
|
|
73
|
-
|
|
89
|
+
# Fetch the agent_settings with ENCRYPTED secrets exposed. This returns the
|
|
90
|
+
# real llm.api_key as a Fernet token (gAAAAA...) plus the existing
|
|
91
|
+
# agent_context/agent kind, so we preserve the caller's config and never
|
|
92
|
+
# handle plaintext credentials.
|
|
93
|
+
SETTINGS_JSON="$(curl -sS -H "X-Session-API-Key: $KEY" -H "X-Expose-Secrets: encrypted" "$BASE/api/settings")"
|
|
94
|
+
|
|
74
95
|
PROMPT='Write a complete, task-specific prompt here. Include repo, branch, constraints, validation, and expected report.'
|
|
75
96
|
|
|
76
97
|
PAYLOAD="$(jq -n --argjson settings "$SETTINGS_JSON" --arg prompt "$PROMPT" --arg workdir "$WORKDIR" '
|
|
77
|
-
|
|
98
|
+
# Start from the encrypted agent_settings so llm.api_key (Fernet token),
|
|
99
|
+
# agent_kind, and agent_context are preserved. Drop schema_version and
|
|
100
|
+
# mcp_config (MCP servers can fail to connect at creation time; the profile
|
|
101
|
+
# can be re-resolved later if needed).
|
|
102
|
+
def base_agent_settings:
|
|
78
103
|
($settings.agent_settings // {})
|
|
79
104
|
| del(.schema_version)
|
|
80
|
-
| .
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
| del(.mcp_config);
|
|
106
|
+
|
|
107
|
+
# Merge the exec tool set into the existing tools list. Include task_tool_set
|
|
108
|
+
# when sub-agents are enabled — enable_sub_agents alone does not expose the
|
|
109
|
+
# delegation tool; Agent Canvas adds task_tool_set for that.
|
|
110
|
+
def with_tools:
|
|
111
|
+
.tools = ((.tools // []) + [
|
|
112
|
+
{name: "terminal", params: {}},
|
|
113
|
+
{name: "file_editor", params: {}},
|
|
114
|
+
{name: "task_tracker", params: {}},
|
|
115
|
+
{name: "browser_tool_set", params: {}},
|
|
116
|
+
{name: "canvas_ui", params: {}}
|
|
117
|
+
] + (if .enable_sub_agents then [{name: "task_tool_set", params: {}}] else [] end)
|
|
118
|
+
| unique_by(.name));
|
|
119
|
+
|
|
120
|
+
# Preserve the existing agent_context and enable skill loading for the
|
|
121
|
+
# delegated agent (defaults are false, so set these explicitly).
|
|
122
|
+
def with_skill_loading:
|
|
123
|
+
.agent_context = ((.agent_context // {}) + {
|
|
124
|
+
load_public_skills: true,
|
|
125
|
+
load_user_skills: true,
|
|
126
|
+
load_project_skills: true
|
|
127
|
+
});
|
|
128
|
+
|
|
87
129
|
($settings.conversation_settings // {}) as $conv |
|
|
88
130
|
{
|
|
89
|
-
|
|
131
|
+
secrets_encrypted: true,
|
|
132
|
+
agent_settings: (base_agent_settings | with_tools | with_skill_loading),
|
|
133
|
+
tool_module_qualnames: { canvas_ui: "canvas_ui_tool" },
|
|
90
134
|
workspace: {kind: "LocalWorkspace", working_dir: $workdir},
|
|
91
135
|
confirmation_policy: {kind: "NeverConfirm"},
|
|
92
|
-
|
|
136
|
+
# Delegated tasks usually need more than the SDK default of 80 iterations;
|
|
137
|
+
# default to the caller's conversation_settings value (1000 in Agent Canvas)
|
|
138
|
+
# so long-running tasks aren't cut off prematurely. Override per-task if needed.
|
|
139
|
+
max_iterations: (($conv.max_iterations // 1000) | if . == null then 1000 else . end),
|
|
93
140
|
stuck_detection: true,
|
|
94
141
|
autotitle: true,
|
|
95
142
|
worktree: false,
|
|
@@ -107,6 +154,20 @@ curl -sS -X POST "$BASE/api/conversations" \
|
|
|
107
154
|
--data-binary "$PAYLOAD" | jq '{id, title, execution_status, workspace}'
|
|
108
155
|
```
|
|
109
156
|
|
|
157
|
+
Verify the new conversation actually has tools and is running (not errored):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
CID="<conversation_id>"
|
|
161
|
+
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID" \
|
|
162
|
+
| jq '{execution_status, tools: [.agent.tools[]?.name]}'
|
|
163
|
+
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID/events/search?limit=20" \
|
|
164
|
+
| jq '[.events[]? | select(.kind=="ConversationErrorEvent") | .code] // []'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`execution_status` should be `running`/`idle`/`finished` (not `error`), `tools` should list the exec tools, and there should be no `ConversationErrorEvent`.
|
|
168
|
+
|
|
169
|
+
If MCP servers configured in the profile are unreachable, conversation creation can fail with `MCP Connection Failure`; the template drops `mcp_config` from the forwarded `agent_settings` to avoid that.
|
|
170
|
+
|
|
110
171
|
Report both links:
|
|
111
172
|
|
|
112
173
|
- UI: `http://localhost:8000/conversations/<conversation_id>`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Bitbucket
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bitbucket integration **hub** skill. It detects whether the repository is on Bitbucket
|
|
4
|
+
Cloud or Bitbucket Data Center and directs the agent to the matching detailed skill.
|
|
4
5
|
|
|
5
6
|
## Triggers
|
|
6
7
|
|
|
@@ -9,42 +10,24 @@ This skill is activated by the following keywords:
|
|
|
9
10
|
- `bitbucket`
|
|
10
11
|
- `git`
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Atlassian's Bitbucket Cloud docs recommend avoiding long-lived credentials in the remote URL when possible. Their API token examples use either `https://{bitbucket_username}:{api_token}@...` or `https://x-bitbucket-api-token-auth:{api_token}@...`; OpenHands users should only construct those URLs on demand, with proper URL encoding.
|
|
36
|
-
|
|
37
|
-
Here are some instructions for pushing, but ONLY do this if the user asks you to:
|
|
38
|
-
* NEVER push directly to the `main` or `master` branch
|
|
39
|
-
* Git config (username and email) is pre-set. Do not modify.
|
|
40
|
-
* You may already be on a branch starting with `openhands-workspace`. Create a new branch with a better name before pushing.
|
|
41
|
-
* Use the `create_bitbucket_pr` tool to create a pull request, if you haven't already
|
|
42
|
-
* Once you've created your own branch or a pull request, continue to update it. Do NOT create a new one unless you are explicitly asked to. Update the PR title and description as necessary, but don't change the branch name.
|
|
43
|
-
* Use the main branch as the base branch, unless the user requests otherwise
|
|
44
|
-
* After opening or updating a pull request, send the user a short message with a link to the pull request.
|
|
45
|
-
* Do NOT mark a pull request as ready to review unless the user explicitly says so
|
|
46
|
-
* Do all of the above in as few steps as possible. E.g. you could push changes with one step by running the following bash commands:
|
|
47
|
-
```bash
|
|
48
|
-
git remote -v && git branch # to find the current org, repo and branch
|
|
49
|
-
git checkout -b create-widget && git add . && git commit -m "Create widget" && git push -u origin create-widget
|
|
50
|
-
```
|
|
13
|
+
## Why a hub skill?
|
|
14
|
+
|
|
15
|
+
Bitbucket ships as two products that behave differently — **Bitbucket Cloud**
|
|
16
|
+
(`bitbucket.org`) and **Bitbucket Data Center** (self-hosted Bitbucket Server). They use
|
|
17
|
+
different token environment variables, REST APIs, repository identifiers, git remote URL
|
|
18
|
+
formats, and pull request tools.
|
|
19
|
+
|
|
20
|
+
This hub triggers broadly (on `git`/`bitbucket`) so it loads for any Bitbucket task — even
|
|
21
|
+
when the words "data center" never appear. It then:
|
|
22
|
+
|
|
23
|
+
1. **Detects** the environment by checking which token variable is present
|
|
24
|
+
(`BITBUCKET_DATA_CENTER_TOKEN` → Data Center, else `BITBUCKET_TOKEN` → Cloud). The check
|
|
25
|
+
is case-insensitive so it is robust to env-var casing differences.
|
|
26
|
+
2. **Hands off** to the matching detailed skill via the `invoke_skill` tool:
|
|
27
|
+
- Bitbucket Cloud → [`bitbucket-cloud`](../bitbucket-cloud/)
|
|
28
|
+
- Bitbucket Data Center → [`bitbucket-data-center`](../bitbucket-data-center/)
|
|
29
|
+
|
|
30
|
+
The hub also carries a small quick-reference table as a fallback in case the detailed skill
|
|
31
|
+
cannot be loaded.
|
|
32
|
+
|
|
33
|
+
See [`SKILL.md`](SKILL.md) for the full content.
|