import type { GoalConfig } from "./types.ts"; export const DEFAULT_CONFIG: GoalConfig = { enabled: true, skepticN: 3, verifyMax: 10, stallThreshold: 2, strategistEvery: 5, // max(1, 10/2) plannerRequired: true, preverify: true, receipts: true, subagentsTimeoutMs: 600_000, /** Planner research can exceed panel waits (B029). */ plannerTimeoutMs: 1_800_000, }; function clampInt(value: number, min: number, max?: number): number { let n = Math.trunc(value); if (!Number.isFinite(n)) n = min; if (n < min) n = min; if (max !== undefined && n > max) n = max; return n; } /** * Merge a partial config over defaults with clamping: * - skepticN: 1–5 * - verifyMax: ≥ 1 * - stallThreshold: ≥ 1 * - strategistEvery: ≥ 1 (default max(1, verifyMax/2) when omitted) * - subagentsTimeoutMs: ≥ 1 * - plannerTimeoutMs: ≥ 1 */ export function resolveConfig(partial?: Partial): GoalConfig { const base = { ...DEFAULT_CONFIG, ...(partial ?? {}) }; const verifyMax = clampInt(base.verifyMax, 1); const skepticN = clampInt(base.skepticN, 1, 5); const stallThreshold = clampInt(base.stallThreshold, 1); const subagentsTimeoutMs = clampInt(base.subagentsTimeoutMs, 1); const plannerTimeoutMs = clampInt(base.plannerTimeoutMs, 1); // If caller omitted strategistEvery, derive from resolved verifyMax. const strategistEvery = partial?.strategistEvery === undefined ? Math.max(1, Math.floor(verifyMax / 2)) : clampInt(base.strategistEvery, 1); return { enabled: Boolean(base.enabled), skepticN, verifyMax, stallThreshold, strategistEvery, plannerRequired: Boolean(base.plannerRequired), preverify: Boolean(base.preverify), receipts: Boolean(base.receipts), subagentsTimeoutMs, plannerTimeoutMs, }; }