@openhands/extensions 0.15.0 → 0.16.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.15.0"
2
+ ".": "0.16.0"
3
3
  }
@@ -177,13 +177,26 @@ extension surface tomorrow) rather than between entries, so each is stated once
177
177
  Agent Canvas keeps its rendering components and reads every automation-specific datum from this file,
178
178
  falling back to its built-in defaults when the manifest is absent or fails admission:
179
179
 
180
- - **`routes`** - the list, setup, and detail routes. The host must have a registration serving each
181
- declared shape, so admission verifies they match what it mounted; the manifest is the single source for
182
- link construction.
183
- - **`navigation`** - the sidebar entry and the command-menu entry.
180
+ - **`routes`** - the list, setup, detail, and templates routes. The host must have a registration serving
181
+ each declared shape, so admission verifies they match what it mounted; the manifest is the single source
182
+ for link construction.
183
+ - **`navigation`** - the sidebar entry, the command-menu entry, and `subPages`: the ordered sub-page
184
+ navigation rendered inside the Automation interface, each item naming a `pages` entry with a label and
185
+ an icon slug from the host's closed icon map.
184
186
  - **`pages`** - page-identity copy: the list title and subtitle, the detail back label, the edit-dialog
185
- title. Generic chrome - buttons, toasts, empty states, validation sentences - stays host copy, rendered
186
- through the host's translations.
187
+ title, the templates title and description. Generic chrome - buttons, toasts, empty states, validation
188
+ sentences - stays host copy, rendered through the host's translations.
189
+ - **`pages.list.overview` / `filters` / `sort` / `insights`** - the list page's dashboard composition.
190
+ Every value here names something the host implements from a closed set, and the manifest picks and
191
+ captions it: `overview.tiles[].metric` names a host-computed value (`automations`, `needs-attention`,
192
+ `total-runs`, `average-duration`), filter option values name host predicates (`status`: enabled /
193
+ latest-run-failed / disabled; `trigger`: `event` matches event-triggered automations, `schedule`
194
+ everything else), `sort` values name host comparators, and `insights` captions the host's run-health
195
+ states and per-automation stats. The health precedence, the run sampling, the value formatting, the
196
+ relative-time rendering, and the filtered-empty state with its reset button are the host's - a manifest
197
+ cannot redefine them, only relabel what appears. Tile `detail` copy is plain substitution over the
198
+ metric's placeholder namespace (only the `automations` metric exposes `{{active}}`); `zeroDetail`
199
+ replaces `detail` while the value is zero.
187
200
  - **`docsUrl`** - the automations documentation link, prefix-pinned to docs.openhands.dev by schema.
188
201
  - **`attributes`** - the input surface of an existing Automation: which attributes can be set after
