feat(tools): the P2 toggle bridge — toolToggles first-class, definitive vocabulary
Reconciliation of the diverged tool-toggles seam (2026-09-19, master's P2): Platform injected the resolved delta as an ad-hoc intersection on AgentBuildOptions while the factories' enabled? param sat unread — the seam was inert on both sides. - teacher-agent: AgentBuildOptions gains toolToggles?: EnabledTools; both constructors thread it to the factories (createTeacherTools / createProberTools). An explicitly injected opts.tools registry is never filtered — the caller owns it. - Vocabulary ADJUDICATED with the user (2026-09-19): TOGGLEABLE_TOOLS is the definitive list — the job tools only (research/demo/visual/pdf). quiz/ask_user_question stay always-on forever (the prober's flow is graded-quiz brackets -> probe_done; quiz-off collapses assessment and teaching doctrine is built on graded verification — the wedge danger); the learner-fs set, plan tools, and probe_done also always-on. - settings-body (write side): unknown tool keys reject with 400 + the supported list, mirroring the prompt-switch validation; the read-side sanitizer stays lenient for forward-compat. - chat-handler: the intersection is gone (field now first-class) and the seam comment states the adjudicated vocabulary. Gates: typecheck, 28 new/updated tests green (tool-toggles-bridge, question-tools, settings-body), chat-flow + settings suites green.
This commit is contained in:
@@ -22,6 +22,7 @@ import type {
|
||||
RoleModelOverride,
|
||||
} from "../core/settings.ts";
|
||||
import { BACKGROUND_MODEL_ROLES } from "../core/settings.ts";
|
||||
import { TOGGLEABLE_TOOLS } from "../core/pending.ts";
|
||||
|
||||
/** Parsed-or-rejected — `ok: false` carries the 400's error message. */
|
||||
export type SettingsBodyResult<T> =
|
||||
@@ -166,7 +167,20 @@ export function parseAgentSettingsBody(body: unknown): SettingsBodyResult<AgentS
|
||||
}
|
||||
const tools = parseBooleanMap(raw.tools, "tools");
|
||||
if (tools.error) return { ok: false, error: tools.error };
|
||||
if (tools.map) settings.tools = tools.map;
|
||||
if (tools.map) {
|
||||
// Unknown tool keys are rejected at WRITE time (user decision
|
||||
// 2026-09-19): the delta is inert while unread, but once the bridge
|
||||
// honors it a persisted `quiz: false` would genuinely drop the quiz —
|
||||
// and the definitive list is the adjudicated job-tools-only set.
|
||||
// The read-side sanitizer stays lenient for forward-compat.
|
||||
const supported = TOGGLEABLE_TOOLS as readonly string[];
|
||||
for (const key of Object.keys(tools.map)) {
|
||||
if (!supported.includes(key)) {
|
||||
return { ok: false, error: `unknown tool "${key}" (supported: ${supported.join(", ")})` };
|
||||
}
|
||||
}
|
||||
settings.tools = tools.map;
|
||||
}
|
||||
const prompts = parseBooleanMap(raw.prompts, "prompts");
|
||||
if (prompts.error) return { ok: false, error: prompts.error };
|
||||
if (prompts.map) settings.prompts = prompts.map;
|
||||
|
||||
Reference in New Issue
Block a user