diff --git a/components/settings/agent-section.tsx b/components/settings/agent-section.tsx index 72e053d..5a3608a 100644 --- a/components/settings/agent-section.tsx +++ b/components/settings/agent-section.tsx @@ -96,6 +96,19 @@ export function composeTool(agent: AgentSettings | undefined, id: string, on: bo return { ...agent, tools: { ...agent?.tools, [id]: on } }; } +/** The research tool runs on the search port — the teacher registry DROPS + * it when no engine is live (chat-handler's searchAvailable, the same + * /api/search signal the section fetches). Pure — exported for tests. + * Teacher-stream flag 2026-09-22: the settings row grays on the SAME + * signal, so the row and the actual registry availability can never + * disagree. `search === null` (loading/failed) grays NOTHING. */ +export function researchToolUnavailable( + toolId: string, + search: { available: boolean } | null, +): boolean { + return toolId === "research" && search !== null && !search.available; +} + /** Compose: one slot's model/thinking override. Empty model = absent * override (Default — falls to the factory pin); empty thinking = the * tier keeps its resolved thinking budget. Per-field independence, same @@ -295,7 +308,6 @@ export function AgentSection({ .then((r) => r.json()) .then((body: { prober: SegmentInfo[]; teacher: SegmentInfo[] }) => setPrompts(body)) .catch(() => setPrompts(null)); - refreshSearch(); fetch("/api/keys") .then((r) => r.json()) .then((body: { keys: { id: string; present: boolean }[] }) => { @@ -309,6 +321,14 @@ export function AgentSection({ .catch(() => {}); }, []); + // The reason line re-reads /api/search AFTER an engine write lands — + // the write updates the `agent` prop (the dialog's record mirror), so + // keying on the pin avoids the race where the fetch beat the PUT and + // the hint contradicted the trigger it sits under. + useEffect(() => { + refreshSearch(); + }, [agent?.search?.engine]); + // The dropdown contract (the chat model selector's, mirrored): any // click outside the open menu — or an Esc — closes it. Esc is CLAIMED // with stopPropagation so the settings dialog behind does NOT also @@ -393,6 +413,7 @@ export function AgentSection({ onPick: () => void, ) => (