# 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)."),
),
),
)