idle-draft: per-work-type allowed_tools; research must web-search for stories

Research and topic_ideas runs get --allowedTools WebSearch,WebFetch;
research template mandates >=1 web search for related stories with
full-URL + access-date citations under the dossier's status labels.

Claude-Session: https://claude.ai/code/session_01YQDoWNM7XPPii28khFWoMc
This commit is contained in:
Paul O'Reilly
2026-08-02 21:32:35 +12:00
parent cd5be1cf92
commit 75add58246
6 changed files with 158 additions and 8 deletions

View File

@@ -223,6 +223,10 @@ def load_config(path: Path) -> dict:
for wtype, wcfg in data["work_types"].items():
if "providers" not in wcfg or not isinstance(wcfg["providers"], list):
raise ConfigError(f"work_type '{wtype}' missing 'providers' list")
if "allowed_tools" in wcfg:
at = wcfg["allowed_tools"]
if not isinstance(at, list) or not all(isinstance(x, str) for x in at):
raise ConfigError(f"work_type '{wtype}' 'allowed_tools' must be a list of strings")
return data
@@ -749,12 +753,16 @@ def validate_output(work_type: str, text: str) -> tuple[bool, str]:
# === Claude argv / execution ===
def build_claude_argv(model_id: str | None, max_turns: int, add_dirs: list[str]) -> list[str]:
def build_claude_argv(
model_id: str | None, max_turns: int, add_dirs: list[str], allowed_tools: list[str] | None = None
) -> list[str]:
argv = ["claude", "-p", "--max-turns", str(max_turns)]
if model_id:
argv += ["--model", model_id]
for d in add_dirs:
argv += ["--add-dir", d]
if allowed_tools:
argv += ["--allowedTools", ",".join(allowed_tools)]
return argv
@@ -801,6 +809,14 @@ def resolve_add_dirs(config: dict, work_type: str) -> list[str]:
return []
def resolve_allowed_tools(config: dict, work_type: str) -> list[str]:
"""Tool names granted to the headless child via --allowedTools, from
config["work_types"][work_type]["allowed_tools"]. Empty/absent -> []
(caller omits the flag; headless runs cannot answer permission prompts,
so only tools explicitly listed here are usable by that work type)."""
return list(config["work_types"].get(work_type, {}).get("allowed_tools") or [])
def dispatch_preview(repo: Path, config: dict, candidate: dict, max_turns: int) -> dict:
"""Compute everything a dryrun needs to print, without executing anything."""
dossier = candidate["dossier"]
@@ -809,7 +825,8 @@ def dispatch_preview(repo: Path, config: dict, candidate: dict, max_turns: int)
profile_dir = expand(config["providers"][provider]["profile"])
model_id = resolve_model_id(profile_dir)
add_dirs = resolve_add_dirs(config, work_type)
argv = build_claude_argv(model_id, max_turns, add_dirs)
allowed_tools = resolve_allowed_tools(config, work_type)
argv = build_claude_argv(model_id, max_turns, add_dirs, allowed_tools)
canonical = canonical_output_path(repo, candidate)
tmp_path = canonical.with_name(canonical.name + ".tmp")
parsed_env = parse_provider_env(profile_dir / "provider.env")
@@ -847,7 +864,8 @@ def run_task(repo: Path, config: dict, candidate: dict, max_turns: int, task_tim
profile_dir = expand(config["providers"][provider]["profile"])
model_id = resolve_model_id(profile_dir)
add_dirs = resolve_add_dirs(config, work_type)
argv = build_claude_argv(model_id, max_turns, add_dirs)
allowed_tools = resolve_allowed_tools(config, work_type)
argv = build_claude_argv(model_id, max_turns, add_dirs, allowed_tools)
env = build_child_env(profile_dir)
timed_out = False