189
202
  creation, keyed by the runtime-model property the host sends (`name`, `prompt`, `model`, `timeout`,
@@ -300,8 +313,8 @@ Recorded rather than resolved, because they need an owner outside this contract:
300
313
  related fields onto one line, which reads better; if that matters, it is a host concern, not an entry's.
301
314
  - **Assisted-setup completion.** The assisted flow ends at a conversation, so the host cannot emit a
302
315
  completion event. Until something reports back, the ratio of direct to assisted setup is not measurable.
303
- - **Delete confirmation and per-run views.** `interface.json` models the list, setup, and detail routes
304
- and the settable attributes; deletion, run logs, and the dashboard sub-pages remain host-owned surfaces
305
- with no manifest data of their own yet.
316
+ - **Delete confirmation and per-run views.** `interface.json` models the routes, the settable attributes,
317
+ and the dashboard and templates sub-pages; deletion and run logs remain host-owned surfaces with no
318
+ manifest data of their own yet.
306
319
  - **Types from the schema.** `index.d.ts` is hand-written and mirrors `catalog.schema.json`. Generating it
307
320
  would remove that second source of truth.
@@ -130,17 +130,142 @@ export interface AutomationInterfaceRoutes {
130
130
  setup: string;
131
131
  /** Carries the `:automationId` segment the host substitutes. */
132
132
  detail: string;
133
+ /** The templates sub-page. Static: there is no parameter to substitute. */
134
+ templates: string;
135
+ }
136
+
137
+ /** An icon name from the host's closed icon map. */
138
+ export type AutomationIconSlug =
139
+ | "layout-dashboard"
140
+ | "sparkles"
141
+ | "bot"
142
+ | "circle-alert"
143
+ | "activity"
144
+ | "timer";
145
+
146
+ /** The pages a sub-page navigation item may point at. */
147
+ export type AutomationSubPageId = "list" | "templates";
148
+
149
+ export interface AutomationSubPageNavItem {
150
+ /** The `pages` entry this item navigates to; its route comes from `routes`. */
151
+ page: AutomationSubPageId;
152
+ label: string;
153
+ icon: AutomationIconSlug;
133
154
  }
134
155
 
135
156
  export interface AutomationInterfaceNavigation {
136
157
  sidebar: { label: string };
137
158
  commandMenu: { title: string; description: string; keywords: string };
159
+ /** The ordered sub-page navigation of the Automation interface. */
160
+ subPages: AutomationSubPageNavItem[];
161
+ }
162
+
163
+ /** A value the host computes; a tile picks and captions it, never defines it. */
164
+ export type AutomationOverviewMetric =
165
+ | "automations"
166
+ | "needs-attention"
167
+ | "total-runs"
168
+ | "average-duration";
169
+
170
+ /**
171
+ * One summary tile. `detail` captions the value; `zeroDetail`, when present,
172
+ * replaces it while the value is zero. Both are plain substitution over the
173
+ * metric's placeholder namespace: only the `automations` metric exposes
174
+ * `{{active}}`, so every other tile's copy is literal.
175
+ */
176
+ export interface AutomationOverviewTile {
177
+ metric: AutomationOverviewMetric;
178
+ label: string;
179
+ detail: string;
180
+ zeroDetail?: string;
181
+ icon: AutomationIconSlug;
182
+ }
183
+
184
+ export interface AutomationOverview {
185
+ /** Names the tiles section for assistive technology. */
186
+ label: string;
187
+ tiles: AutomationOverviewTile[];
188
+ }
189
+
190
+ export type AutomationStatusFilterValue =
191
+ | "all"
192
+ | "active"
193
+ | "failing"
194
+ | "disabled";
195
+ export type AutomationTriggerFilterValue = "all" | "schedule" | "event";
196
+
197
+ /**
198
+ * A filter dropdown. Values name predicates the host implements; the manifest
199
+ * supplies which appear and their labels. `label` is the control's accessible
200
+ * name. The `all` option is the host's default and reset target.
201
+ */
202
+ export interface AutomationStatusFilter {
203
+ id: "status";
204
+ label: string;
205
+ options: { value: AutomationStatusFilterValue; label: string }[];
206
+ }
207
+
208
+ export interface AutomationTriggerFilter {
209
+ id: "trigger";
210
+ label: string;
211
+ options: { value: AutomationTriggerFilterValue; label: string }[];
212
+ }
213
+
214
+ export type AutomationDashboardFilter =
215
+ | AutomationStatusFilter
216
+ | AutomationTriggerFilter;
217
+
218
+ /** A comparator the host implements, named from a closed set. */
219
+ export type AutomationSortValue = "last-run" | "runs" | "name";
220
+
221
+ export interface AutomationDashboardSort {
222
+ /** The control's accessible name. */
223
+ label: string;
224
+ options: { value: AutomationSortValue; label: string }[];
225
+ /** Must be one of the declared option values. */
226
+ default: AutomationSortValue;
227
+ }
228
+
229
+ /**
230
+ * Copy for the per-automation run insights on cards and rows. The states,
231
+ * precedence, sampling, and value formatting are the host's; the manifest
232
+ * names them.
233
+ */
234
+ export interface AutomationListInsights {
235
+ health: {
236
+ healthy: string;
237
+ failing: string;
238
+ running: string;
239
+ disabled: string;
240
+ neverRun: string;
241
+ checking: string;
242
+ };
243
+ lastRun: { label: string; never: string; justNow: string };
244
+ stats: { runs: string; recentSuccess: string; averageDuration: string };
245
+ }
246
+
247
+ /**
248
+ * The templates sub-page identity. Its body - the catalog cards and their
249
+ * launch behavior - is the host's existing catalog surface.
250
+ */
251
+ export interface AutomationTemplatesPage {
252
+ title: string;
253
+ description: string;
138
254
  }
139
255
 
140
256
  export interface AutomationInterfacePages {
141
- list: { title: string; subtitle: string };
257
+ list: {
258
+ title: string;
259
+ subtitle: string;
260
+ overview: AutomationOverview;
261
+ /** The filter dropdowns of the list page, in render order. */
262
+ filters: AutomationDashboardFilter[];
263
+ sort: AutomationDashboardSort;
264
+ insights: AutomationListInsights;
265
+ };
142
266
  detail: { backLabel: string };
143
267
  edit: { title: string };
268
+ templates: AutomationTemplatesPage;
144
269
  }
145
270
 
146
271
  export type AutomationAttributeType =
@@ -3,7 +3,8 @@
3
3
  "routes": {
4
4
  "list": "/automations",
5
5
  "setup": "/automations/new/:automationId",
6
- "detail": "/automations/:automationId"
6
+ "detail": "/automations/:automationId",
7
+ "templates": "/automations/templates"
7
8
  },
8
9
  "navigation": {
9
10
  "sidebar": { "label": "Automate" },
@@ -11,15 +12,99 @@
11
12
  "title": "Automations",
12
13
  "description": "Review scheduled and webhook automations.",
13
14
  "keywords": "automate cron schedule webhook jobs"
14
- }
15
+ },
16
+ "subPages": [
17
+ { "page": "list", "label": "Dashboard", "icon": "layout-dashboard" },
18
+ { "page": "templates", "label": "Templates", "icon": "sparkles" }
19
+ ]
15
20
  },
