mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 15:37:55 +08:00
tools/skills: create and modify skills behind a grant
skill_create writes a SKILL.md (name, description, body) plus whatever bundled files the model lists, and skill_edit changes one file of an existing skill either by literal replacement, by appending, or by rewriting it. Both resolve every path inside the skill directory first, so a bad argument is answered before anything is written and before the human is asked anything; a rewritten SKILL.md is re-parsed so a skill that lost its description (or its frontmatter) says so instead of failing quietly. The store is reloaded after a write, which makes a new skill visible to the very next skill_list. Writing is a host policy, not a directory-access grant: the denylist wins first, then a session grant, then [skills].allow_write, then the host's confirm hook — asked once per skill directory, so one answer covers its files. A host without a hook denies, which keeps the library default safe; the confirm can also answer 'session' to cover the rest of the chat.
This commit is contained in:
@@ -868,6 +868,41 @@ async def test_pretty_skill_tools(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert "* Skill Search 'nope' (no matches)" in out
|
||||
|
||||
|
||||
async def test_pretty_skill_create_and_edit(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
await render_events(
|
||||
_aiter(
|
||||
[
|
||||
_pretty_call("skill_create", '{"name": "acme", "description": "d", "body": "b"}'),
|
||||
_result("created skill 'acme' at /skills/acme\nnotes: SKILL.md has no frontmatter description"),
|
||||
]
|
||||
)
|
||||
)
|
||||
out = capsys.readouterr().out
|
||||
assert "* Skill Create 'acme' (created) — SKILL.md has no frontmatter description" in out
|
||||
|
||||
await render_events(
|
||||
_aiter(
|
||||
[
|
||||
_pretty_call("skill_edit", '{"name": "acme"}'),
|
||||
_result("updated skill 'acme' (SKILL.md: replaced 1 of 1 occurrence(s))"),
|
||||
]
|
||||
)
|
||||
)
|
||||
out = capsys.readouterr().out
|
||||
assert "* Skill Edit 'acme' (SKILL.md: replaced 1 of 1 occurrence(s))" in out
|
||||
|
||||
await render_events(
|
||||
_aiter(
|
||||
[
|
||||
_pretty_call("skill_edit", '{"name": "acme", "file": "scripts/run.sh"}'),
|
||||
_result("error: skill write to /skills/acme denied (declined or timed out after 30s)"),
|
||||
]
|
||||
)
|
||||
)
|
||||
out = capsys.readouterr().out
|
||||
assert "* Skill Edit 'acme' scripts/run.sh (error: skill write to /skills/acme denied" in out
|
||||
|
||||
|
||||
async def test_pretty_prefix_shown_while_call_runs(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
|
||||
Reference in New Issue
Block a user