Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions automations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,13 @@ falling back to its built-in defaults when the manifest is absent or fails admis
captions it: `overview.tiles[].metric` names a host-computed value (`automations`, `needs-attention`,
`total-runs`, `average-duration`), filter option values name host predicates (`status`: enabled /
latest-run-failed / disabled; `trigger`: `event` matches event-triggered automations, `schedule`
everything else), `sort` values name host comparators, and `insights` captions the host's run-health
states and per-automation stats. The health precedence, the run sampling, the value formatting, the
relative-time rendering, and the filtered-empty state with its reset button are the host's - a manifest
cannot redefine them, only relabel what appears. Tile `detail` copy is plain substitution over the
metric's placeholder namespace (only the `automations` metric exposes `{{active}}`); `zeroDetail`
replaces `detail` while the value is zero.
everything else; `created_by`: `me` matches automations the signed-in user created, `others` everything
else, and the host decides where it is shown), `sort` values name host comparators, and `insights`
captions the host's run-health states and per-automation stats. The health precedence, the run
sampling, the value formatting, the relative-time rendering, and the filtered-empty state with its reset
button are the host's - a manifest cannot redefine them, only relabel what appears. Tile `detail` copy is
plain substitution over the metric's placeholder namespace (only the `automations` metric exposes
`{{active}}`); `zeroDetail` replaces `detail` while the value is zero.
- **`docsUrl`** - the automations documentation link, prefix-pinned to docs.openhands.dev by schema.
- **`attributes`** - the input surface of an existing Automation: which attributes can be set after
creation, keyed by the runtime-model property the host sends (`name`, `prompt`, `model`, `timeout`,
Expand Down
10 changes: 9 additions & 1 deletion automations/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ export type AutomationStatusFilterValue =
| "failing"
| "disabled";
export type AutomationTriggerFilterValue = "all" | "schedule" | "event";
export type AutomationCreatedByFilterValue = "all" | "me" | "others";

/**
* A filter dropdown. Values name predicates the host implements; the manifest
Expand All @@ -327,9 +328,16 @@ export interface AutomationTriggerFilter {
options: { value: AutomationTriggerFilterValue; label: string }[];
}

export interface AutomationCreatedByFilter {
id: "created_by";
label: string;
options: { value: AutomationCreatedByFilterValue; label: string }[];
}

export type AutomationDashboardFilter =
| AutomationStatusFilter
| AutomationTriggerFilter;
| AutomationTriggerFilter
| AutomationCreatedByFilter;

/** A comparator the host implements, named from a closed set. */
export type AutomationSortValue = "last-run" | "runs" | "name";
Expand Down
9 changes: 9 additions & 0 deletions automations/interface.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@
{ "value": "schedule", "label": "Scheduled" },
{ "value": "event", "label": "Event-driven" }
]
},
{
"id": "created_by",
"label": "Filter by creator",
"options": [
{ "value": "all", "label": "Anyone" },
{ "value": "me", "label": "Me" },
{ "value": "others", "label": "Others" }
]
}
],
"sort": {
Expand Down
39 changes: 37 additions & 2 deletions automations/interface.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,13 @@
"description": "The filter dropdowns of the list page, in render order. Each filter id may appear once; the host's admission checks that.",
"type": "array",
"minItems": 1,
"maxItems": 2,
"maxItems": 3,
"uniqueItems": true,
"items": {
"oneOf": [
{ "$ref": "#/$defs/statusFilter" },
{ "$ref": "#/$defs/triggerFilter" }
{ "$ref": "#/$defs/triggerFilter" },
{ "$ref": "#/$defs/createdByFilter" }
]
}
},
Expand Down Expand Up @@ -425,6 +426,40 @@
}
}
},
"createdByFilter": {
"description": "The creator filter. Like the status filter, values name host-implemented predicates: me matches automations the signed-in user created, others every other automation. The host decides where the filter is shown and may hide it where every automation is the signed-in user's.",
"type": "object",
"additionalProperties": false,
"required": ["id", "label", "options"],
"properties": {
"id": { "const": "created_by" },
"label": {
"description": "The control's accessible name.",
"$ref": "#/$defs/copy"
},
"options": {
"description": "Each value may appear once; the host's admission checks that.",
"type": "array",
"minItems": 2,
"maxItems": 8,
"uniqueItems": true,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["value", "label"],
"properties": {
"value": { "enum": ["all", "me", "others"] },
"label": { "$ref": "#/$defs/copy" }
}
},
"contains": {
"type": "object",
"required": ["value"],
"properties": { "value": { "const": "all" } }
}
}
}
},
"sort": {
"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.",
"type": "object",
Expand Down
24 changes: 23 additions & 1 deletion tests/test_interface_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def _with(mutation):
return document


def _filter(document, filter_id):
return next(
f for f in document["pages"]["list"]["filters"] if f["id"] == filter_id
)


def test_schema_is_a_valid_draft_2020_12_schema():
jsonschema.Draft202012Validator.check_schema(SCHEMA)

Expand Down Expand Up @@ -94,7 +100,23 @@ def test_featured_automations_resolve_to_catalog_entries():
),
(
"a status filter without the all option",
lambda doc: doc["pages"]["list"]["filters"][0]["options"].pop(0),
lambda doc: _filter(doc, "status")["options"].pop(0),
),
(
"a creator filter without the all option",
lambda doc: _filter(doc, "created_by")["options"].pop(0),
),
(
"a creator filter value the host does not implement",
lambda doc: _filter(doc, "created_by")["options"].append(
{"value": "team", "label": "My team"}
),
),
(
"more filters than the host renders",
lambda doc: doc["pages"]["list"]["filters"].append(
{**doc["pages"]["list"]["filters"][0], "label": "Filter again"}
),
),
],
)
Expand Down
Loading