16
21
  "pages": {
17
22
  "list": {
18
- "title": "Automations",
19
- "subtitle": "View active and inactive automations, search by metadata, and inspect read-only details."
23
+ "title": "Dashboard",
24
+ "subtitle": "Health, activity, and run performance across your automations.",
25
+ "overview": {
26
+ "label": "Automation overview",
27
+ "tiles": [
28
+ {
29
+ "metric": "automations",
30
+ "label": "Automations",
31
+ "detail": "{{active}} active",
32
+ "icon": "bot"
33
+ },
34
+ {
35
+ "metric": "needs-attention",
36
+ "label": "Needs attention",
37
+ "detail": "Latest run failed",
38
+ "zeroDetail": "No latest-run failures",
39
+ "icon": "circle-alert"
40
+ },
41
+ {
42
+ "metric": "total-runs",
43
+ "label": "Total runs",
44
+ "detail": "Across loaded automations",
45
+ "icon": "activity"
46
+ },
47
+ {
48
+ "metric": "average-duration",
49
+ "label": "Average duration",
50
+ "detail": "Recent completed runs",
51
+ "icon": "timer"
52
+ }
53
+ ]
54
+ },
55
+ "filters": [
56
+ {
57
+ "id": "status",
58
+ "label": "Filter by status",
59
+ "options": [
60
+ { "value": "all", "label": "All statuses" },
61
+ { "value": "active", "label": "Active" },
62
+ { "value": "failing", "label": "Needs attention" },
63
+ { "value": "disabled", "label": "Disabled" }
64
+ ]
65
+ },
66
+ {
67
+ "id": "trigger",
68
+ "label": "Filter by trigger",
69
+ "options": [
70
+ { "value": "all", "label": "All triggers" },
71
+ { "value": "schedule", "label": "Scheduled" },
72
+ { "value": "event", "label": "Event-driven" }
73
+ ]
74
+ }
75
+ ],
76
+ "sort": {
77
+ "label": "Sort automations",
78
+ "default": "last-run",
79
+ "options": [
80
+ { "value": "last-run", "label": "Latest run" },
81
+ { "value": "runs", "label": "Most runs" },
82
+ { "value": "name", "label": "Name" }
83
+ ]
84
+ },
85
+ "insights": {
86
+ "health": {
87
+ "healthy": "Healthy",
88
+ "failing": "Failing",
89
+ "running": "Running",
90
+ "disabled": "Disabled",
91
+ "neverRun": "Never run",
92
+ "checking": "Checking"
93
+ },
94
+ "lastRun": { "label": "Last run", "never": "Never", "justNow": "Just now" },
95
+ "stats": {
96
+ "runs": "Runs",
97
+ "recentSuccess": "Recent success",
98
+ "averageDuration": "Avg. duration"
99
+ }
100
+ }
20
101
  },
21
102
  "detail": { "backLabel": "Back to Automations" },
22
- "edit": { "title": "Edit automation" }
103
+ "edit": { "title": "Edit automation" },
104
+ "templates": {
105
+ "title": "Templates",
106
+ "description": "Browse proven automations and beta ideas, then launch one into a conversation to tailor it to your work."
107
+ }
23
108
  },
