Files
hermes-agent/tools/annotate_preview_tool.py
T
Teknium 95505c60e9 docs(tools): lead every tool description with its key capability
The deferred tool_search catalog shows each tool's first sentence clipped to
60 chars, so lines like 'image_generate: Generate high-quality images from
text prompts, or edit /…' hid that it edits with reference images on the
user's configured model. An agent skipped it and called FAL directly with a
hardcoded model.

- catalog line cap 60 -> 80 (CATALOG_SUMMARY_MAX_CHARS); every built-in
  tool's first sentence now fits and leads with what it does
- image_generate/video_generate say they use the configured model;
  image_generate says to use it rather than direct API calls/scripts
- web_search/web_extract/browser_exec name when to use each other
- drop a duplicated cronjob sentence and filler in skill_view/video_generate
- regenerate tests/fixtures/prompt_surface
2026-10-23 16:04:50 -07:00

102 lines
4.0 KiB
Python

"""Persistent element annotations in the Hermes desktop GUI's in-app browser.
Unlike ``drive_preview``'s self-retiring marks, an annotation outlines an element
(or, with ``hold``, the whole visible field) until removed. Annotations bind to
elements, not coordinates: they ride scrolls and vanish with their element, so
navigation clears them. Same ``preview.act`` bridge as ``drive_preview`` (the
renderer resolves refs and owns the overlay). ``desktop_ui`` toolset only.
"""
import json
from typing import Callable, Optional
from tools.registry import registry, tool_error
ACTIONS = ("add", "hold", "remove", "clear")
# Renderer verbs keyed by ours; `clear` is `unpin` with no target = "all of them".
WIRE = {"add": "pin", "hold": "hold", "remove": "unpin", "clear": "unpin"}
def annotate_preview_tool(
action: str = "add", ref: Optional[str] = None, selector: Optional[str] = None,
label: Optional[str] = None, callback: Optional[Callable] = None,
) -> str:
"""Put one annotation up, take one down, or clear them all."""
if callback is None:
return tool_error("annotate_preview is only available in the Hermes desktop app.")
verb = (action or "add").strip().lower()
if verb not in ACTIONS:
return tool_error(f"action must be one of: {', '.join(ACTIONS)}.")
if verb in ("add", "remove") and not (ref or selector):
return tool_error(f"{verb} needs a ref from drive_preview action='elements' (e.g. 'btn-sign-in') or a CSS selector.")
targeted = verb not in ("clear", "hold")
fields = (("action", WIRE[verb]), ("ref", ref if targeted else None), ("selector", selector if targeted else None), ("text", label))
payload = {name: val for name, val in fields if val is not None}
try:
raw = callback(payload)
except Exception as exc:
return tool_error(f"Failed to annotate the in-app browser: {exc}")
if not raw:
return tool_error(
"No GUI window answered with a page: no preview tab is open. "
"Open a page with open_preview first. If the pane IS open, the desktop app "
"may be older than this backend — its bridge-unavailable error names that case.")
try:
return json.dumps(json.loads(raw), ensure_ascii=False)
except (TypeError, ValueError):
return json.dumps({"text": str(raw)}, ensure_ascii=False)
ANNOTATE_PREVIEW_SCHEMA = {
"name": "annotate_preview",
"description": (
"Highlight preview-pane elements with lasting marks (drive_preview's fade). "
"Point at findings, flag what you're about to change, keep your place. Use the refs "
"from drive_preview action='elements'. add: outline one element "
"(optional short label — a word or two, drawn on the page). hold: "
"freeze the whole visible field, every element outlined and named. "
"remove/clear: take one/all down. Marks follow their element on "
"scroll; navigation clears them."
),
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": list(ACTIONS),
"description": "Defaults to 'add'.",
},
"ref": {
"type": "string",
"description": "Ref from drive_preview elements.",
},
"selector": {
"type": "string",
"description": "CSS selector fallback. Prefer ref.",
},
"label": {
"type": "string",
"description": "Optional caption, e.g. 'cheapest'.",
},
},
"required": [],
},
}
registry.register(
name="annotate_preview",
toolset="desktop_ui",
schema=ANNOTATE_PREVIEW_SCHEMA,
handler=lambda args, **kw: annotate_preview_tool(
action=args.get("action", "add"),
ref=args.get("ref"),
selector=args.get("selector"),
label=args.get("label"),
callback=kw.get("callback"),
),
emoji="🔖",
)