Repository navigation
Parse subagent frontmatter tolerantly (kebab>camel>snake precedence) - #283
raymondginger2018-sudo wants to merge 2 commits into
Conversation
|
Thank you, @raymondginger2018-sudo.
Also, the description says |
Agent preset frontmatter reads tools / disallowed-tools with key precedence
kebab > camel > snake; foreign dialects degrade to None instead of breaking
the preset load. The deny list feeds AgentPresetSnapshot.tool_filter()
(allowlist minus denylist, deny wins).
disallowed-tools is a restriction, so it parses strictly like tools: a
malformed value marks the preset broken instead of silently dropping the
deny list — a dropped restriction would keep the very tools the author
tried to remove. '*' is rejected in both spellings: the bare string folds
to unrestricted, and ["*"] would deny nothing.
max-turns and memory are no longer parsed or stored — nothing reads them,
and Claude Code's memory is a scope string, not a bool. permission-mode
stays out too: permission remains an independent session-level knob and
the preset never carries it.
/preset, the preset picker, and agent_preset_view now show the deny list
("all tools except ...") instead of claiming "all tools"; the wire entry
gains disallowedTools (schema + generated TS).
Default presets parse identically to before.
da541ea to
a792522
Compare
|
Thanks @Zongwei9888 — all four points are addressed in
Tests: |
AgentPresetEntry gained a required disallowedTools: string[] | null in the app-server protocol (schema + generated types). The two fixture literals predate the field, so tsc --noEmit failed with TS2741 x2 and turned Desktop quality gates red on a792522. Both fixtures now report null, matching the wire contract's absent-never-empty-claim rule.
Summary
Makes subagent preset frontmatter parsing tolerant of foreign dialects (Claude Code / Codex style) without breaking existing presets: unknown or differently-cased keys degrade gracefully instead of failing the load. The deny list (
disallowed-tools) parses strictly and fail-closed — a malformed value marks the preset broken instead of silently dropping the restriction.Changes
core/agent_presets/__init__.py:disallowed-tools>disallowedTools>disallowed_tools).tool_filtercomputed as allowlist minusdisallowed_tools(deny wins).disallowed-tools: a malformed value (non-string entry, wrong type) marks the preset broken through the same path as an invalidtools:— the agent never keeps tools the author tried to remove.*is rejected in both spellings: bare"*"would fold to unrestricted (deny dropped),["*"]would normalize to a deny entry no roster matches (denies nothing); both invert the author's intent, so both mark the preset broken.max-turns/memoryare not parsed or stored — nothing reads them, and Claude Code'smemoryis a scope string (user/project/local), not a bool.permission-modeis not parsed — permission remains an independent session-level knob; no mapping in this PR./preset(cli/tui/app.py), the preset picker (cli/tui/commands.py), andagent_preset_view(core/application/views.py) now render the deny list (all tools except ...) instead of claiming "all tools"; the wire entry gainsdisallowedTools(protocol/app-server.schema.json+ regenerateddesktop/src/generated/app-server.ts).tests/test_agent_presets.py: precedence orders, subtraction semantics, fail-closed broken marking for both*spellings, snapshot round-trip, and default-preset invariance.Tests
tests/test_agent_presets.py— 22 passedtests/app_server/test_preset_methods.py+tests/contract/test_protocol_schema.py(with the above) — 32 passedtests/test_tui.py— 63 passed (incl. new deny-list overview test)ruff check+ruff format --check— passedgenerate-protocol.mjs --check)Notes