24
109
  "docsUrl": "https://docs.openhands.dev/openhands/usage/automations/overview",
25
110
  "attributes": {
@@ -26,17 +26,21 @@
26
26
  "description": "The routes of the Automation interface. The host must have a registration serving each declared shape, so admission verifies they match what it mounted; the manifest is the single source for link construction.",
27
27
  "type": "object",
28
28
  "additionalProperties": false,
29
- "required": ["list", "setup", "detail"],
29
+ "required": ["list", "setup", "detail", "templates"],
30
30
  "properties": {
31
31
  "list": { "$ref": "#/$defs/routePath" },
32
32
  "setup": { "$ref": "#/$defs/parameterizedRoutePath" },
33
- "detail": { "$ref": "#/$defs/parameterizedRoutePath" }
33
+ "detail": { "$ref": "#/$defs/parameterizedRoutePath" },
34
+ "templates": {
35
+ "description": "The templates sub-page. Static: there is no parameter to substitute.",
36
+ "allOf": [{ "$ref": "#/$defs/routePath" }, { "not": { "pattern": ":" } }]
37
+ }
34
38
  }
35
39
  },
36
40
  "navigation": {
37
41
  "type": "object",
38
42
  "additionalProperties": false,
39
- "required": ["sidebar", "commandMenu"],
43
+ "required": ["sidebar", "commandMenu", "subPages"],
40
44
  "properties": {
41
45
  "sidebar": {
42
46
  "type": "object",
@@ -53,22 +57,46 @@
53
57
  "description": { "$ref": "#/$defs/copy" },
54
58
  "keywords": { "$ref": "#/$defs/copy" }
55
59
  }
60
+ },
61
+ "subPages": {
62
+ "description": "The ordered sub-page navigation of the Automation interface. Each page id may appear once; the host's admission checks that, since uniqueItems cannot see through differing labels.",
63
+ "type": "array",
64
+ "minItems": 1,
65
+ "maxItems": 2,
66
+ "uniqueItems": true,
67
+ "items": { "$ref": "#/$defs/subPageNavItem" }
56
68
  }
57
69
  }
58
70
  },
