e455e4afd0
tool_search now takes queries: string[] (searched independently against the same catalog, limit applies per query, default 5 / max 25) and returns the split shape: per-query groups carry tool names only, one shared tools map holds each matched tool's source, description (400-char cap) and required parameter names once. When some queries miss, a single top-level available_sources + hint block replaces the old per-response fallback. tool_describe now takes names: string[] and returns a map keyed by name; unknown names collect in not_found (with the refresh hint) and non-deferrable names keep their per-name spelling-check error in errors, so one bad name no longer fails the whole call. Duplicates dedupe silently. The shared tokenizer now applies Snowball stemming (english, exact-pinned snowballstemmer) at both index and query time, closing the measured plural/singular miss where 'issues' failed to return create_issue. The inline BM25 is unchanged. Stemmer instances are thread-local (they carry mutable parse state and bridge dispatch can run on parallel tool-call threads). New config knobs under tools.tool_search: max_queries / max_describe_names (default 10 each, floor 1, no upper clamp) bound the per-call array inputs; over-cap calls error so the model repairs in one round-trip. No backward compatibility with the single query/name shapes, by decision. scripts/analyze_livetest.py renders both shapes since transcripts on disk may predate this change.