{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/watchdog/settings.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE9E,OAAO,EACN,KAAK,sBAAsB,EAgB3B,KAAK,sBAAsB,EAI3B,MAAM,YAAY,CAAC;AAepB,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,SAAS,CAAC;AAC5D,MAAM,MAAM,2BAA2B,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErH,MAAM,WAAW,0BAA0B;IAC1C,KAAK,EAAE,0BAA0B,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,2BAA2B,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,GAAG,KAAK,GAAG,IAAI,CAAC;CACxC;AAuBD,eAAO,MAAM,uBAAuB,EAAE,sBAkDrC,CAAC;AAmaF,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAcD,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElE;AA8BD,wBAAgB,2BAA2B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GACjD,sBAAsB,CAoBxB;AA+CD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAQjE;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAWpF;AAED,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GACjD,sBAAsB,CAyCxB","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { THINKING_LEVELS, type ThinkingLevel } from \"../shared/model-info.ts\";\nimport { getAgentDir, getProjectConfigDir } from \"../shared/utils.ts\";\nimport {\n\ttype ResolvedWatchdogConfig,\n\tWATCHDOG_DELIVERY_MODES,\n\tWATCHDOG_LATE_WARNING_POLICIES,\n\tWATCHDOG_WARNING_SEVERITIES,\n\ttype WatchdogAsyncCompletionConfig,\n\ttype WatchdogAutoFollowConfig,\n\ttype WatchdogCadenceConfig,\n\ttype WatchdogChildOverrideConfig,\n\ttype WatchdogChildrenConfig,\n\ttype WatchdogDeliveryMode,\n\ttype WatchdogEndpointConfig,\n\ttype WatchdogGuidanceConfig,\n\ttype WatchdogLateWarningPolicy,\n\ttype WatchdogLspConfig,\n\ttype WatchdogScopeConfig,\n\ttype WatchdogSettingsError,\n\ttype WatchdogSettingsResult,\n\ttype WatchdogSettingsSource,\n\ttype WatchdogSeverity,\n\ttype WatchdogSyncBacklog,\n} from \"./types.ts\";\n\ntype WatchdogAutoFollowPatch = Partial<WatchdogAutoFollowConfig>;\ntype WatchdogGuidancePatch = Partial<WatchdogGuidanceConfig>;\ntype WatchdogScopePatch = Partial<WatchdogScopeConfig>;\ntype WatchdogCadencePatch = Partial<WatchdogCadenceConfig>;\ntype WatchdogEndpointPatch = Partial<WatchdogEndpointConfig>;\ntype WatchdogChildOverridePatch = Partial<WatchdogChildOverrideConfig>;\ntype WatchdogChildrenPatch = Partial<Omit<WatchdogChildrenConfig, \"autoFollow\" | \"overrides\">> & {\n\tautoFollow?: WatchdogAutoFollowPatch;\n\toverrides?: Record<string, WatchdogChildOverridePatch>;\n};\ntype WatchdogAsyncCompletionPatch = Partial<WatchdogAsyncCompletionConfig>;\ntype WatchdogLspPatch = Partial<WatchdogLspConfig>;\n\nexport type WatchdogSettingsWriteScope = \"user\" | \"project\";\nexport type WatchdogModelSettingsTarget = { kind: \"main\" } | { kind: \"children\" } | { kind: \"child\"; agent: string };\n\nexport interface WatchdogModelSettingsWrite {\n\tscope: WatchdogSettingsWriteScope;\n\tcwd?: string;\n\ttarget: WatchdogModelSettingsTarget;\n\tmodel?: string | null;\n\tthinking?: ThinkingLevel | false | null;\n}\n\ntype WatchdogConfigPatch = Partial<\n\tOmit<\n\t\tResolvedWatchdogConfig,\n\t\t\"guidance\" | \"autoFollow\" | \"scope\" | \"cadence\" | \"main\" | \"children\" | \"asyncCompletion\" | \"lsp\"\n\t>\n> & {\n\tguidance?: WatchdogGuidancePatch;\n\tautoFollow?: WatchdogAutoFollowPatch;\n\tscope?: WatchdogScopePatch;\n\tcadence?: WatchdogCadencePatch;\n\tmain?: WatchdogEndpointPatch;\n\tchildren?: WatchdogChildrenPatch;\n\tasyncCompletion?: WatchdogAsyncCompletionPatch;\n\tlsp?: WatchdogLspPatch;\n};\n\ninterface ParseMeta {\n\tscope: \"user\" | \"project\" | \"session\";\n\tpath?: string;\n}\n\nexport const DEFAULT_WATCHDOG_CONFIG: ResolvedWatchdogConfig = {\n\tenabled: false,\n\tdelivery: \"held\",\n\tshowDuringRun: false,\n\tsyncBacklog: \"off\",\n\tagentEndTimeoutMs: 30_000,\n\tlateWarningPolicy: \"show-stale-no-autofollow\",\n\tseverityThreshold: \"concern\",\n\tmaxWarnings: null,\n\tguidance: {\n\t\twatchdogMd: true,\n\t\tsystemPromptPath: null,\n\t},\n\tautoFollow: {\n\t\tblockers: true,\n\t\tmaxAttempts: 3,\n\t\tstalemateRepeats: 3,\n\t},\n\tscope: {\n\t\tenabled: true,\n\t},\n\tcadence: {\n\t\teveryNTools: null,\n\t},\n\tmain: {\n\t\tenabled: false,\n\t},\n\tchildren: {\n\t\tenabled: false,\n\t\twatchdogTailTimeoutMs: 120_000,\n\t\tautoFollow: {\n\t\t\tblockers: true,\n\t\t\tmaxAttempts: 3,\n\t\t\tstalemateRepeats: 3,\n\t\t},\n\t\toverrides: {},\n\t},\n\tasyncCompletion: {\n\t\tenabled: false,\n\t\tautoFollowBlockers: false,\n\t},\n\tlsp: {\n\t\tenabled: true,\n\t\ttimeoutMs: 3_000,\n\t\tmaxFiles: 20,\n\t\tmaxDiagnostics: 50,\n\t},\n\tcompactAtPercent: 80,\n\treviewRetryDelayMs: 1_000,\n\tmaxReviewFailures: 3,\n};\n\nconst WATCHDOG_FIELDS = new Set([\n\t\"enabled\",\n\t\"delivery\",\n\t\"showDuringRun\",\n\t\"syncBacklog\",\n\t\"agentEndTimeoutMs\",\n\t\"lateWarningPolicy\",\n\t\"severityThreshold\",\n\t\"maxWarnings\",\n\t\"guidance\",\n\t\"autoFollow\",\n\t\"scope\",\n\t\"cadence\",\n\t\"main\",\n\t\"children\",\n\t\"asyncCompletion\",\n\t\"lsp\",\n\t\"compactAtPercent\",\n\t\"reviewRetryDelayMs\",\n\t\"maxReviewFailures\",\n]);\nconst GUIDANCE_FIELDS = new Set([\"watchdogMd\", \"systemPromptPath\"]);\nconst AUTO_FOLLOW_FIELDS = new Set([\"blockers\", \"maxAttempts\", \"stalemateRepeats\"]);\nconst SCOPE_FIELDS = new Set([\"enabled\"]);\nconst CADENCE_FIELDS = new Set([\"everyNTools\"]);\nconst ENDPOINT_FIELDS = new Set([\"enabled\", \"model\", \"thinking\"]);\nconst CHILDREN_FIELDS = new Set([\"enabled\", \"model\", \"thinking\", \"watchdogTailTimeoutMs\", \"autoFollow\", \"overrides\"]);\nconst CHILD_OVERRIDE_FIELDS = new Set([\"enabled\", \"model\", \"thinking\"]);\nconst ASYNC_COMPLETION_FIELDS = new Set([\"enabled\", \"autoFollowBlockers\"]);\nconst LSP_FIELDS = new Set([\"enabled\", \"timeoutMs\", \"maxFiles\", \"maxDiagnostics\"]);\n\nfunction cloneDefaultConfig(): ResolvedWatchdogConfig {\n\treturn {\n\t\t...DEFAULT_WATCHDOG_CONFIG,\n\t\tguidance: { ...DEFAULT_WATCHDOG_CONFIG.guidance },\n\t\tautoFollow: { ...DEFAULT_WATCHDOG_CONFIG.autoFollow },\n\t\tscope: { ...DEFAULT_WATCHDOG_CONFIG.scope },\n\t\tcadence: { ...DEFAULT_WATCHDOG_CONFIG.cadence },\n\t\tmain: { ...DEFAULT_WATCHDOG_CONFIG.main },\n\t\tchildren: {\n\t\t\t...DEFAULT_WATCHDOG_CONFIG.children,\n\t\t\tautoFollow: { ...DEFAULT_WATCHDOG_CONFIG.children.autoFollow },\n\t\t\toverrides: {},\n\t\t},\n\t\tasyncCompletion: { ...DEFAULT_WATCHDOG_CONFIG.asyncCompletion },\n\t\tlsp: { ...DEFAULT_WATCHDOG_CONFIG.lsp },\n\t};\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sourceName(meta: ParseMeta): string {\n\treturn meta.path ? `'${meta.path}'` : \"session override\";\n}\n\nfunction invalid(meta: ParseMeta, field: string, expected: string): Error {\n\treturn new Error(`Watchdog settings in ${sourceName(meta)} have invalid '${field}'; expected ${expected}.`);\n}\n\nfunction unknown(meta: ParseMeta, field: string): Error {\n\treturn new Error(`Watchdog settings in ${sourceName(meta)} have unknown field '${field}'.`);\n}\n\nfunction assertKnownFields(\n\tinput: Record<string, unknown>,\n\tallowed: Set<string>,\n\tfieldPrefix: string,\n\tmeta: ParseMeta,\n): void {\n\tfor (const key of Object.keys(input)) {\n\t\tif (!allowed.has(key)) throw unknown(meta, `${fieldPrefix}.${key}`);\n\t}\n}\n\nfunction parseObject(value: unknown, field: string, meta: ParseMeta): Record<string, unknown> {\n\tif (isPlainObject(value)) return value;\n\tthrow invalid(meta, field, \"an object\");\n}\n\nfunction parseBoolean(value: unknown, field: string, meta: ParseMeta): boolean {\n\tif (typeof value === \"boolean\") return value;\n\tthrow invalid(meta, field, \"a boolean\");\n}\n\nfunction parseNonEmptyString(value: unknown, field: string, meta: ParseMeta): string {\n\tif (typeof value === \"string\" && value.trim()) return value.trim();\n\tthrow invalid(meta, field, \"a non-empty string\");\n}\n\nfunction parseThinking(value: unknown, field: string, meta: ParseMeta): ThinkingLevel | false {\n\tif (value === false) return false;\n\tif (typeof value === \"string\" && (THINKING_LEVELS as readonly string[]).includes(value))\n\t\treturn value as ThinkingLevel;\n\tthrow invalid(meta, field, `${THINKING_LEVELS.map((level) => `'${level}'`).join(\" or \")} or false`);\n}\n\nfunction parseInteger(\n\tvalue: unknown,\n\tfield: string,\n\tmeta: ParseMeta,\n\texpected: string,\n\tcheck: (value: number) => boolean,\n): number {\n\tif (typeof value === \"number\" && Number.isInteger(value) && check(value)) return value;\n\tthrow invalid(meta, field, expected);\n}\n\nfunction parseNullableInteger(\n\tvalue: unknown,\n\tfield: string,\n\tmeta: ParseMeta,\n\texpected: string,\n\tcheck: (value: number) => boolean,\n): number | null {\n\tif (value === null) return null;\n\treturn parseInteger(value, field, meta, expected, check);\n}\n\nfunction parseEnum<T extends string>(value: unknown, field: string, meta: ParseMeta, values: readonly T[]): T {\n\tif (typeof value === \"string\" && (values as readonly string[]).includes(value)) return value as T;\n\tthrow invalid(meta, field, values.map((item) => `'${item}'`).join(\" or \"));\n}\n\nfunction parseSyncBacklog(value: unknown, field: string, meta: ParseMeta): WatchdogSyncBacklog {\n\tif (value === \"off\") return \"off\";\n\treturn parseInteger(value, field, meta, \"'off' or a positive integer\", (candidate) => candidate >= 1);\n}\n\nfunction parseGuidancePatch(value: unknown, field: string, meta: ParseMeta): WatchdogGuidancePatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, GUIDANCE_FIELDS, field, meta);\n\tconst patch: WatchdogGuidancePatch = {};\n\tif (\"watchdogMd\" in input) patch.watchdogMd = parseBoolean(input.watchdogMd, `${field}.watchdogMd`, meta);\n\tif (\"systemPromptPath\" in input) {\n\t\tpatch.systemPromptPath =\n\t\t\tinput.systemPromptPath === null\n\t\t\t\t? null\n\t\t\t\t: parseNonEmptyString(input.systemPromptPath, `${field}.systemPromptPath`, meta);\n\t}\n\treturn patch;\n}\n\nfunction parseAutoFollowPatch(value: unknown, field: string, meta: ParseMeta): WatchdogAutoFollowPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, AUTO_FOLLOW_FIELDS, field, meta);\n\tconst patch: WatchdogAutoFollowPatch = {};\n\tif (\"blockers\" in input) patch.blockers = parseBoolean(input.blockers, `${field}.blockers`, meta);\n\tif (\"maxAttempts\" in input) {\n\t\tpatch.maxAttempts = parseNullableInteger(\n\t\t\tinput.maxAttempts,\n\t\t\t`${field}.maxAttempts`,\n\t\t\tmeta,\n\t\t\t\"null or a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\tif (\"stalemateRepeats\" in input) {\n\t\tpatch.stalemateRepeats = parseInteger(\n\t\t\tinput.stalemateRepeats,\n\t\t\t`${field}.stalemateRepeats`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\treturn patch;\n}\n\nfunction parseScopePatch(value: unknown, field: string, meta: ParseMeta): WatchdogScopePatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, SCOPE_FIELDS, field, meta);\n\tconst patch: WatchdogScopePatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\treturn patch;\n}\n\nfunction parseCadencePatch(value: unknown, field: string, meta: ParseMeta): WatchdogCadencePatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, CADENCE_FIELDS, field, meta);\n\tconst patch: WatchdogCadencePatch = {};\n\tif (\"everyNTools\" in input) {\n\t\tpatch.everyNTools =\n\t\t\tinput.everyNTools === null\n\t\t\t\t? null\n\t\t\t\t: parseInteger(\n\t\t\t\t\t\tinput.everyNTools,\n\t\t\t\t\t\t`${field}.everyNTools`,\n\t\t\t\t\t\tmeta,\n\t\t\t\t\t\t\"null or an integer >= 5\",\n\t\t\t\t\t\t(candidate) => candidate >= 5,\n\t\t\t\t\t);\n\t}\n\treturn patch;\n}\n\nfunction parseEndpointPatch(value: unknown, field: string, meta: ParseMeta): WatchdogEndpointPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, ENDPOINT_FIELDS, field, meta);\n\tconst patch: WatchdogEndpointPatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"model\" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);\n\tif (\"thinking\" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);\n\treturn patch;\n}\n\nfunction parseChildOverridePatch(value: unknown, field: string, meta: ParseMeta): WatchdogChildOverridePatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, CHILD_OVERRIDE_FIELDS, field, meta);\n\tconst patch: WatchdogChildOverridePatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"model\" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);\n\tif (\"thinking\" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);\n\treturn patch;\n}\n\nfunction parseChildrenPatch(value: unknown, field: string, meta: ParseMeta): WatchdogChildrenPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, CHILDREN_FIELDS, field, meta);\n\tconst patch: WatchdogChildrenPatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"model\" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);\n\tif (\"thinking\" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);\n\tif (\"watchdogTailTimeoutMs\" in input) {\n\t\tpatch.watchdogTailTimeoutMs = parseInteger(\n\t\t\tinput.watchdogTailTimeoutMs,\n\t\t\t`${field}.watchdogTailTimeoutMs`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\tif (\"autoFollow\" in input) patch.autoFollow = parseAutoFollowPatch(input.autoFollow, `${field}.autoFollow`, meta);\n\tif (\"overrides\" in input) {\n\t\tconst overrides = parseObject(input.overrides, `${field}.overrides`, meta);\n\t\tpatch.overrides = {};\n\t\tfor (const [agent, override] of Object.entries(overrides)) {\n\t\t\tif (!agent.trim()) throw invalid(meta, `${field}.overrides`, \"agent names to be non-empty\");\n\t\t\tpatch.overrides[agent] = parseChildOverridePatch(override, `${field}.overrides.${agent}`, meta);\n\t\t}\n\t}\n\treturn patch;\n}\n\nfunction parseAsyncCompletionPatch(value: unknown, field: string, meta: ParseMeta): WatchdogAsyncCompletionPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, ASYNC_COMPLETION_FIELDS, field, meta);\n\tconst patch: WatchdogAsyncCompletionPatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"autoFollowBlockers\" in input)\n\t\tpatch.autoFollowBlockers = parseBoolean(input.autoFollowBlockers, `${field}.autoFollowBlockers`, meta);\n\treturn patch;\n}\n\nfunction parseLspPatch(value: unknown, field: string, meta: ParseMeta): WatchdogLspPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, LSP_FIELDS, field, meta);\n\tconst patch: WatchdogLspPatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"timeoutMs\" in input)\n\t\tpatch.timeoutMs = parseInteger(\n\t\t\tinput.timeoutMs,\n\t\t\t`${field}.timeoutMs`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\tif (\"maxFiles\" in input)\n\t\tpatch.maxFiles = parseInteger(\n\t\t\tinput.maxFiles,\n\t\t\t`${field}.maxFiles`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\tif (\"maxDiagnostics\" in input)\n\t\tpatch.maxDiagnostics = parseInteger(\n\t\t\tinput.maxDiagnostics,\n\t\t\t`${field}.maxDiagnostics`,\n\t\t\tmeta,\n\t\t\t\"a non-negative integer\",\n\t\t\t(candidate) => candidate >= 0,\n\t\t);\n\treturn patch;\n}\n\nfunction parseWatchdogPatch(value: unknown, field: string, meta: ParseMeta): WatchdogConfigPatch {\n\tconst input = parseObject(value, field, meta);\n\tassertKnownFields(input, WATCHDOG_FIELDS, field, meta);\n\tconst patch: WatchdogConfigPatch = {};\n\tif (\"enabled\" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);\n\tif (\"delivery\" in input)\n\t\tpatch.delivery = parseEnum<WatchdogDeliveryMode>(\n\t\t\tinput.delivery,\n\t\t\t`${field}.delivery`,\n\t\t\tmeta,\n\t\t\tWATCHDOG_DELIVERY_MODES,\n\t\t);\n\tif (\"showDuringRun\" in input)\n\t\tpatch.showDuringRun = parseBoolean(input.showDuringRun, `${field}.showDuringRun`, meta);\n\tif (\"syncBacklog\" in input) patch.syncBacklog = parseSyncBacklog(input.syncBacklog, `${field}.syncBacklog`, meta);\n\tif (\"agentEndTimeoutMs\" in input) {\n\t\tpatch.agentEndTimeoutMs = parseInteger(\n\t\t\tinput.agentEndTimeoutMs,\n\t\t\t`${field}.agentEndTimeoutMs`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\tif (\"lateWarningPolicy\" in input) {\n\t\tpatch.lateWarningPolicy = parseEnum<WatchdogLateWarningPolicy>(\n\t\t\tinput.lateWarningPolicy,\n\t\t\t`${field}.lateWarningPolicy`,\n\t\t\tmeta,\n\t\t\tWATCHDOG_LATE_WARNING_POLICIES,\n\t\t);\n\t}\n\tif (\"severityThreshold\" in input) {\n\t\tpatch.severityThreshold = parseEnum<WatchdogSeverity>(\n\t\t\tinput.severityThreshold,\n\t\t\t`${field}.severityThreshold`,\n\t\t\tmeta,\n\t\t\tWATCHDOG_WARNING_SEVERITIES,\n\t\t);\n\t}\n\tif (\"maxWarnings\" in input) {\n\t\tpatch.maxWarnings = parseNullableInteger(\n\t\t\tinput.maxWarnings,\n\t\t\t`${field}.maxWarnings`,\n\t\t\tmeta,\n\t\t\t\"null or a non-negative integer\",\n\t\t\t(candidate) => candidate >= 0,\n\t\t);\n\t}\n\tif (\"guidance\" in input) patch.guidance = parseGuidancePatch(input.guidance, `${field}.guidance`, meta);\n\tif (\"autoFollow\" in input) patch.autoFollow = parseAutoFollowPatch(input.autoFollow, `${field}.autoFollow`, meta);\n\tif (\"scope\" in input) patch.scope = parseScopePatch(input.scope, `${field}.scope`, meta);\n\tif (\"cadence\" in input) patch.cadence = parseCadencePatch(input.cadence, `${field}.cadence`, meta);\n\tif (\"main\" in input) patch.main = parseEndpointPatch(input.main, `${field}.main`, meta);\n\tif (\"children\" in input) patch.children = parseChildrenPatch(input.children, `${field}.children`, meta);\n\tif (\"asyncCompletion\" in input)\n\t\tpatch.asyncCompletion = parseAsyncCompletionPatch(input.asyncCompletion, `${field}.asyncCompletion`, meta);\n\tif (\"lsp\" in input) patch.lsp = parseLspPatch(input.lsp, `${field}.lsp`, meta);\n\tif (\"compactAtPercent\" in input) {\n\t\tpatch.compactAtPercent = parseInteger(\n\t\t\tinput.compactAtPercent,\n\t\t\t`${field}.compactAtPercent`,\n\t\t\tmeta,\n\t\t\t\"an integer from 50 through 95\",\n\t\t\t(candidate) => candidate >= 50 && candidate <= 95,\n\t\t);\n\t}\n\tif (\"reviewRetryDelayMs\" in input) {\n\t\tpatch.reviewRetryDelayMs = parseInteger(\n\t\t\tinput.reviewRetryDelayMs,\n\t\t\t`${field}.reviewRetryDelayMs`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\tif (\"maxReviewFailures\" in input) {\n\t\tpatch.maxReviewFailures = parseInteger(\n\t\t\tinput.maxReviewFailures,\n\t\t\t`${field}.maxReviewFailures`,\n\t\t\tmeta,\n\t\t\t\"a positive integer\",\n\t\t\t(candidate) => candidate >= 1,\n\t\t);\n\t}\n\treturn patch;\n}\n\nfunction parseSettingsObject(settings: Record<string, unknown>, meta: ParseMeta): WatchdogConfigPatch {\n\tif (!(\"subagents\" in settings)) return {};\n\tconst subagents = parseObject(settings.subagents, \"subagents\", meta);\n\tif (!(\"watchdog\" in subagents)) return {};\n\treturn parseWatchdogPatch(subagents.watchdog, \"subagents.watchdog\", meta);\n}\n\nfunction readSettingsFileStrict(filePath: string): Record<string, unknown> {\n\tif (!fs.existsSync(filePath)) return {};\n\tlet raw: string;\n\ttry {\n\t\traw = fs.readFileSync(filePath, \"utf-8\");\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });\n\t}\n\tif (!isPlainObject(parsed)) {\n\t\tthrow new Error(`Settings file '${filePath}' must contain a JSON object.`);\n\t}\n\treturn parsed;\n}\n\nfunction isDirectory(dir: string): boolean {\n\ttry {\n\t\treturn fs.statSync(dir).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction getUserSettingsPath(): string {\n\treturn path.join(getAgentDir(), \"settings.json\");\n}\n\nexport function getWatchdogUserSettingsPath(): string {\n\treturn getUserSettingsPath();\n}\n\nfunction getProjectSettingsPath(cwd: string): string | undefined {\n\tlet currentDir = cwd;\n\twhile (true) {\n\t\tif (isDirectory(getProjectConfigDir(currentDir)) || isDirectory(path.join(currentDir, \".agents\"))) {\n\t\t\treturn path.join(getProjectConfigDir(currentDir), \"settings.json\");\n\t\t}\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) return undefined;\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nexport function getWatchdogProjectSettingsPath(cwd: string): string {\n\treturn path.join(getProjectConfigDir(cwd), \"settings.json\");\n}\n\nfunction deepMerge<T extends Record<string, unknown>>(base: T, patch: Record<string, unknown>): T {\n\tconst next: Record<string, unknown> = { ...base };\n\tfor (const [key, value] of Object.entries(patch)) {\n\t\tconst current = next[key];\n\t\tnext[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;\n\t}\n\treturn next as T;\n}\n\nfunction resolvePatch(patch: WatchdogConfigPatch): ResolvedWatchdogConfig {\n\tconst config = deepMerge(\n\t\tcloneDefaultConfig() as unknown as Record<string, unknown>,\n\t\tpatch as Record<string, unknown>,\n\t) as unknown as ResolvedWatchdogConfig;\n\tconfig.enabled = patch.enabled ?? DEFAULT_WATCHDOG_CONFIG.enabled;\n\tconfig.main.enabled = patch.main?.enabled ?? config.enabled;\n\treturn config;\n}\n\nfunction parseSourceFile(filePath: string, scope: \"user\" | \"project\"): WatchdogConfigPatch {\n\treturn parseSettingsObject(readSettingsFileStrict(filePath), { scope, path: filePath });\n}\n\nfunction parseSessionOverride(value: Record<string, unknown>): WatchdogConfigPatch {\n\tif (\"subagents\" in value) return parseSettingsObject(value, { scope: \"session\" });\n\treturn parseWatchdogPatch(value, \"subagents.watchdog\", { scope: \"session\" });\n}\n\nexport function resolveWatchdogConfigStrict(\n\tcwd: string,\n\toptions: { session?: Record<string, unknown> } = {},\n): ResolvedWatchdogConfig {\n\tlet patch: WatchdogConfigPatch = {};\n\tpatch = deepMerge(\n\t\tpatch as Record<string, unknown>,\n\t\tparseSourceFile(getUserSettingsPath(), \"user\") as Record<string, unknown>,\n\t) as WatchdogConfigPatch;\n\tconst projectSettingsPath = getProjectSettingsPath(cwd);\n\tif (projectSettingsPath) {\n\t\tpatch = deepMerge(\n\t\t\tpatch as Record<string, unknown>,\n\t\t\tparseSourceFile(projectSettingsPath, \"project\") as Record<string, unknown>,\n\t\t) as WatchdogConfigPatch;\n\t}\n\tif (options.session) {\n\t\tpatch = deepMerge(\n\t\t\tpatch as Record<string, unknown>,\n\t\t\tparseSessionOverride(options.session) as Record<string, unknown>,\n\t\t) as WatchdogConfigPatch;\n\t}\n\treturn resolvePatch(patch);\n}\n\nfunction ensureObjectField(\n\tparent: Record<string, unknown>,\n\tkey: string,\n\tfield: string,\n\tmeta: ParseMeta,\n): Record<string, unknown> {\n\tif (!(key in parent)) parent[key] = {};\n\tif (!isPlainObject(parent[key])) throw invalid(meta, field, \"an object\");\n\treturn parent[key];\n}\n\nfunction ensureWatchdogSettings(settings: Record<string, unknown>, meta: ParseMeta): Record<string, unknown> {\n\tconst subagents = ensureObjectField(settings, \"subagents\", \"subagents\", meta);\n\treturn ensureObjectField(subagents, \"watchdog\", \"subagents.watchdog\", meta);\n}\n\nfunction settingsPathForWrite(scope: WatchdogSettingsWriteScope, cwd: string | undefined): string {\n\treturn scope === \"user\" ? getUserSettingsPath() : getWatchdogProjectSettingsPath(cwd ?? process.cwd());\n}\n\nfunction targetSettingsObject(\n\twatchdog: Record<string, unknown>,\n\ttarget: WatchdogModelSettingsTarget,\n\tmeta: ParseMeta,\n): Record<string, unknown> {\n\tif (target.kind === \"main\") return ensureObjectField(watchdog, \"main\", \"subagents.watchdog.main\", meta);\n\tconst children = ensureObjectField(watchdog, \"children\", \"subagents.watchdog.children\", meta);\n\tif (target.kind === \"children\") return children;\n\tif (!target.agent.trim())\n\t\tthrow invalid(meta, \"subagents.watchdog.children.overrides.<agent>\", \"a non-empty agent name\");\n\tconst overrides = ensureObjectField(children, \"overrides\", \"subagents.watchdog.children.overrides\", meta);\n\treturn ensureObjectField(\n\t\toverrides,\n\t\ttarget.agent.trim(),\n\t\t`subagents.watchdog.children.overrides.${target.agent.trim()}`,\n\t\tmeta,\n\t);\n}\n\nfunction writeSettingsFile(settingsPath: string, settings: Record<string, unknown>): string {\n\tfs.mkdirSync(path.dirname(settingsPath), { recursive: true });\n\tfs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`, \"utf-8\");\n\treturn settingsPath;\n}\n\nexport function writeUserWatchdogEnabled(enabled: boolean): string {\n\tconst settingsPath = getUserSettingsPath();\n\tconst meta: ParseMeta = { scope: \"user\", path: settingsPath };\n\tconst settings = readSettingsFileStrict(settingsPath);\n\tconst watchdog = ensureWatchdogSettings(settings, meta);\n\twatchdog.enabled = enabled;\n\ttargetSettingsObject(watchdog, { kind: \"main\" }, meta).enabled = enabled;\n\treturn writeSettingsFile(settingsPath, settings);\n}\n\nexport function writeWatchdogModelSettings(input: WatchdogModelSettingsWrite): string {\n\tconst settingsPath = settingsPathForWrite(input.scope, input.cwd);\n\tconst meta: ParseMeta = { scope: input.scope, path: settingsPath };\n\tconst settings = readSettingsFileStrict(settingsPath);\n\tconst watchdog = ensureWatchdogSettings(settings, meta);\n\tconst target = targetSettingsObject(watchdog, input.target, meta);\n\tif (input.model === null) delete target.model;\n\telse if (input.model !== undefined) target.model = input.model;\n\tif (input.thinking === null) delete target.thinking;\n\telse if (input.thinking !== undefined) target.thinking = input.thinking;\n\treturn writeSettingsFile(settingsPath, settings);\n}\n\nexport function resolveWatchdogConfig(\n\tcwd: string,\n\toptions: { session?: Record<string, unknown> } = {},\n): WatchdogSettingsResult {\n\tconst sources: WatchdogSettingsSource[] = [];\n\tconst errors: WatchdogSettingsError[] = [];\n\tlet patch: WatchdogConfigPatch = {};\n\tconst sourceSpecs: Array<{ scope: \"user\" | \"project\"; path: string | undefined }> = [\n\t\t{ scope: \"user\", path: getUserSettingsPath() },\n\t\t{ scope: \"project\", path: getProjectSettingsPath(cwd) },\n\t];\n\tfor (const source of sourceSpecs) {\n\t\tif (!source.path) continue;\n\t\tsources.push({ scope: source.scope, path: source.path, exists: fs.existsSync(source.path) });\n\t\ttry {\n\t\t\tpatch = deepMerge(\n\t\t\t\tpatch as Record<string, unknown>,\n\t\t\t\tparseSourceFile(source.path, source.scope) as Record<string, unknown>,\n\t\t\t) as WatchdogConfigPatch;\n\t\t} catch (error) {\n\t\t\terrors.push({\n\t\t\t\tscope: source.scope,\n\t\t\t\tpath: source.path,\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t}\n\tif (options.session) {\n\t\tsources.push({ scope: \"session\", exists: true });\n\t\ttry {\n\t\t\tpatch = deepMerge(\n\t\t\t\tpatch as Record<string, unknown>,\n\t\t\t\tparseSessionOverride(options.session) as Record<string, unknown>,\n\t\t\t) as WatchdogConfigPatch;\n\t\t} catch (error) {\n\t\t\terrors.push({ scope: \"session\", message: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn {\n\t\tok: errors.length === 0,\n\t\tconfig: errors.length === 0 ? resolvePatch(patch) : cloneDefaultConfig(),\n\t\terrors,\n\t\tsources,\n\t};\n}\n"]}