59
71
  "pages": {
60
- "description": "Page-identity copy. Generic chrome - buttons, toasts, empty states, validation sentences - stays host copy, rendered through the host's translations.",
72
+ "description": "Page-identity copy, plus the list page's dashboard composition. Generic chrome - buttons, toasts, empty states, validation sentences - stays host copy, rendered through the host's translations.",
61
73
  "type": "object",
62
74
  "additionalProperties": false,
63
- "required": ["list", "detail", "edit"],
75
+ "required": ["list", "detail", "edit", "templates"],
64
76
  "properties": {
65
77
  "list": {
66
78
  "type": "object",
67
79
  "additionalProperties": false,
68
- "required": ["title", "subtitle"],
80
+ "required": ["title", "subtitle", "overview", "filters", "sort", "insights"],
69
81
  "properties": {
70
82
  "title": { "$ref": "#/$defs/copy" },
71
- "subtitle": { "$ref": "#/$defs/copy" }
83
+ "subtitle": { "$ref": "#/$defs/copy" },
84
+ "overview": { "$ref": "#/$defs/overview" },
85
+ "filters": {
86
+ "description": "The filter dropdowns of the list page, in render order. Each filter id may appear once; the host's admission checks that.",
87
+ "type": "array",
88
+ "minItems": 1,
89
+ "maxItems": 2,
90
+ "uniqueItems": true,
91
+ "items": {
92
+ "oneOf": [
93
+ { "$ref": "#/$defs/statusFilter" },
94
+ { "$ref": "#/$defs/triggerFilter" }
95
+ ]
96
+ }
97
+ },
98
+ "sort": { "$ref": "#/$defs/sort" },
99
+ "insights": { "$ref": "#/$defs/insights" }
72
100
  }
73
101
  },
74
102
  "detail": {
@@ -82,6 +110,16 @@
82
110
  "additionalProperties": false,
83
111
  "required": ["title"],
84
112
  "properties": { "title": { "$ref": "#/$defs/copy" } }
113
+ },
114
+ "templates": {
115
+ "description": "The templates sub-page identity. Its body - the catalog cards and their launch behavior - is the host's existing catalog surface.",
116
+ "type": "object",
117
+ "additionalProperties": false,
118
+ "required": ["title", "description"],
119
+ "properties": {
120
+ "title": { "$ref": "#/$defs/copy" },
121
+ "description": { "$ref": "#/$defs/copy" }
122
+ }
85
123
  }
86
124
  }
87
125
  },
@@ -238,6 +276,228 @@
238
276
  "minItems": 1,
239
277
  "uniqueItems": true,
240
278
  "items": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" }
