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:
@@ -9,8 +9,8 @@
|
|||||||
"work_types": {
|
"work_types": {
|
||||||
"review": { "providers": ["anthropic"] },
|
"review": { "providers": ["anthropic"] },
|
||||||
"draft": { "providers": ["anthropic"] },
|
"draft": { "providers": ["anthropic"] },
|
||||||
"research": { "providers": ["anthropic", "minimax"] },
|
"research": { "providers": ["anthropic", "minimax"], "allowed_tools": ["WebSearch", "WebFetch"] },
|
||||||
"topic_ideas": { "providers": ["anthropic", "minimax"] }
|
"topic_ideas": { "providers": ["anthropic", "minimax"], "allowed_tools": ["WebSearch", "WebFetch"] }
|
||||||
},
|
},
|
||||||
"dossiers": ["ai", "ai-technical", "devops-2020", "devops-2020-technical"],
|
"dossiers": ["ai", "ai-technical", "devops-2020", "devops-2020-technical"],
|
||||||
"review_score_threshold": 11,
|
"review_score_threshold": 11,
|
||||||
|
|||||||
@@ -30,6 +30,33 @@ anything new and relevant you find under the evidence directories above:
|
|||||||
for a reader (a scene, a named organisation's experience, a practitioner's account)
|
for a reader (a scene, a named organisation's experience, a practitioner's account)
|
||||||
— still sourced, but framed as narrative rather than a bare statistic.
|
— still sourced, but framed as narrative rather than a bare statistic.
|
||||||
|
|
||||||
|
## Required: web search for current stories (before writing Subjective stories)
|
||||||
|
|
||||||
|
You have `WebSearch` and `WebFetch` available. Before writing the **Subjective
|
||||||
|
stories** section, run **at least one `WebSearch`** for recent stories, case
|
||||||
|
studies, or practitioner accounts directly related to this topic — derive your
|
||||||
|
query terms from the topic brief (`$overview_path`) and commissioning brief
|
||||||
|
(`$agent_path`), not from the dossier title alone. This is not optional and not
|
||||||
|
decorative: a research file with zero web search calls and zero web-sourced
|
||||||
|
entries is incomplete.
|
||||||
|
|
||||||
|
- Web-sourced entries go in Subjective stories (or Objective evidence, if the
|
||||||
|
find is a concrete measurable rather than a narrative) alongside the local
|
||||||
|
evidence-dir findings — web search **complements** the local evidence dirs,
|
||||||
|
it never replaces them. Local evidence-dir citation rules above are
|
||||||
|
unchanged.
|
||||||
|
- Cite the **full URL** and the **access date** (today's date) for every
|
||||||
|
web-sourced entry, e.g. `https://example.com/post (accessed 2026-08-02)`.
|
||||||
|
- Apply the dossier's status-label family correctly to web sources: a company
|
||||||
|
engineering-blog post is "Named organisation, self-reported"; vendor
|
||||||
|
marketing or a vendor's own benchmark is "Vendor data, treat with caution";
|
||||||
|
an industry survey is "Research-survey finding"; use "GOES synthesis / bet"
|
||||||
|
only for your own synthesis, never for a source you found.
|
||||||
|
- **Only cite a URL actually returned by `WebSearch` or fetched via
|
||||||
|
`WebFetch`.** Never construct, guess, or paraphrase a plausible-looking URL.
|
||||||
|
A fabricated URL is grounds for outright rejection at human sampling —
|
||||||
|
treat it as equivalent to citing a dead file path.
|
||||||
|
|
||||||
## Citation rules (non-negotiable — read `$agents_root_path` for the full statement)
|
## Citation rules (non-negotiable — read `$agents_root_path` for the full statement)
|
||||||
|
|
||||||
- **Cite the file, not the concept.** Any code or spec reference must name the file
|
- **Cite the file, not the concept.** Any code or spec reference must name the file
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ Do **not** propose anything that duplicates or trivially rephrases one of these:
|
|||||||
|
|
||||||
$existing_titles
|
$existing_titles
|
||||||
|
|
||||||
|
## Check current discussion first
|
||||||
|
|
||||||
|
You have `WebSearch` available. Before proposing, run at least one `WebSearch`
|
||||||
|
to check what is currently being discussed in this dossier's topic area — this
|
||||||
|
keeps proposals grounded in what practitioners are actually talking about
|
||||||
|
right now, not just what the local evidence dirs already contain. This is a
|
||||||
|
lighter-touch check than the research work type's requirement: you do not need
|
||||||
|
to cite web sources in the proposal itself (evidence pointers below still come
|
||||||
|
from the evidence directories), just use the search to sanity-check relevance
|
||||||
|
and avoid pitching something already stale or already covered elsewhere.
|
||||||
|
|
||||||
## What to produce
|
## What to produce
|
||||||
|
|
||||||
Propose **up to $max_new** new candidate topics for this dossier, each grounded in
|
Propose **up to $max_new** new candidate topics for this dossier, each grounded in
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ def load_config(path: Path) -> dict:
|
|||||||
for wtype, wcfg in data["work_types"].items():
|
for wtype, wcfg in data["work_types"].items():
|
||||||
if "providers" not in wcfg or not isinstance(wcfg["providers"], list):
|
if "providers" not in wcfg or not isinstance(wcfg["providers"], list):
|
||||||
raise ConfigError(f"work_type '{wtype}' missing '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
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -749,12 +753,16 @@ def validate_output(work_type: str, text: str) -> tuple[bool, str]:
|
|||||||
# === Claude argv / execution ===
|
# === 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)]
|
argv = ["claude", "-p", "--max-turns", str(max_turns)]
|
||||||
if model_id:
|
if model_id:
|
||||||
argv += ["--model", model_id]
|
argv += ["--model", model_id]
|
||||||
for d in add_dirs:
|
for d in add_dirs:
|
||||||
argv += ["--add-dir", d]
|
argv += ["--add-dir", d]
|
||||||
|
if allowed_tools:
|
||||||
|
argv += ["--allowedTools", ",".join(allowed_tools)]
|
||||||
return argv
|
return argv
|
||||||
|
|
||||||
|
|
||||||
@@ -801,6 +809,14 @@ def resolve_add_dirs(config: dict, work_type: str) -> list[str]:
|
|||||||
return []
|
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:
|
def dispatch_preview(repo: Path, config: dict, candidate: dict, max_turns: int) -> dict:
|
||||||
"""Compute everything a dryrun needs to print, without executing anything."""
|
"""Compute everything a dryrun needs to print, without executing anything."""
|
||||||
dossier = candidate["dossier"]
|
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"])
|
profile_dir = expand(config["providers"][provider]["profile"])
|
||||||
model_id = resolve_model_id(profile_dir)
|
model_id = resolve_model_id(profile_dir)
|
||||||
add_dirs = resolve_add_dirs(config, work_type)
|
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)
|
canonical = canonical_output_path(repo, candidate)
|
||||||
tmp_path = canonical.with_name(canonical.name + ".tmp")
|
tmp_path = canonical.with_name(canonical.name + ".tmp")
|
||||||
parsed_env = parse_provider_env(profile_dir / "provider.env")
|
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"])
|
profile_dir = expand(config["providers"][provider]["profile"])
|
||||||
model_id = resolve_model_id(profile_dir)
|
model_id = resolve_model_id(profile_dir)
|
||||||
add_dirs = resolve_add_dirs(config, work_type)
|
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)
|
env = build_child_env(profile_dir)
|
||||||
|
|
||||||
timed_out = False
|
timed_out = False
|
||||||
|
|||||||
@@ -228,7 +228,12 @@ For the selected `(item_or_dossier, work_type, provider)`:
|
|||||||
4. Build the argv: `claude -p --max-turns <N>` (`N` = 25 unless overridden), plus
|
4. Build the argv: `claude -p --max-turns <N>` (`N` = 25 unless overridden), plus
|
||||||
`--model <id>` if `provider.env` sets `MODEL_ID`, plus `--add-dir <dir>` for each
|
`--model <id>` if `provider.env` sets `MODEL_ID`, plus `--add-dir <dir>` for each
|
||||||
entry in `config["evidence_dirs"]` **only** for `research` and `topic_ideas` work
|
entry in `config["evidence_dirs"]` **only** for `research` and `topic_ideas` work
|
||||||
types (the only ones that cite external evidence).
|
types (the only ones that cite external evidence), plus `--allowedTools
|
||||||
|
"<comma-joined list>"` if `config["work_types"][work_type]["allowed_tools"]` is
|
||||||
|
present and non-empty (see Config schema below). Headless `claude -p` runs cannot
|
||||||
|
answer permission prompts, so any tool beyond the CLI's defaults must be granted
|
||||||
|
this way or the child run stalls/fails; when the key is absent or an empty list,
|
||||||
|
the flag is omitted entirely (unchanged pre-existing behaviour).
|
||||||
5. Run the subprocess: `cwd=<repo>`, `env=<built env>`, prompt piped via **stdin**
|
5. Run the subprocess: `cwd=<repo>`, `env=<built env>`, prompt piped via **stdin**
|
||||||
(not as an argv element — avoids `ARG_MAX` on large rendered prompts, same lesson
|
(not as an argv element — avoids `ARG_MAX` on large rendered prompts, same lesson
|
||||||
`claude-profile` already applies to its system-prompt injection), timeout = per-task
|
`claude-profile` already applies to its system-prompt injection), timeout = per-task
|
||||||
@@ -311,6 +316,17 @@ already says) is never written here.
|
|||||||
`idle-draft.config.json` — see `data/idle-draft/config.example.json` for the exact
|
`idle-draft.config.json` — see `data/idle-draft/config.example.json` for the exact
|
||||||
structure. All top-level keys listed in §3 are required.
|
structure. All top-level keys listed in §3 are required.
|
||||||
|
|
||||||
|
Each entry in `work_types` may carry an optional `allowed_tools` key: a list of Claude
|
||||||
|
Code tool names (e.g. `["WebSearch", "WebFetch"]`) granted to that work type's headless
|
||||||
|
child via `--allowedTools` (§11 step 4). Present-but-empty and absent are both treated
|
||||||
|
as "no extra tools" — the flag is omitted. If present, it must be a JSON array of
|
||||||
|
strings; any other shape (a string, a number, a list containing a non-string) is a
|
||||||
|
config validation failure — exit **2** at config load, same as a missing required key.
|
||||||
|
`config.example.json` sets `allowed_tools: ["WebSearch", "WebFetch"]` on `research` and
|
||||||
|
`topic_ideas` (the research prompt template requires at least one `WebSearch` call
|
||||||
|
before writing its Subjective stories section; the topic-ideas template requires one as
|
||||||
|
a lighter-touch check); `draft` and `review` carry no `allowed_tools` key.
|
||||||
|
|
||||||
## Failure classes
|
## Failure classes
|
||||||
|
|
||||||
| Class | Examples | `attempts` effect | Item outcome |
|
| Class | Examples | `attempts` effect | Item outcome |
|
||||||
@@ -344,6 +360,7 @@ structure. All top-level keys listed in §3 are required.
|
|||||||
| `--probe-json` combined with `--dryrun` | Composes normally — no subprocess call either way |
|
| `--probe-json` combined with `--dryrun` | Composes normally — no subprocess call either way |
|
||||||
| `--once` with no eligible work | Exits 0 immediately, no task dispatched |
|
| `--once` with no eligible work | Exits 0 immediately, no task dispatched |
|
||||||
| Config missing a required key | Exit 2 |
|
| Config missing a required key | Exit 2 |
|
||||||
|
| `work_types[*].allowed_tools` present but not a list of strings | Exit 2 |
|
||||||
| `agent-subscriptions` subprocess fails entirely | Exit 1, loud log line, no dispatch attempted |
|
| `agent-subscriptions` subprocess fails entirely | Exit 1, loud log line, no dispatch attempted |
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ cat > "$FIXTURE_CONFIG" <<EOF
|
|||||||
"work_types": {
|
"work_types": {
|
||||||
"review": { "providers": ["anthropic"] },
|
"review": { "providers": ["anthropic"] },
|
||||||
"draft": { "providers": ["anthropic"] },
|
"draft": { "providers": ["anthropic"] },
|
||||||
"research": { "providers": ["anthropic", "minimax"] },
|
"research": { "providers": ["anthropic", "minimax"], "allowed_tools": ["WebSearch", "WebFetch"] },
|
||||||
"topic_ideas": { "providers": ["anthropic", "minimax"] }
|
"topic_ideas": { "providers": ["anthropic", "minimax"], "allowed_tools": ["WebSearch", "WebFetch"] }
|
||||||
},
|
},
|
||||||
"dossiers": ["ai"],
|
"dossiers": ["ai"],
|
||||||
"review_score_threshold": 11,
|
"review_score_threshold": 11,
|
||||||
@@ -135,6 +135,7 @@ assert_contains "$output" "item: ai/03-topic-three" "dryrun picks item 03 (
|
|||||||
assert_contains "$output" "work_type: review" "dryrun names work_type review"
|
assert_contains "$output" "work_type: review" "dryrun names work_type review"
|
||||||
assert_contains "$output" "provider: anthropic" "dryrun names provider anthropic"
|
assert_contains "$output" "provider: anthropic" "dryrun names provider anthropic"
|
||||||
assert_not_contains "$output" "would run" "dryrun does not execute claude"
|
assert_not_contains "$output" "would run" "dryrun does not execute claude"
|
||||||
|
assert_not_contains "$output" "--allowedTools" "dryrun argv for review (no allowed_tools configured) omits the flag"
|
||||||
|
|
||||||
echo "-- dryrun: exact resolved claude argv for a research item --"
|
echo "-- dryrun: exact resolved claude argv for a research item --"
|
||||||
# Force only the research-ready item to be eligible by pointing --repo at a
|
# Force only the research-ready item to be eligible by pointing --repo at a
|
||||||
@@ -151,6 +152,8 @@ output=$("$SCRIPT" --config "$FIXTURE_CONFIG" --repo "$SOLO" --probe-json "$FIXT
|
|||||||
assert_contains "$output" "work_type: research" "dryrun (solo fixture) picks research"
|
assert_contains "$output" "work_type: research" "dryrun (solo fixture) picks research"
|
||||||
assert_contains "$output" "argv: ['claude', '-p', '--max-turns', '25', '--add-dir'" "dryrun prints resolved claude argv with --add-dir for research"
|
assert_contains "$output" "argv: ['claude', '-p', '--max-turns', '25', '--add-dir'" "dryrun prints resolved claude argv with --add-dir for research"
|
||||||
assert_contains "$output" "$TMPDIR/evidence" "dryrun argv includes configured evidence dir"
|
assert_contains "$output" "$TMPDIR/evidence" "dryrun argv includes configured evidence dir"
|
||||||
|
assert_contains "$output" "--allowedTools" "dryrun argv for research includes --allowedTools (allowed_tools configured)"
|
||||||
|
assert_contains "$output" "WebSearch,WebFetch" "dryrun argv --allowedTools value is the comma-joined tool list"
|
||||||
|
|
||||||
echo "-- dryrun: no mutation --"
|
echo "-- dryrun: no mutation --"
|
||||||
before_hash=$(cd "$FIXTURE_REPO" && git rev-parse HEAD)
|
before_hash=$(cd "$FIXTURE_REPO" && git rev-parse HEAD)
|
||||||
@@ -182,6 +185,31 @@ output=$("$SCRIPT" --config "$BADCONFIG" --repo "$FIXTURE_REPO" --probe-json "$F
|
|||||||
code=$?
|
code=$?
|
||||||
assert_exit_code "$code" 2 "config missing required keys exits 2"
|
assert_exit_code "$code" 2 "config missing required keys exits 2"
|
||||||
|
|
||||||
|
BADALLOWED="$TMPDIR/bad-allowed.config.json"
|
||||||
|
cat > "$BADALLOWED" <<EOF
|
||||||
|
{
|
||||||
|
"parallel": 2,
|
||||||
|
"providers": {
|
||||||
|
"anthropic": { "profile": "$PROFILE_ANTHROPIC", "threshold_pct": 80, "five_hour_ceiling": 50, "min_idle": 5 }
|
||||||
|
},
|
||||||
|
"work_types": {
|
||||||
|
"review": { "providers": ["anthropic"] },
|
||||||
|
"draft": { "providers": ["anthropic"] },
|
||||||
|
"research": { "providers": ["anthropic"], "allowed_tools": "WebSearch" },
|
||||||
|
"topic_ideas": { "providers": ["anthropic"] }
|
||||||
|
},
|
||||||
|
"dossiers": ["ai"],
|
||||||
|
"review_score_threshold": 11,
|
||||||
|
"max_unreviewed_research_per_dossier": 3,
|
||||||
|
"max_open_topic_proposals": 6,
|
||||||
|
"evidence_dirs": ["$TMPDIR/evidence"]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
output=$("$SCRIPT" --config "$BADALLOWED" --repo "$FIXTURE_REPO" --probe-json "$FIXTURE_PROBE" --dryrun 2>&1)
|
||||||
|
code=$?
|
||||||
|
assert_exit_code "$code" 2 "allowed_tools not a list of strings exits 2"
|
||||||
|
assert_contains "$output" "allowed_tools" "allowed_tools config error names the offending key"
|
||||||
|
|
||||||
echo "-- mark subcommand round-trip --"
|
echo "-- mark subcommand round-trip --"
|
||||||
MARKREPO="$TMPDIR/mark-repo"
|
MARKREPO="$TMPDIR/mark-repo"
|
||||||
mkdir -p "$MARKREPO/ai"
|
mkdir -p "$MARKREPO/ai"
|
||||||
@@ -398,6 +426,55 @@ check("build_child_env: profile without provider.env gets only CLAUDE_CONFIG_DIR
|
|||||||
"ANTHROPIC_BASE_URL" not in env2 and "ANTHROPIC_API_KEY" not in env2 and env2["CLAUDE_CONFIG_DIR"] == str(no_provider_env_dir),
|
"ANTHROPIC_BASE_URL" not in env2 and "ANTHROPIC_API_KEY" not in env2 and env2["CLAUDE_CONFIG_DIR"] == str(no_provider_env_dir),
|
||||||
env2)
|
env2)
|
||||||
|
|
||||||
|
# --- allowed_tools / --allowedTools argv ---
|
||||||
|
argv = m.build_claude_argv("model-x", 25, ["/tmp/ev"], ["WebSearch", "WebFetch"])
|
||||||
|
check("build_claude_argv: appends --allowedTools with comma-joined list when provided",
|
||||||
|
"--allowedTools" in argv and argv[argv.index("--allowedTools") + 1] == "WebSearch,WebFetch", argv)
|
||||||
|
|
||||||
|
argv_empty = m.build_claude_argv("model-x", 25, ["/tmp/ev"], [])
|
||||||
|
check("build_claude_argv: omits --allowedTools when allowed_tools is an empty list", "--allowedTools" not in argv_empty, argv_empty)
|
||||||
|
|
||||||
|
argv_none = m.build_claude_argv("model-x", 25, ["/tmp/ev"], None)
|
||||||
|
check("build_claude_argv: omits --allowedTools when allowed_tools is None", "--allowedTools" not in argv_none, argv_none)
|
||||||
|
|
||||||
|
cfg_at = {
|
||||||
|
"work_types": {
|
||||||
|
"research": {"providers": ["anthropic"], "allowed_tools": ["WebSearch", "WebFetch"]},
|
||||||
|
"draft": {"providers": ["anthropic"]},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("resolve_allowed_tools: returns configured list for research",
|
||||||
|
m.resolve_allowed_tools(cfg_at, "research") == ["WebSearch", "WebFetch"], m.resolve_allowed_tools(cfg_at, "research"))
|
||||||
|
check("resolve_allowed_tools: returns [] when work_type has no allowed_tools key",
|
||||||
|
m.resolve_allowed_tools(cfg_at, "draft") == [], m.resolve_allowed_tools(cfg_at, "draft"))
|
||||||
|
check("resolve_allowed_tools: returns [] for a work_type not present in config at all",
|
||||||
|
m.resolve_allowed_tools(cfg_at, "review") == [], m.resolve_allowed_tools(cfg_at, "review"))
|
||||||
|
|
||||||
|
bad_allowed_cfg_path = tmpdir / "bad-allowed-direct.config.json"
|
||||||
|
bad_allowed_cfg = json.loads((tmpdir / "idle-draft.config.json").read_text())
|
||||||
|
bad_allowed_cfg["work_types"]["research"]["allowed_tools"] = "WebSearch"
|
||||||
|
bad_allowed_cfg_path.write_text(json.dumps(bad_allowed_cfg))
|
||||||
|
try:
|
||||||
|
m.load_config(bad_allowed_cfg_path)
|
||||||
|
check("load_config: allowed_tools as a bare string (not a list) raises ConfigError", False)
|
||||||
|
except m.ConfigError as e:
|
||||||
|
check("load_config: allowed_tools as a bare string (not a list) raises ConfigError", "allowed_tools" in str(e), str(e))
|
||||||
|
|
||||||
|
bad_allowed_cfg2_path = tmpdir / "bad-allowed-direct2.config.json"
|
||||||
|
bad_allowed_cfg2 = json.loads((tmpdir / "idle-draft.config.json").read_text())
|
||||||
|
bad_allowed_cfg2["work_types"]["research"]["allowed_tools"] = ["WebSearch", 5]
|
||||||
|
bad_allowed_cfg2_path.write_text(json.dumps(bad_allowed_cfg2))
|
||||||
|
try:
|
||||||
|
m.load_config(bad_allowed_cfg2_path)
|
||||||
|
check("load_config: allowed_tools list containing a non-string raises ConfigError", False)
|
||||||
|
except m.ConfigError as e:
|
||||||
|
check("load_config: allowed_tools list containing a non-string raises ConfigError", "allowed_tools" in str(e), str(e))
|
||||||
|
|
||||||
|
good_allowed_cfg = m.load_config(tmpdir / "idle-draft.config.json")
|
||||||
|
check("load_config: well-formed allowed_tools (list of strings) loads without error",
|
||||||
|
good_allowed_cfg["work_types"]["research"]["allowed_tools"] == ["WebSearch", "WebFetch"],
|
||||||
|
good_allowed_cfg["work_types"]["research"])
|
||||||
|
|
||||||
# --- Citation validation ---
|
# --- Citation validation ---
|
||||||
real_path = cred_repo / "exists.txt"
|
real_path = cred_repo / "exists.txt"
|
||||||
real_path.write_text("x")
|
real_path.write_text("x")
|
||||||
|
|||||||
Reference in New Issue
Block a user