Files
devplacepy/devplacepy/services/devii/actions/client_actions.py
T
retoor 5488008216 feat: add /stop and /reset chat commands and bots internals docs section
Add two new chat commands (`/stop` and `/reset`) to the Devii WebSocket handler, enabling users to stop or reset a session via text input. Introduce a new "Bots internals" documentation section with six prose pages covering architecture, personas, content generation, engagement, realism, and configuration for the autonomous bot fleet. Extend the bot service with article scoring, category picking, configurable pause/break timing, and a `gist_min_lines` parameter for LLM client initialization.
2026-06-11 12:06:17 +00:00

376 lines
15 KiB
Python

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