279
+ },
280
+ "iconSlug": {
281
+ "description": "An icon name from the host's closed icon map. The manifest picks from what the host ships; it cannot supply artwork.",
282
+ "enum": [
283
+ "layout-dashboard",
284
+ "sparkles",
285
+ "bot",
286
+ "circle-alert",
287
+ "activity",
288
+ "timer"
289
+ ]
290
+ },
291
+ "subPageNavItem": {
292
+ "type": "object",
293
+ "additionalProperties": false,
294
+ "required": ["page", "label", "icon"],
295
+ "properties": {
296
+ "page": {
297
+ "description": "The pages entry this item navigates to; its route comes from routes.list or routes.templates.",
298
+ "enum": ["list", "templates"]
299
+ },
300
+ "label": { "$ref": "#/$defs/copy" },
301
+ "icon": { "$ref": "#/$defs/iconSlug" }
302
+ }
303
+ },
304
+ "overview": {
305
+ "description": "The summary tiles above the list. label names the section for assistive technology.",
306
+ "type": "object",
307
+ "additionalProperties": false,
308
+ "required": ["label", "tiles"],
309
+ "properties": {
310
+ "label": { "$ref": "#/$defs/copy" },
311
+ "tiles": {
312
+ "description": "Each metric may appear once; the host's admission checks that.",
313
+ "type": "array",
314
+ "minItems": 1,
315
+ "maxItems": 8,
316
+ "uniqueItems": true,
317
+ "items": { "$ref": "#/$defs/overviewTile" }
318
+ }
319
+ }
320
+ },
321
+ "overviewTile": {
322
+ "description": "One summary tile. The metric names a value the host computes - a tile picks and captions it, never defines it. detail is the caption under the value; zeroDetail, when present, replaces it while the value is zero. Both are plain substitution over the metric's placeholder namespace: only the automations metric exposes {{active}}, so every other tile's copy is literal.",
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "required": ["metric", "label", "detail", "icon"],
326
+ "properties": {
327
+ "metric": {
328
+ "enum": ["automations", "needs-attention", "total-runs", "average-duration"]
329
+ },
330
+ "label": { "$ref": "#/$defs/copy" },
331
+ "detail": { "$ref": "#/$defs/copy" },
332
+ "zeroDetail": { "$ref": "#/$defs/copy" },
333
+ "icon": { "$ref": "#/$defs/iconSlug" }
334
+ },
335
+ "allOf": [
336
+ {
337
+ "if": {
338
+ "properties": { "metric": { "const": "automations" } },
339
+ "required": ["metric"]
340
+ },
341
+ "then": {
342
+ "properties": {
343
+ "detail": { "not": { "pattern": "\\{\\{(?!active\\}\\})" } },
344
+ "zeroDetail": { "not": { "pattern": "\\{\\{(?!active\\}\\})" } }
345
+ }
346
+ },
347
+ "else": {
348
+ "properties": {
349
+ "detail": { "not": { "pattern": "\\{\\{" } },
350
+ "zeroDetail": { "not": { "pattern": "\\{\\{" } }
351
+ }
352
+ }
353
+ }
354
+ ]
355
+ },
356
+ "statusFilter": {
357
+ "description": "The status filter. Values name predicates the host implements over an automation and its derived health; the manifest supplies which appear and their labels. The all option is the host's default and reset target, so it must be offered.",
358
+ "type": "object",
359
+ "additionalProperties": false,
360
+ "required": ["id", "label", "options"],
361
+ "properties": {
362
+ "id": { "const": "status" },
363
+ "label": {
364
+ "description": "The control's accessible name.",
365
+ "$ref": "#/$defs/copy"
366
+ },
367
+ "options": {
368
+ "description": "Each value may appear once; the host's admission checks that.",
369
+ "type": "array",
370
+ "minItems": 2,
371
+ "maxItems": 8,
372
+ "uniqueItems": true,
373
+ "items": {
374
+ "type": "object",
375
+ "additionalProperties": false,
376
+ "required": ["value", "label"],
377
+ "properties": {
378
+ "value": { "enum": ["all", "active", "failing", "disabled"] },
379
+ "label": { "$ref": "#/$defs/copy" }
380
+ }
381
+ },
382
+ "contains": {
383
+ "type": "object",
384
+ "required": ["value"],
385
+ "properties": { "value": { "const": "all" } }
386
+ }
387
+ }
388
+ }
389
+ },
390
+ "triggerFilter": {
391
+ "description": "The trigger filter. Like the status filter, values name host-implemented predicates: event matches event-triggered automations, schedule everything else.",
392
+ "type": "object",
393
+ "additionalProperties": false,
394
+ "required": ["id", "label", "options"],
395
+ "properties": {
396
+ "id": { "const": "trigger" },
397
+ "label": {
398
+ "description": "The control's accessible name.",
399
+ "$ref": "#/$defs/copy"
400
+ },
401
+ "options": {
402
+ "description": "Each value may appear once; the host's admission checks that.",
403
+ "type": "array",
404
+ "minItems": 2,
405
+ "maxItems": 8,
406
+ "uniqueItems": true,
407
+ "items": {
408
+ "type": "object",
409
+ "additionalProperties": false,
410
+ "required": ["value", "label"],
411
+ "properties": {
412
+ "value": { "enum": ["all", "schedule", "event"] },
413
+ "label": { "$ref": "#/$defs/copy" }
414
+ }
415
+ },
416
+ "contains": {
417
+ "type": "object",
418
+ "required": ["value"],
419
+ "properties": { "value": { "const": "all" } }
420
+ }
421
+ }
422
+ }
423
+ },
424
+ "sort": {
425
+ "description": "The sort dropdown. Values name comparators the host implements. default must be one of the declared option values; the host's admission checks that, as schemas cannot cheaply.",
426
+ "type": "object",
427
+ "additionalProperties": false,
428
+ "required": ["label", "options", "default"],
429
+ "properties": {
430
+ "label": {
431
+ "description": "The control's accessible name.",
432
+ "$ref": "#/$defs/copy"
433
+ },
434
+ "options": {
435
+ "description": "Each value may appear once; the host's admission checks that.",
436
+ "type": "array",
437
+ "minItems": 1,
438
+ "maxItems": 3,
439
+ "uniqueItems": true,
440
+ "items": {
441
+ "type": "object",
442
+ "additionalProperties": false,
443
+ "required": ["value", "label"],
444
+ "properties": {
445
+ "value": { "$ref": "#/$defs/sortValue" },
446
+ "label": { "$ref": "#/$defs/copy" }
447
+ }
448
+ }
449
+ },
450
+ "default": { "$ref": "#/$defs/sortValue" }
451
+ }
452
+ },
453
+ "sortValue": { "enum": ["last-run", "runs", "name"] },
454
+ "insights": {
455
+ "description": "Copy for the per-automation run insights on cards and rows. The states, precedence, sampling, and value formatting are the host's; the manifest names them.",
456
+ "type": "object",
457
+ "additionalProperties": false,
458
+ "required": ["health", "lastRun", "stats"],
459
+ "properties": {
460
+ "health": {
461
+ "type": "object",
462
+ "additionalProperties": false,
463
+ "required": [
464
+ "healthy",
465
+ "failing",
466
+ "running",
467
+ "disabled",
468
+ "neverRun",
469
+ "checking"
470
+ ],
471
+ "properties": {
472
+ "healthy": { "$ref": "#/$defs/copy" },
473
+ "failing": { "$ref": "#/$defs/copy" },
474
+ "running": { "$ref": "#/$defs/copy" },
475
+ "disabled": { "$ref": "#/$defs/copy" },
476
+ "neverRun": { "$ref": "#/$defs/copy" },
477
+ "checking": { "$ref": "#/$defs/copy" }
478
+ }
479
+ },
480
+ "lastRun": {
481
+ "type": "object",
482
+ "additionalProperties": false,
483
+ "required": ["label", "never", "justNow"],
484
+ "properties": {
485
+ "label": { "$ref": "#/$defs/copy" },
486
+ "never": { "$ref": "#/$defs/copy" },
487
+ "justNow": { "$ref": "#/$defs/copy" }
488
+ }
489
+ },
490
+ "stats": {
491
+ "type": "object",
492
+ "additionalProperties": false,
493
+ "required": ["runs", "recentSuccess", "averageDuration"],
494
+ "properties": {
495
+ "runs": { "$ref": "#/$defs/copy" },
496
+ "recentSuccess": { "$ref": "#/$defs/copy" },
497
+ "averageDuration": { "$ref": "#/$defs/copy" }
498
+ }
499
+ }
500
+ }
241
501
  }
