|
# retoor <retoor@molodetz.nl>
|
|
|
|
from __future__ import annotations
|
|
|
|
from .spec import Action, Param
|
|
|
|
|
|
def arg(
|
|
name: str, description: str, required: bool = False, kind: str = "string"
|
|
) -> Param:
|
|
return Param(
|
|
name=name,
|
|
location="body",
|
|
description=description,
|
|
required=required,
|
|
type=kind,
|
|
)
|
|
|
|
|
|
CLIENT = (
|
|
"Runs in the user's own browser through the web terminal. Only effective in the web "
|
|
"interface with a live connection; elsewhere it reports that no browser is attached."
|
|
)
|
|
|
|
CLIENT_ACTIONS: tuple[Action, ...] = (
|
|
Action(
|
|
name="get_page_context",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Read the user's current page: URL, title, viewport, scroll, selected text, visible headings, and whether they are signed in",
|
|
description=CLIENT
|
|
+ " Use this to understand where the user is and what they are looking at before acting or guiding them.",
|
|
handler="client",
|
|
requires_auth=False,
|
|
read_only=True,
|
|
),
|
|
Action(
|
|
name="run_js",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Execute JavaScript in the user's browser and return its result",
|
|
description=(
|
|
CLIENT
|
|
+ " The code is the body of an async function; use 'return value' to return a "
|
|
"JSON-serializable result. You have full access to window and document. Use this for "
|
|
"anything not covered by the dedicated tools: read or change the DOM, drive a live "
|
|
"demo, inspect state, or update the screen. Prefer the dedicated tools "
|
|
"(highlight_element, show_toast, scroll_to_element, navigate_to, reload_page) when they fit."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"code",
|
|
"JavaScript to run as an async function body. Return a JSON-serializable value.",
|
|
required=True,
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="highlight_element",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Highlight an element on screen with an outline and an optional callout label, for live tutorials",
|
|
description=CLIENT
|
|
+ " Scrolls the element into view and draws an attention outline. Call clear_highlights to remove it.",
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"selector",
|
|
"CSS selector, or the element's exact visible text (e.g. a heading or link label).",
|
|
required=True,
|
|
),
|
|
arg("label", "Optional callout text shown next to the element."),
|
|
),
|
|
),
|
|
Action(
|
|
name="clear_highlights",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Remove all highlights and callouts placed by highlight_element",
|
|
description=CLIENT,
|
|
handler="client",
|
|
requires_auth=False,
|
|
),
|
|
Action(
|
|
name="show_toast",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Show a brief on-screen message (toast) to the user",
|
|
description=CLIENT,
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg("text", "Message to display.", required=True),
|
|
arg(
|
|
"duration_ms",
|
|
"How long to show it, in milliseconds (default 4000).",
|
|
kind="integer",
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="scroll_to_element",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Smoothly scroll an element into view",
|
|
description=CLIENT,
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"selector",
|
|
"CSS selector, or the element's exact visible text.",
|
|
required=True,
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="navigate_to",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Send the user's browser to a URL",
|
|
description=(
|
|
CLIENT
|
|
+ " Use a same-origin path like /feed or /docs/index.html, or a full URL. The "
|
|
"page reloads; the user's Devii session and conversation persist and reconnect automatically."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(arg("url", "Path or URL to navigate to.", required=True),),
|
|
),
|
|
Action(
|
|
name="reload_page",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Refresh the user's current page, e.g. after something changed",
|
|
description=CLIENT
|
|
+ " The Devii session and conversation persist and reconnect automatically.",
|
|
handler="client",
|
|
requires_auth=False,
|
|
),
|
|
Action(
|
|
name="discover_elements",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="List the actionable elements on the user's screen in reading order, each with a stable ref, role, label, selector, and state",
|
|
description=(
|
|
CLIENT
|
|
+ " This is how you SEE the page before acting: it returns buttons, links, inputs, "
|
|
"textboxes, checkboxes, selects, tabs, and menu items in document (logical) order, each "
|
|
"with a `ref` (like e3) you can pass to click_element/fill_field/etc, plus its role, "
|
|
"accessible label, a robust CSS selector, current state (visible, enabled, checked, value), "
|
|
"and which form or modal it belongs to. Call this first, then act on the refs. Use `query` "
|
|
"to filter by label, `within` to scope to a region (a ref/selector, e.g. an open modal), and "
|
|
"`kind` to keep only one type."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
read_only=True,
|
|
params=(
|
|
arg("query", "Only return elements whose label contains this text (case-insensitive)."),
|
|
arg(
|
|
"within",
|
|
"Scope discovery to inside this element (a ref, CSS selector, or exact text), e.g. an open modal or form.",
|
|
),
|
|
arg(
|
|
"kind",
|
|
"Keep only one type: button, link, input, textbox, checkbox, radio, select, tab, or menuitem.",
|
|
),
|
|
arg("limit", "Maximum number of elements to return (default 40).", kind="integer"),
|
|
),
|
|
),
|
|
Action(
|
|
name="read_element",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Read one element in detail: text, value, attributes, state, position, and visibility",
|
|
description=CLIENT
|
|
+ " Use this to investigate a specific element you found with discover_elements, "
|
|
"or to confirm the result of an action (its value, whether it is checked, any error text near it).",
|
|
handler="client",
|
|
requires_auth=False,
|
|
read_only=True,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The element to read: a ref (e3), a CSS selector, or its exact visible text.",
|
|
required=True,
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="click_element",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Click an element on the user's screen and report what changed",
|
|
description=(
|
|
CLIENT
|
|
+ " Scrolls the element into view, waits briefly for it to be visible and enabled, "
|
|
"dispatches a real click, and returns a delta (did the URL change, did a modal open or close, "
|
|
"any new toast or validation error, is the element still there). Prefer this over run_js for clicking. "
|
|
"Set `button` to 'right' for a context menu or 'double' for a double-click."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The element to click: a ref (e3), a CSS selector, or its exact visible text.",
|
|
required=True,
|
|
),
|
|
arg("button", "left (default), right, or double."),
|
|
),
|
|
),
|
|
Action(
|
|
name="fill_field",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Type a value into an input, textarea, contenteditable, or code editor and fire the right events",
|
|
description=(
|
|
CLIENT
|
|
+ " Sets the value through the native setter and dispatches input and change events, so "
|
|
"framework and validation handlers actually run (plain run_js value assignment does not). "
|
|
"Supports text inputs, textareas, contenteditable elements, and CodeMirror editors. "
|
|
"Returns the resulting value and any validation message shown near the field."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The field to fill: a ref (e3), a CSS selector, or its label/placeholder text.",
|
|
required=True,
|
|
),
|
|
arg("value", "The value to type into the field.", required=True),
|
|
),
|
|
),
|
|
Action(
|
|
name="set_control",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Toggle a checkbox or radio, or choose an option in a select",
|
|
description=CLIENT
|
|
+ " For a checkbox or radio pass `checked` true/false; for a select pass `option` "
|
|
"(matched against option value or visible label). Dispatches a change event.",
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The control: a ref (e3), a CSS selector, or its label text.",
|
|
required=True,
|
|
),
|
|
arg("checked", "For a checkbox or radio: true to check, false to uncheck.", kind="boolean"),
|
|
arg("option", "For a select: the option value or visible label to choose."),
|
|
),
|
|
),
|
|
Action(
|
|
name="submit_form",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Optionally fill a form's fields, then submit it, and report what changed",
|
|
description=(
|
|
CLIENT
|
|
+ " Resolves the form that contains `target` (a ref/selector/text for the form, a field in it, "
|
|
"or its submit button). If `fields` is given (a map of field name/label to value) each is filled "
|
|
"first with the proper events, then the form is submitted by clicking its submit button (so app "
|
|
"handlers run) or calling requestSubmit. Returns a delta (URL change, modal open/close, toast, errors). "
|
|
"This is the primary tool for completing a mutation such as creating a post or saving a profile."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The form, one of its fields, or its submit button: a ref (e3), CSS selector, or exact text.",
|
|
required=True,
|
|
),
|
|
arg(
|
|
"fields",
|
|
"Optional map of field name or label to the value to fill before submitting.",
|
|
kind="object",
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="wait_for",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Wait until an element becomes visible, hidden, or contains text, before continuing",
|
|
description=(
|
|
CLIENT
|
|
+ " Polls the page until the condition holds or it times out, so sequences after a navigation, "
|
|
"click, or fetch do not race. `condition` is visible (default), hidden, or text_contains "
|
|
"(supply `text`). Returns whether it was satisfied and how long it waited."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
read_only=True,
|
|
params=(
|
|
arg(
|
|
"target",
|
|
"The element to wait on: a ref (e3), a CSS selector, or exact visible text.",
|
|
required=True,
|
|
),
|
|
arg("condition", "visible (default), hidden, or text_contains."),
|
|
arg("text", "For text_contains: the text the element should contain."),
|
|
arg("timeout_ms", "Maximum wait in milliseconds (default 8000, capped at 55000).", kind="integer"),
|
|
),
|
|
),
|
|
Action(
|
|
name="press_key",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Send a keypress to an element or the page (Enter, Escape, Tab, arrows, etc.)",
|
|
description=CLIENT
|
|
+ " Dispatches keydown/keypress/keyup for the named key to `target` (or the focused element "
|
|
"if omitted). Use Enter to submit, Escape to close a modal, Tab to move focus.",
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg("key", "The key name: Enter, Escape, Tab, ArrowDown, a, etc.", required=True),
|
|
arg("target", "Optional element to send the key to: a ref (e3), selector, or exact text."),
|
|
),
|
|
),
|
|
Action(
|
|
name="run_sequence",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Run an ordered list of client steps in one round-trip, stopping on the first failure",
|
|
description=(
|
|
CLIENT
|
|
+ " Each step is an object with an `action` (click_element, fill_field, set_control, submit_form, "
|
|
"wait_for, press_key, scroll_to_element, read_element, discover_elements, highlight_element, show_toast) "
|
|
"and its arguments. Steps run in order with the page settling between them; execution stops at the first "
|
|
"error and returns every step's result plus the final page context. Use this to perform a whole UI flow "
|
|
"(open a modal, fill it, submit) reliably without a round-trip per step. run_js is not allowed inside a sequence."
|
|
),
|
|
handler="client",
|
|
requires_auth=False,
|
|
params=(
|
|
arg(
|
|
"steps",
|
|
"Ordered list of step objects, each with an `action` field and that action's arguments.",
|
|
required=True,
|
|
kind="array",
|
|
),
|
|
),
|
|
),
|
|
Action(
|
|
name="open_terminal",
|
|
method="LOCAL",
|
|
path="",
|
|
summary="Open a floating interactive terminal window attached to a running container instance",
|
|
description=(
|
|
CLIENT
|
|
+ " Opens a new xterm.js window in the user's browser connected to the container's "
|
|
"interactive shell (admin only). Resolve the instance first with container_list_instances, "
|
|
"then pass the project slug and the instance slug or uid."
|
|
),
|
|
handler="client",
|
|
requires_admin=True,
|
|
params=(
|
|
arg(
|
|
"project_slug",
|
|
"Project slug or uid that owns the container.",
|
|
required=True,
|
|
),
|
|
arg("instance", "Container instance slug or uid.", required=True),
|
|
arg("label", "Optional window title (defaults to the instance name)."),
|
|
),
|
|
),
|
|
)
|