@openhands/extensions 0.19.0 → 0.20.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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.19.0"
2
+ ".": "0.20.0"
3
3
  }
package/AGENTS.md CHANGED
@@ -106,7 +106,7 @@ When editing or adding skills in this repo, follow these rules (and add new skil
106
106
  - If you change a skill’s behavior or scope, update its `README.md` (if present) accordingly.
107
107
  - If you change top-level documentation, ensure links still resolve.
108
108
  - `integrations/catalog/*.json` is the single hand-authored source of truth consumed by `@openhands/extensions`; adding or editing an integration should require changing exactly one JSON file in that directory. Do not reintroduce `integrations/integration-catalog.json`, separate provider files, per-language catalog duplicates, or provider-specific runtime code. Run `npm run build:integrations` after catalog edits to regenerate `integrations/catalog-index.js`, which statically imports each individual JSON file for the JS package. The Python package includes the same individual JSON files via wheel data and reads them directly. Agent-canvas and integrations-hub import this package directly, so integration marketplace fixes belong here rather than in app-local constants. When upstream MCP projects move repos, verify both `docsUrl` and the connection option (`transport`, `command`/`args`, or URL), not just links. JS exposes `listIntegrationCatalog({ mcp, oauth })` / `getIntegrationCatalogEntry`; Python mirrors with `list_integration_catalog_models(mcp=, oauth=)` / `get_integration_catalog_entry_model`, which return validated Pydantic models (the raw-dict accessors were deprecated in 0.10.0 and removed in 0.12.0). The legacy provider-catalog compatibility layer and `managedConnectorMigration` / `legacyScopeBundles` / `canonicalServerUrl` / `errorHints` mechanisms were removed intentionally; providers declare only standard OAuth config as integration data.
109
- - `automations/catalog/<id>/` follows the same rule, as a directory per automation so an automation can ship the scripts it uploads to the automations service alongside its metadata. `manifest.json` is the single hand-authored file, carrying both the card metadata and an optional nested `setup` block (the extension-owned configuration experience). Every entry is validated against `automations/catalog.schema.json`. Run `npm run build:automations` after catalog edits to regenerate `automations/catalog-index.js`. JS exposes `listAutomationCatalog()` / `getAutomationCatalogEntry(id)` alongside `AUTOMATION_CATALOG`. The governing rule is that a manifest states only what varies between automations, and states it once; anything derivable is absent and the host generates it. Derived, so never written in a manifest: the command that launches the card (read from the `triggers:` frontmatter of the skill named by `skill`, which defaults to `id` and is stated only where the two differ - editing an automation must never require editing a skill), the setup route (`/automations/new/<id>`), the trigger kinds a deployment must support (the keys of `setup.form.triggers`), schedule limits and timezone lists (the `cron` and `timezone` field types), the preflight call, the create request's `name`/`repos`/`trigger` (rebuilt from the entry name, the repo-picker field and the keys and fields under `setup.form.triggers`, where a field is named after the property it fills), the mapping from a rejected payload path back to the input at fault, the review screen, the create endpoint, the post-success navigation, and the analytics stages. `requires.integrations` is keyed by integration id and lives on the entry, not in `setup`, because a card lists its integrations whether or not it ships a setup flow; there is no key anywhere for a credential, because the credential comes with the connection. `setup.form` splits inputs into `triggers` (keyed by trigger kind - what decides when the automation runs) and `args` (everything else), both keyed by field name. `setup` declares a `prompt` (plus a `filter` for an event trigger) when `mode` is `direct`, and a `message` when it is `assisted`. Contract fixtures are test vectors, not catalog data: they live in `tests/fixtures/automations/`, are optional per automation, and are reachable only through the `@openhands/extensions/testing/automations/*.json` subpath - never from the runtime entry point.
109
+ - `automations/catalog/<id>/` follows the same rule, as a directory per automation so an automation can ship the scripts it uploads to the automations service alongside its metadata. `manifest.json` is the single hand-authored file, carrying both the card metadata and an optional nested `setup` block (the extension-owned configuration experience). Every entry is validated against `automations/catalog.schema.json`. Run `npm run build:automations` after catalog edits to regenerate `automations/catalog-index.js`. JS exposes `listAutomationCatalog()` / `getAutomationCatalogEntry(id)` alongside `AUTOMATION_CATALOG`. The governing rule is that a manifest states only what varies between automations, and states it once; anything derivable is absent and the host generates it. Derived, so never written in a manifest: the command that launches the card (read from the `triggers:` frontmatter of the skill named by `skill`, which defaults to `id` and is stated only where the two differ - editing an automation must never require editing a skill), the setup route (`/automations/new/<id>`), the trigger variants a deployment can offer (the keys of `setup.form.triggers`; multiple keys mean any supported selected variant can run), schedule limits, timezone lists, event choices, and model profiles (the `cron`, `timezone`, `event-source`, `event-type`, and `llm-profile` field types), the preflight call, the create request's `name`/`repos`/`model`/`timeout`/`trigger` (rebuilt from same-named form fields when present, the repo-picker field, and the selected key and fields under `setup.form.triggers`, where a field is named after the property it fills), the mapping from a rejected payload path back to the input at fault, the review screen, the create endpoint, the post-success navigation, and the analytics stages. `requires.integrations` is keyed by integration id and lives on the entry, not in `setup`, because a card lists its integrations whether or not it ships a setup flow; there is no key anywhere for a credential, because the credential comes with the connection. `setup.form` splits inputs into `triggers` (keyed by trigger kind - what decides when the automation runs) and `args` (everything else), both keyed by field name. `setup` declares a `prompt` (plus a `filter` for an event trigger) when `mode` is `direct`, and a `message` when it is `assisted`. Contract fixtures are test vectors, not catalog data: they live in `tests/fixtures/automations/`, are optional per automation, and are reachable only through the `@openhands/extensions/testing/automations/*.json` subpath - never from the runtime entry point.
110
110
  - For Python test runs, prefer `uv sync --group test` followed by `uv run pytest -q`; the full suite depends on `openhands-sdk`, which is not available in the base environment.
111
111
  - Agent-driven plugins (for example `plugins/pr-review` and `plugins/release-notes`) use `uv run --with openhands-sdk --with openhands-tools ...` and require an `LLM_API_KEY` in addition to `GITHUB_TOKEN`.
112
112
  - For OpenHands Cloud API guidance, automations, and CLI integration, use `plugins/openhands`. It is the canonical unified OpenHands plugin covering the V1 Cloud API, Automations API, and CLI. The individual skills (`skills/openhands-api`, `skills/openhands-automation`) are also available standalone.
@@ -42,16 +42,17 @@ two records of the same fact from drifting apart, and it is why the file is as s
42
42
  | The setup route | `/automations/new/<id>` |
43
43
  | The integrations the card lists | `requires.integrations`, which every entry carries |
44
44
  | The capabilities endpoint | The host queries it for every automation |
45
- | The trigger kinds a deployment must support | The keys of `setup.form.triggers` |
46
- | Schedule limits and the timezone list | The `cron` and `timezone` field types |
47
- | Local validation rules | The `required` flag and `constraints` on each field |
45
+ | The trigger variants a deployment can offer | The keys of `setup.form.triggers`; an entry with multiple keys is usable when at least one variant is supported |
46
+ | Schedule limits, timezone list, event choices, and model profiles | The `cron`, `timezone`, `event-source`, `event-type`, and `llm-profile` field types |
47
+ | Local validation rules | The `required` flag and `constraints` on each field, applied to the selected trigger variant |
48
48
  | The preflight call | `POST /v1/validate` with the entry id, the create endpoint, and the rendered payload |
49
- | The created automation's name | The entry's `name`, plus the repository that was picked |
50
- | `repos` in the create request | The repo-picker field, its declared `provider`, and a field named `ref` if there is one |
51
- | `trigger` in the create request | The key under `form.triggers`, and the fields under it named after trigger properties |
49
+ | The created automation's name | A form field named `name`, or the entry's `name` plus the repository that was picked |
50
+ | `repos` in the create request | The repo-picker field, its declared `provider`, and a non-empty field named `ref` if there is one |
51
+ | `model` and `timeout` in the create request | Same-named form fields when they have values |
52
+ | `trigger` in the create request | The selected key under `form.triggers`, and the fields under it named after trigger properties |
52
53
  | Which input a rejected payload path belongs to | Rebuilding that body with each field standing in for its own value |
53
54
  | The review screen | The fields and their labels |
54
- | The create endpoint | `POST /v1/preset/prompt`, or `POST /v1` for an entry that ships a bundle |
55
+ | The create endpoint | `POST /v1/preset/prompt`, `POST /v1/preset/plugin`, or `POST /v1` for an uploaded or bundled tarball |
55
56
  | The files a bundle packs | `setup.bundle.files`, read from this repository at build time |
56
57
  | Where a success navigates | The created automation, or the started conversation |
57
58
  | The analytics stages | The same stages for every automation |
@@ -64,7 +65,7 @@ checks them against the recorded fixtures, so these deletions stay honest rather
64
65
  ```jsonc
65
66
  {
66
67
  "id": "github-pr-reviewer",
67
- "name": "GitHub Code Review Agent",
68
+ "name": "GitHub code review",
68
69
  "category": "Code review",
69
70
  "description": "...",
70
71
  "requires": {
@@ -97,32 +98,40 @@ three entries whose names differ from their skill state it. The command that lau
97
98
  repeated here: it lives once, in the skill's own `triggers:` frontmatter, and the skills catalog exposes it.
98
99
  That way a skill can rename its trigger without leaving a stale copy behind in this catalog.
99
100
 
100
- `setup` is optional. Three of eight entries carry one today. It never repeats `id`, `name`, `category`, or
101
- `description`. `setup.version` selects how the block is interpreted; a future format ships a new constant.
101
+ `setup` is optional. Entries that carry one never repeat `id`, `name`, `category`, or `description`.
102
+ `setup.version` selects how the block is interpreted; a future format ships a new constant.
102
103
 
103
104
  ### Triggers and args
104
105
 
105
106
  `form` separates the two things a user is configuring, and both are keyed by field name:
106
107
 
107
108
  - **`form.triggers`** decides *when* the automation runs, keyed by trigger kind (`cron` or `event`).
108
- `github-pr-reviewer` and `github-repo-monitor` each ask for a schedule and a timezone.
109
- - **`form.args`** is everything else: the arguments to the automation itself, such as the repository to
110
- clone and the tone of the review.
109
+ If an entry declares more than one key, the host renders those keys as selectable variants and validates
110
+ only the selected variant's fields before building the payload.
111
+ - **`form.args`** is everything else that is common to every creation path, such as the automation name,
112
+ model profile, or timeout.
113
+ - **`setup.actions`** is optional and declares user-selectable creation paths for one card. The host renders
114
+ the common form plus the selected action's `args`, checks that action's `features`, and creates through
115
+ the endpoint implied by the action key: `prompt` -> `/v1/preset/prompt`, `plugin` -> `/v1/preset/plugin`,
116
+ and `upload` -> `/v1/uploads` followed by `/v1`.
111
117
 
112
118
  An assisted entry declares no triggers, because the trigger is settled during the conversation.
113
119
 
114
120
  ### What the form produces
115
121
 
116
- - **`mode: "direct"`** declares a `prompt`: what the automation is told to do. The rest of the create
117
- request restates the form, so it is not written out. An event trigger also declares a `filter`, because
118
- composing form values into a JMESPath expression is the one part of an event trigger that cannot be read
119
- off the form.
122
+ - **`mode: "direct"`** declares exactly one creation archetype: a `prompt`, a repository-shipped `bundle`,
123
+ or `actions`. A prompt creates through `/v1/preset/prompt`. A bundle is packed and uploaded by the host,
124
+ then creates through `/v1`. An actions block lets the user choose between prompt, plugin, and uploaded
125
+ tarball paths in one card. The rest of the create request restates the form, so it is not written out. A
126
+ form field named `name`, `model`, or `timeout` fills the matching top-level create property. An event
127
+ trigger may also declare a `filter`, because composing form values into a JMESPath expression is the one
128
+ part of an event trigger that cannot always be read off the form.
120
129
  - **`mode: "assisted"`** declares a `message`: setup context handed to an agent conversation that finishes
121
130
  the job. The command that opens that conversation comes from the skill, so it is not repeated here.
122
131
 
123
132
  A direct entry may also declare a `message`. It is the seed for the fallback conversation the host offers
124
- when the deployment cannot run the direct path - a deployment whose capabilities lack the entry's trigger
125
- kind or required features. The same 2000-character cap applies, and because the fallback fires before the
133
+ when the deployment cannot run the direct path - a deployment whose capabilities lack every declared trigger
134
+ variant or required features. The same 2000-character cap applies, and because the fallback fires before the
126
135
  form is trustworthy, a direct `message` should not reference `{{form.*}}`.
127
136
 
128
137
  A `repo-picker` may declare `multiple: true`, and then it collects several
@@ -135,17 +144,17 @@ repository when there is one and after the count when there are several, since a
135
144
  list of names does not fit a name.
136
145
 
137
146
  A form field is named after the property it fills. `schedule` and `timezone` under `triggers.cron` become
138
- `trigger.schedule` and `trigger.timezone`; `on` under `triggers.event` becomes `trigger.on`; a field named
139
- `ref` becomes `repos[].ref`. Any other field under a trigger kind, such as a phrase to match, is an input
140
- to `filter` rather than a trigger property.
147
+ `trigger.schedule` and `trigger.timezone`; `source` and `on` under `triggers.event` become `trigger.source`
148
+ and `trigger.on`; a field named `ref` becomes `repos[].ref` when it has a value. Any other field under a
149
+ trigger kind, such as a phrase to match, is an input to `filter` rather than a trigger property.
141
150
 
142
151
  ### Entries that ship a script
143
152
 
144
- `mode: "direct"` produces either a `prompt` or a `bundle`, never both. A prompt
145
- is the right shape when the automation *is* the judgement: the agent reads the
146
- prompt and does the work. A bundle is the right shape when most of what the
147
- automation does is deterministic machinery - polling, dedupe, state, fixed API
148
- calls - and the agent is needed only for the part that genuinely needs judgement.
153
+ `mode: "direct"` produces exactly one of `prompt`, `bundle`, or `actions`. A
154
+ prompt is the right shape when the automation *is* the judgement: the agent
155
+ reads the prompt and does the work. A bundle is the right shape when most of
156
+ what the automation does is deterministic machinery - polling, dedupe, state,
157
+ fixed API calls - and the agent is needed only for the part that genuinely needs judgement.
149
158
  `github-pr-reviewer` is the first: its script owns discovery, label-event
150
159
  dedupe, per-repo state and the review checkout, and starts a conversation only
151
160
  once a pull request actually needs reviewing.
@@ -188,7 +197,10 @@ Two things follow from the archetype rather than being stated:
188
197
  A bundle declares `requires.features: ["customTarball"]`: a deployment that
189
198
  cannot run a client-supplied tarball cannot run the entry, whatever trigger kinds
190
199
  it offers. It never declares `repos` - the raw create endpoint has no such field,
191
- and a bundle fetches what it needs itself.
200
+ and a bundle fetches what it needs itself. In an `actions` entry, creation-path
201
+ features move down to each action (`presetPrompt`, `presetPlugin`, or
202
+ `customTarball`) so a deployment can offer the supported choices without needing
203
+ every creation path.
192
204
 
193
205
  ### Format constraints
194
206
 
@@ -208,17 +220,19 @@ trust boundary. It enforces:
208
220
  Placeholders are namespaced and the schema rejects any other namespace: `{{form.*}}` for what the user
209
221
  entered and `{{automation.*}}` for the entry itself. There is deliberately no secrets namespace.
210
222
 
211
- ### The three archetypes
223
+ ### The archetypes
212
224
 
213
225
  | Entry | Archetype | Trigger | Produces |
214
226
  | --- | --- | --- | --- |
215
227
  | `github-pr-reviewer` | Direct scheduled, script bundle | `cron` | an upload, then a create payload |
216
228
  | `github-repo-monitor` | Direct scheduled | `cron` | a create payload |
229
+ | `qa-changes` | Direct event | `event` | a create payload |
230
+ | `custom-automation` | Direct custom action with prompt, plugin, or upload variants | `cron` or `event` | a create payload, or an upload then create payload |
217
231
  | `incident-retrospective-drafter` | Assisted conversation | decided during the conversation | a seed message |
218
232
 
219
- An event archetype is still expressible - `github-repo-monitor` used one until its deployment could no
220
- longer receive webhooks, and was converted to the polled `cron` form. The schema keeps supporting `event`
221
- triggers for deployments that can. See the `event` key under `setup.form.triggers` in `catalog.schema.json`.
233
+ An entry can declare both `cron` and `event` under `setup.form.triggers`. The host treats those keys as
234
+ selectable variants and creates the payload from the selected one; a deployment that only supports one of
235
+ the variants can still offer that supported path.
222
236
 
223
237
  The assisted archetype has no payload and no preflight, because at the end of its flow no automation exists
224
238
  yet. The agent creates it during the conversation, and the service validates it there. That is the defining
@@ -300,9 +314,11 @@ import scenarios from "@openhands/extensions/testing/automations/github-pr-revie
300
314
  must result. That pairing is the contract: form shape and API shape genuinely differ, the create endpoint
301
315
  is declared `extra="forbid"`, and a mapping mistake is a 422 discovered only at creation time.
302
316
 
303
- Each scenario carries whichever blocks apply: `formValues`, `integrationState`, `localValidation`,
304
- `preflight`, `create`, `conversation`, `expectedFieldErrors`, `expectedPrerequisiteOutcome`.
305
- `capabilities.json` holds three deployment shapes so the unsupported paths have coverage too.
317
+ Each scenario carries whichever blocks apply: `formValues`, `selectedTrigger`, `integrationState`,
318
+ `localValidation`, `preflight`, `create`, `conversation`, `expectedFieldErrors`,
319
+ `expectedPrerequisiteOutcome`. `selectedTrigger` is required when an entry declares multiple trigger
320
+ variants and the scenario records a create or preflight payload. `capabilities.json` holds deployment
321
+ shapes so the unsupported paths have coverage too.
306
322
 
307
323
  Beyond the derivation checks, every request body here has been verified against the live Pydantic models in
308
324
  `OpenHands/automation`:
@@ -359,7 +375,7 @@ questions to this work. What changed and why:
359
375
  | `analytics` | Removed. The same stages fire for every automation, so they belong in shared host code. |
360
376
  | `workflow.steps` | Removed. It restated which keys were present, creating a second source of truth that could contradict the file. |
361
377
  | `form.intent: "seed"` | Removed. Derivable from `setup.mode: "assisted"`. |
362
- | `triggerKindsAnyOf` | Removed. The keys of `form.triggers` are the trigger kinds. |
378
+ | `triggerKindsAnyOf` | Removed. The keys of `form.triggers` are the available trigger variants, and multiple keys already mean the host can offer any supported one. |
363
379
  | `{{form.filledCount}}` | Removed. It overloaded the `form` namespace with a computed value that names no field. |
364
380
  | `submit.message` | Kept as `setup.message`, capped at 2000 characters, so seed messages cannot grow back into the giant runtime prompts that recommended automation cards were already fixed to stop sending. |
365
381
 
@@ -0,0 +1,229 @@
1
+ {
2
+ "id": "custom-automation",
3
+ "name": "Custom automation",
4
+ "category": "Custom",
5
+ "icon": "sparkles",
6
+ "description": "Create a custom automation from a prompt, plugin-powered prompt, or uploaded script tarball.",
7
+ "requires": {
8
+ "integrations": {
9
+ "github": {
10
+ "message": "Optional. Used when you choose a GitHub repository to clone or trigger from GitHub events.",
11
+ "required": false
12
+ }
13
+ }
14
+ },
15
+ "popularityRank": 99,
16
+ "estimatedSetupMinutes": 5,
17
+ "skill": "openhands-automation",
18
+ "exampleImplementation": "Action: a user-selected prompt, plugin, or uploaded tarball creation path\nTrigger: a user-selected cron schedule or event source and event type\n\n1. Ask whether the automation should run a prompt, run a prompt with plugins, or upload a tarball.\n2. Render only the fields for the selected creation path.\n3. Create prompt automations through /v1/preset/prompt, plugin automations through /v1/preset/plugin, and uploaded tarball automations by uploading to /v1/uploads then creating through /v1.\n4. The selected trigger and common run settings are shared by all creation paths.",
19
+ "setup": {
20
+ "version": "1.0",
21
+ "mode": "direct",
22
+ "form": {
23
+ "note": "Choose whether this custom automation runs a plain prompt, a prompt with plugins, or your own uploaded tarball. Prompt and plugin runs use the LLM; uploaded tarballs are best for deterministic scripts.",
24
+ "triggers": {
25
+ "cron": {
26
+ "schedule": {
27
+ "type": "cron",
28
+ "label": "Schedule",
29
+ "help": "How often this automation should run.",
30
+ "default": "0 9 * * *",
31
+ "required": true
32
+ },
33
+ "timezone": {
34
+ "type": "timezone",
35
+ "label": "Timezone",
36
+ "help": "Timezone used to evaluate the schedule.",
37
+ "default": "UTC",
38
+ "required": true
39
+ }
40
+ },
41
+ "event": {
42
+ "source": {
43
+ "type": "event-source",
44
+ "label": "Event source",
45
+ "help": "Webhook source that starts the automation.",
46
+ "default": "github",
47
+ "required": true
48
+ },
49
+ "on": {
50
+ "type": "event-type",
51
+ "label": "Event type",
52
+ "help": "Event key or wildcard pattern that starts the automation.",
53
+ "placeholder": "issue_comment.created",
54
+ "required": true
55
+ },
56
+ "filter": {
57
+ "type": "textarea",
58
+ "label": "Event filter",
59
+ "help": "Optional JMESPath expression evaluated against the event payload.",
60
+ "placeholder": "glob(repository.full_name, 'OpenHands/*')",
61
+ "required": false,
62
+ "constraints": {
63
+ "maxLength": 2000
64
+ }
65
+ }
66
+ }
67
+ },
68
+ "args": {
69
+ "name": {
70
+ "type": "text",
71
+ "label": "Automation name",
72
+ "help": "Name shown in the automations list.",
73
+ "placeholder": "Daily repository summary",
74
+ "required": true,
75
+ "constraints": {
76
+ "minLength": 1,
77
+ "maxLength": 500
78
+ }
79
+ },
80
+ "model": {
81
+ "type": "llm-profile",
82
+ "label": "LLM profile",
83
+ "help": "Optional model profile used for automation runs. Leave blank to use the active profile.",
84
+ "required": false
85
+ },
86
+ "timeout": {
87
+ "type": "number",
88
+ "label": "Timeout (seconds)",
89
+ "help": "Maximum time a single run may take. Leave blank for the service default.",
90
+ "required": false,
91
+ "constraints": {
92
+ "min": 1,
93
+ "max": 1800
94
+ }
95
+ }
96
+ }
97
+ },
98
+ "actions": {
99
+ "prompt": {
100
+ "label": "Prompt",
101
+ "help": "Run a natural-language prompt with the default OpenHands agent.",
102
+ "features": [
103
+ "presetPrompt"
104
+ ],
105
+ "args": {
106
+ "prompt": {
107
+ "type": "textarea",
108
+ "label": "Prompt",
109
+ "help": "Instructions the agent follows every time the automation runs.",
110
+ "placeholder": "Summarize recent repository activity and post the result to Slack.",
111
+ "required": true,
112
+ "constraints": {
113
+ "minLength": 1,
114
+ "maxLength": 50000
115
+ }
116
+ },
117
+ "repository": {
118
+ "type": "repo-picker",
119
+ "label": "Repository",
120
+ "help": "Optional GitHub repository to clone before each run.",
121
+ "provider": "github",
122
+ "required": false
123
+ },
124
+ "ref": {
125
+ "type": "text",
126
+ "label": "Branch or ref",
127
+ "help": "Branch, tag, or commit to check out when a repository is selected.",
128
+ "placeholder": "main",
129
+ "required": false,
130
+ "constraints": {
131
+ "minLength": 1,
132
+ "maxLength": 255
133
+ }
134
+ }
135
+ },
136
+ "prompt": "{{form.prompt}}"
137
+ },
138
+ "plugin": {
139
+ "label": "Plugin",
140
+ "help": "Run a prompt with one or more OpenHands plugins loaded.",
141
+ "features": [
142
+ "presetPlugin"
143
+ ],
144
+ "args": {
145
+ "plugins": {
146
+ "type": "plugin-sources",
147
+ "label": "Plugins",
148
+ "help": "One or more plugin sources to load. Each source may include an optional ref and repository path.",
149
+ "required": true
150
+ },
151
+ "prompt": {
152
+ "type": "textarea",
153
+ "label": "Prompt",
154
+ "help": "Instructions the agent follows after loading the plugins.",
155
+ "placeholder": "Use the plugin commands to inspect the repository and summarize findings.",
156
+ "required": true,
157
+ "constraints": {
158
+ "minLength": 1,
159
+ "maxLength": 50000
160
+ }
161
+ },
162
+ "repository": {
163
+ "type": "repo-picker",
164
+ "label": "Repository",
165
+ "help": "Optional GitHub repository to clone before each run.",
166
+ "provider": "github",
167
+ "required": false
168
+ },
169
+ "ref": {
170
+ "type": "text",
171
+ "label": "Branch or ref",
172
+ "help": "Branch, tag, or commit to check out when a repository is selected.",
173
+ "placeholder": "main",
174
+ "required": false,
175
+ "constraints": {
176
+ "minLength": 1,
177
+ "maxLength": 255
178
+ }
179
+ }
180
+ },
181
+ "prompt": "{{form.prompt}}",
182
+ "plugins": "{{form.plugins}}"
183
+ },
184
+ "upload": {
185
+ "label": "Upload tarball",
186
+ "help": "Upload a .tar or .tar.gz containing the automation code to run.",
187
+ "features": [
188
+ "customTarball"
189
+ ],
190
+ "args": {
191
+ "tarball": {
192
+ "type": "tarball-upload",
193
+ "label": "Tarball",
194
+ "help": "A .tar or .tar.gz archive containing the automation code. Maximum size is enforced by the automation service.",
195
+ "required": true
196
+ },
197
+ "entrypoint": {
198
+ "type": "text",
199
+ "label": "Entrypoint",
200
+ "help": "Command to run after the tarball is extracted.",
201
+ "default": "python3 main.py",
202
+ "placeholder": "python3 main.py",
203
+ "required": true,
204
+ "constraints": {
205
+ "minLength": 1,
206
+ "maxLength": 500
207
+ }
208
+ },
209
+ "setupScript": {
210
+ "type": "text",
211
+ "label": "Setup script",
212
+ "help": "Optional path inside the tarball to run before the entrypoint.",
213
+ "placeholder": "setup.sh",
214
+ "required": false,
215
+ "constraints": {
216
+ "minLength": 1,
217
+ "maxLength": 255
218
+ }
219
+ }
220
+ },
221
+ "tarballPath": "{{form.tarball}}",
222
+ "entrypoint": "{{form.entrypoint}}",
223
+ "setupScript": "{{form.setupScript}}"
224
+ }
225
+ },
226
+ "filter": "{{form.filter}}",
227
+ "message": "This deployment cannot create custom automations directly. Set it up in this conversation instead: choose prompt, plugin, or uploaded tarball mode; confirm the selected trigger, model profile, timeout, and mode-specific fields; then create the automation."
228
+ }
229
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "github-issue-to-pr",
3
- "name": "GitHub Issue to PR Agent",
3
+ "name": "GitHub issue to PR",
4
4
  "category": "Software development",
5
5
  "description": "Watch for a configurable label on GitHub issues, implement the issue in a clone of the default branch, and open a pull request for each label event.",
6
6
  "requires": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "github-pr-reviewer",
3
3
  "version": "1.0.0",
4
- "name": "GitHub Code Review Agent",
4
+ "name": "GitHub code review",
5
5
  "category": "Code review",
6
6
  "description": "Watch for a configurable label on GitHub pull requests, inspect full PR and repository context, and post an AI review comment once per label event.",
7
7
  "requires": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "qa-changes",
3
- "name": "QA Changes Agent",
3
+ "name": "QA changes",
4
4
  "category": "Code review",
5
5
  "description": "When a pull request is opened for review, run the QA changes methodology — set up the environment, exercise the changed behavior as a real user would, and post the outcome by editing the PR description with a QA Agent section.",
6
6
  "requires": {
@@ -3,23 +3,24 @@
3
3
  // run: npm run build:automations
4
4
 
5
5
  import entry0 from "./catalog/github-pr-reviewer/manifest.json" with { type: "json" };
6
- import entry1 from "./catalog/github-repo-monitor/manifest.json" with { type: "json" };
7
- import entry2 from "./catalog/github-issue-to-pr/manifest.json" with { type: "json" };
8
- import entry3 from "./catalog/slack-standup-digest/manifest.json" with { type: "json" };
9
- import entry4 from "./catalog/slack-channel-monitor/manifest.json" with { type: "json" };
10
- import entry5 from "./catalog/linear-triage-assistant/manifest.json" with { type: "json" };
11
- import entry6 from "./catalog/linear-issue-to-github-pr/manifest.json" with { type: "json" };
12
- import entry7 from "./catalog/linear-issue-to-gitlab-mr/manifest.json" with { type: "json" };
13
- import entry8 from "./catalog/linear-issue-to-bitbucket-pr/manifest.json" with { type: "json" };
14
- import entry9 from "./catalog/jira-issue-to-pr/manifest.json" with { type: "json" };
15
- import entry10 from "./catalog/qa-changes/manifest.json" with { type: "json" };
16
- import entry11 from "./catalog/jira-issue-to-gitlab-mr/manifest.json" with { type: "json" };
17
- import entry12 from "./catalog/research-brief-writer/manifest.json" with { type: "json" };
18
- import entry13 from "./catalog/jira-issue-to-bitbucket-pr/manifest.json" with { type: "json" };
19
- import entry14 from "./catalog/github-agents-md-maintainer/manifest.json" with { type: "json" };
20
- import entry15 from "./catalog/upstream-fork-sync/manifest.json" with { type: "json" };
21
- import entry16 from "./catalog/incident-retrospective-drafter/manifest.json" with { type: "json" };
22
- import entry17 from "./catalog/news-digest/manifest.json" with { type: "json" };
6
+ import entry1 from "./catalog/custom-automation/manifest.json" with { type: "json" };
7
+ import entry2 from "./catalog/github-repo-monitor/manifest.json" with { type: "json" };
8
+ import entry3 from "./catalog/github-issue-to-pr/manifest.json" with { type: "json" };
9
+ import entry4 from "./catalog/slack-standup-digest/manifest.json" with { type: "json" };
10
+ import entry5 from "./catalog/slack-channel-monitor/manifest.json" with { type: "json" };
11
+ import entry6 from "./catalog/linear-triage-assistant/manifest.json" with { type: "json" };
12
+ import entry7 from "./catalog/linear-issue-to-github-pr/manifest.json" with { type: "json" };
13
+ import entry8 from "./catalog/linear-issue-to-gitlab-mr/manifest.json" with { type: "json" };
14
+ import entry9 from "./catalog/linear-issue-to-bitbucket-pr/manifest.json" with { type: "json" };
15
+ import entry10 from "./catalog/jira-issue-to-pr/manifest.json" with { type: "json" };
16
+ import entry11 from "./catalog/qa-changes/manifest.json" with { type: "json" };
17
+ import entry12 from "./catalog/jira-issue-to-gitlab-mr/manifest.json" with { type: "json" };
18
+ import entry13 from "./catalog/research-brief-writer/manifest.json" with { type: "json" };
19
+ import entry14 from "./catalog/jira-issue-to-bitbucket-pr/manifest.json" with { type: "json" };
20
+ import entry15 from "./catalog/github-agents-md-maintainer/manifest.json" with { type: "json" };
21
+ import entry16 from "./catalog/upstream-fork-sync/manifest.json" with { type: "json" };
22
+ import entry17 from "./catalog/incident-retrospective-drafter/manifest.json" with { type: "json" };
23
+ import entry18 from "./catalog/news-digest/manifest.json" with { type: "json" };
23
24
 
24
25
  export const AUTOMATION_CATALOG_ENTRIES = [
25
26
  entry0,
@@ -40,4 +41,5 @@ export const AUTOMATION_CATALOG_ENTRIES = [
40
41
  entry15,
41
42
  entry16,
42
43
  entry17,
44
+ entry18,
43
45
  ];