mirror of
https://github.com/usestrix/strix.git
synced 2026-10-12 03:47:56 +00:00
refactor(skills): render opt-in Jinja skills instead of comment markers
This commit is contained in:
parent
e543884adf
commit
76baba8edf
3 changed files with 28 additions and 22 deletions
|
|
@ -6,6 +6,7 @@ from pathlib import Path
|
|||
from typing import TypeGuard
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from strix.utils.resource_paths import get_strix_resource_path
|
||||
|
||||
|
|
@ -13,9 +14,14 @@ from strix.utils.resource_paths import get_strix_resource_path
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
_FRONTMATTER_PATTERN = re.compile(r"^---\s*\n(?P<body>.*?)\n---\s*\n", re.DOTALL)
|
||||
# Skill text between `<!-- images -->` and `<!-- /images -->` is kept only for models
|
||||
# that accept images; text between `<!-- text-only -->` markers only for those that don't.
|
||||
_MODALITY_BLOCK_PATTERN = re.compile(r"<!-- (images|text-only) -->\n(.*?)<!-- /\1 -->\n", re.DOTALL)
|
||||
# Only skills with `template: jinja` in their frontmatter are rendered; the rest hold
|
||||
# literal `{{ }}` payloads.
|
||||
_SKILL_TEMPLATES = Environment(
|
||||
autoescape=False, # noqa: S701 - prompts, not HTML
|
||||
undefined=StrictUndefined,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
|
||||
_INTERNAL_SKILL_CATEGORIES: frozenset[str] = frozenset({"scan_modes", "coordination", "analysis"})
|
||||
_ROOT_SKILL_CATEGORY = "root"
|
||||
|
|
@ -276,7 +282,6 @@ def load_skills(skill_names: list[str], *, supports_images: bool = True) -> dict
|
|||
if not search_dirs:
|
||||
return {}
|
||||
|
||||
keep = "images" if supports_images else "text-only"
|
||||
skill_content: dict[str, str] = {}
|
||||
for skill_name in skill_names:
|
||||
candidates = _candidate_skill_files(skill_name)
|
||||
|
|
@ -295,10 +300,12 @@ def load_skills(skill_names: list[str], *, supports_images: bool = True) -> dict
|
|||
continue
|
||||
|
||||
var_name = skill_name.split("/")[-1]
|
||||
_, skill_body = _parse_skill_content(content, file_path)
|
||||
skill_content[var_name] = _MODALITY_BLOCK_PATTERN.sub(
|
||||
lambda m: m[2] if m[1] == keep else "", skill_body
|
||||
)
|
||||
metadata, skill_body = _parse_skill_content(content, file_path)
|
||||
if metadata.get("template") == "jinja":
|
||||
skill_body = _SKILL_TEMPLATES.from_string(skill_body).render(
|
||||
supports_images=supports_images
|
||||
)
|
||||
skill_content[var_name] = skill_body
|
||||
logger.debug("Loaded skill: %s -> %s", skill_name, var_name)
|
||||
_track_skill_loaded(var_name, file_path)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
name: agent_browser
|
||||
description: agent-browser CLI for headless Chrome via shell. Snapshot-and-ref workflow, click/fill/extract, screenshots, multi-tab, multi-session, network mocking. Pre-installed in the sandbox; invoke via exec_command.
|
||||
template: jinja
|
||||
---
|
||||
|
||||
|
||||
|
|
@ -261,7 +262,7 @@ only for simple expressions.
|
|||
### Screenshot
|
||||
|
||||
`agent-browser screenshot` writes a PNG to disk in the sandbox.
|
||||
<!-- images -->
|
||||
{% if supports_images %}
|
||||
The shell command alone does **not** put the image into your context —
|
||||
chain it with the SDK ``view_image`` tool to actually see it:
|
||||
|
||||
|
|
@ -276,13 +277,12 @@ the full path on stdout — pass that to ``view_image``). If you need a
|
|||
specific filename, keep it inside that directory or a sibling hidden
|
||||
dir under ``/workspace``. Never write screenshots to ``/tmp`` —
|
||||
``view_image`` rejects anything outside the workspace root.
|
||||
<!-- /images -->
|
||||
<!-- text-only -->
|
||||
{% else %}
|
||||
You are running on a text-only model and cannot view images, so a
|
||||
screenshot is only useful as a saved file (for example, login evidence).
|
||||
Drive the page from `snapshot -i` refs, `eval` for any DOM/JS state you
|
||||
need to read, and `text @ref` / `get text` for content extraction.
|
||||
<!-- /text-only -->
|
||||
{% endif %}
|
||||
|
||||
```bash
|
||||
agent-browser screenshot # path printed on stdout
|
||||
|
|
@ -291,7 +291,7 @@ agent-browser screenshot --full # full scroll height
|
|||
agent-browser screenshot --annotate # numbered labels + legend keyed to snapshot refs
|
||||
```
|
||||
|
||||
<!-- images -->
|
||||
{% if supports_images %}
|
||||
`--annotate` is designed for multimodal models: each label `[N]` maps
|
||||
to ref `@eN`. Take the annotated screenshot, then ``view_image`` it,
|
||||
and you can correlate visual layout with snapshot refs.
|
||||
|
|
@ -307,7 +307,7 @@ supported", or similar), you are running on a text-only model — stop
|
|||
calling it and stop taking screenshots. Drive the page entirely from
|
||||
`snapshot -i` refs, `eval` for any DOM/JS state you need to read, and
|
||||
`text @ref` / `get text` for content extraction.
|
||||
<!-- /images -->
|
||||
{% endif %}
|
||||
|
||||
### Handle multiple pages via tabs
|
||||
|
||||
|
|
|
|||
|
|
@ -70,16 +70,15 @@ def test_registered_dir_adds_new_skill(tmp_path: Path) -> None:
|
|||
assert load_skills(["widget"]) == {"widget": "widget body"}
|
||||
|
||||
|
||||
def test_skill_modality_blocks_follow_image_support(tmp_path: Path) -> None:
|
||||
body = (
|
||||
"a\n<!-- images -->\nlook\n<!-- /images -->\n"
|
||||
"<!-- text-only -->\nread\n<!-- /text-only -->\nb"
|
||||
)
|
||||
_write_skill(tmp_path, "extra", "widget", body)
|
||||
def test_only_jinja_skills_are_rendered(tmp_path: Path) -> None:
|
||||
body = "{% if supports_images %}\nlook\n{% else %}\nread\n{% endif %}\n"
|
||||
_write_skill(tmp_path, "extra", "plain", body)
|
||||
_write_skill(tmp_path, "extra", "templated", f"---\ntemplate: jinja\n---\n{body}")
|
||||
register_skill_dir(tmp_path)
|
||||
|
||||
assert load_skills(["widget"]) == {"widget": "a\nlook\nb"}
|
||||
assert load_skills(["widget"], supports_images=False) == {"widget": "a\nread\nb"}
|
||||
assert load_skills(["plain"]) == {"plain": body}
|
||||
assert load_skills(["templated"]) == {"templated": "look\n"}
|
||||
assert load_skills(["templated"], supports_images=False) == {"templated": "read\n"}
|
||||
|
||||
|
||||
def test_available_skill_includes_frontmatter_description(tmp_path: Path) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue