refactor(skills): render opt-in Jinja skills instead of comment markers

This commit is contained in:
Ian 2026-10-07 14:46:05 -04:00
parent e543884adf
commit 76baba8edf
3 changed files with 28 additions and 22 deletions

View file

@ -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)

View file

@ -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

View file

@ -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: