Something went wrong. Try again.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091/** * ModeController — selects the active mode and narrows the toolset (ARCHITECTURE §4.4, §5). * * The turn loop consults `narrow(tools)` BEFORE the provider call so that the model only * sees tools the current mode permits. Modes are additive: the loop never branches on * `mode === 'plan'`, it always goes through these hooks. */import { Value } from '@sinclair/typebox/value';import { ModeDescriptorSchema, type Mode, type ModeDescriptor } from '@/contracts/mode';import type { Tool } from '@/contracts/tool';
export class ModeController { private modes = new Map<string, Mode>(); private current: Mode;
constructor(defaultMode: Mode) { this.validateDescriptor(defaultMode); this.current = defaultMode; this.modes.set(defaultMode.name, defaultMode); }
/** Register a mode. Overwrites if a mode with the same name already exists. * * saga vr7kgx.6 hardening: the descriptor (name + description) is validated against the * canonical {@link ModeDescriptorSchema} bounds (name 1–64, description 1–512) BEFORE * insertion so an invalid extension mode fails explicitly here rather than emitting * invalid wire data from {@link ModeController.list}. Duplicate/replacement semantics * for VALID modes are preserved (same-name register still overwrites in place). */ register(mode: Mode): void { this.validateDescriptor(mode); this.modes.set(mode.name, mode); }
/** Validate a mode's secret-free descriptor against the canonical wire bounds. Throws * an explicit error naming the offending mode so a bad extension fails at registration * (the only insertion point) instead of leaking invalid data through `list()`. */ private validateDescriptor(mode: Mode): void { const descriptor: ModeDescriptor = { name: mode.name, description: mode.description }; if (!Value.Check(ModeDescriptorSchema, descriptor)) { throw new Error( `ModeController: invalid mode descriptor for "${mode.name}" — name must be 1–64 chars and description 1–512 chars (saga vr7kgx.6)`, ); } }
/** Look up a mode by name. Returns undefined if no mode with that name is registered. */ select(name: string): Mode | undefined { return this.modes.get(name); }
/** * Read-only catalog snapshot (saga vr7kgx.6): every registered mode as a secret-free * {@link ModeDescriptor}, in registration order (insertion order — `Map` preserves it), * plus the active mode's name. The active name is always present in the catalog (the * default mode is registered in the constructor; `setActive` throws for unknown names). * Returns a fresh immutable copy so callers cannot mutate controller state. */ list(): { modes: ModeDescriptor[]; active: string } { const modes: ModeDescriptor[] = []; for (const mode of this.modes.values()) { modes.push({ name: mode.name, description: mode.description }); } return { modes, active: this.current.name }; }
/** Set the active mode by name. Throws if the name is unknown. */ setActive(name: string): void { const mode = this.modes.get(name); if (!mode) { throw new Error(`ModeController: unknown mode "${name}"`); } this.current = mode; }
/** The currently active mode. */ active(): Mode { return this.current; }
/** * Narrow a tool list by applying the active mode's `filterTools`. * If the active mode does not define `filterTools`, all tools pass through. */ narrow(tools: Tool[]): Tool[] { if (this.current.filterTools) { return this.current.filterTools(tools); } return tools; }}