242
502
  }
243
503
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-extensions"
3
- version = "0.15.0"
3
+ version = "0.16.0"
4
4
  description = "OpenHands extensions, plugins, and skills (Python bindings for the integration catalog)"
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -18,7 +18,7 @@ from importlib.metadata import PackageNotFoundError, version
18
18
  #: Kept in lock-step with pyproject.toml/package.json by the version test.
19
19
  #: release-please only rewrites a semver found on the annotated line itself, so
20
20
  #: the annotation must stay on the assignment; on its own line it is a no-op.
21
- _FALLBACK_VERSION = "0.15.0" # x-release-please-version
21
+ _FALLBACK_VERSION = "0.16.0" # x-release-please-version
22
22
 
23
23
  try:
24
24
  __version__: str = version("openhands-extensions")
@@ -76,6 +76,26 @@ def test_featured_automations_resolve_to_catalog_entries():
76
76
  constraints={"max": 50}
77
77
  ),
78
78
  ),
79
+ (
80
+ "a sub-page the interface does not serve",
81
+ lambda doc: doc["navigation"]["subPages"][0].update(
82
+ page="workflows"
83
+ ),
84
+ ),
85
+ (
86
+ "an icon outside the host's icon map",
87
+ lambda doc: doc["navigation"]["subPages"][0].update(icon="rocket"),
88
+ ),
89
+ (
90
+ "tile copy using a placeholder its metric does not expose",
91
+ lambda doc: doc["pages"]["list"]["overview"]["tiles"][2].update(
92
+ detail="{{active}} runs"
93
+ ),
94
+ ),
95
+ (
96
+ "a status filter without the all option",
97
+ lambda doc: doc["pages"]["list"]["filters"][0]["options"].pop(0),
98
+ ),
79
99
  ],
80
100
  )
81
101
  def test_schema_rejects(case, mutation):