Files
oh-my-pi/packages/coding-agent/src/config/settings-schema.ts
T
can1357 35dbf43670 feat(coding-agent/web): added Brave web search provider with recency filtering and API integration
- Added Brave web search provider with support for query, result count, and recency filtering.
- Added BRAVE_API_KEY environment variable configuration for Brave search authentication.
- Updated web search provider priority order to include Brave as second-highest priority after Exa.
- Extended recency filter support to Brave search provider alongside Perplexity.
- Implemented BraveProvider class with API integration, response mapping, and parameter validation.
- Added helper functions for recency mapping, result clamping, and snippet building in Brave provider.
2026-02-16 18:33:02 +01:00

1151 lines
42 KiB
TypeScript

/**
* Unified settings schema - single source of truth for all settings.
*
* Each setting is defined once here with:
* - Type and default value
* - Optional UI metadata (label, description, tab)
*
* The Settings singleton provides type-safe path-based access:
* settings.get("compaction.enabled") // => boolean
* settings.set("theme.dark", "titanium") // sync, saves in background
*/
// ═══════════════════════════════════════════════════════════════════════════
// Schema Definition Types
// ═══════════════════════════════════════════════════════════════════════════
export type SettingTab =
| "display"
| "agent"
| "input"
| "tools"
| "config"
| "services"
| "bash"
| "lsp"
| "ttsr"
| "status";
/** Tab display metadata - icon is resolved via theme.symbol() */
export type TabMetadata = { label: string; icon: `tab.${string}` };
/** Ordered list of tabs for UI rendering (status excluded - custom menu) */
export const SETTING_TABS: SettingTab[] = [
"display",
"agent",
"input",
"tools",
"config",
"services",
"bash",
"lsp",
"ttsr",
];
/** Tab display metadata - icon is a symbol key from theme.ts (tab.*) */
export const TAB_METADATA: Record<SettingTab, { label: string; icon: `tab.${string}` }> = {
display: { label: "Display", icon: "tab.display" },
agent: { label: "Agent", icon: "tab.agent" },
input: { label: "Input", icon: "tab.input" },
tools: { label: "Tools", icon: "tab.tools" },
config: { label: "Config", icon: "tab.config" },
services: { label: "Services", icon: "tab.services" },
bash: { label: "Bash", icon: "tab.bash" },
lsp: { label: "LSP", icon: "tab.lsp" },
ttsr: { label: "TTSR", icon: "tab.ttsr" },
status: { label: "Status", icon: "tab.status" },
};
/** Status line segment identifiers */
export type StatusLineSegmentId =
| "pi"
| "model"
| "plan_mode"
| "path"
| "git"
| "subagents"
| "token_in"
| "token_out"
| "token_total"
| "cost"
| "context_pct"
| "context_total"
| "time_spent"
| "time"
| "session"
| "hostname"
| "cache_read"
| "cache_write";
interface UiMetadata {
tab: SettingTab;
label: string;
description: string;
/** For enum/submenu - display as inline toggle vs dropdown */
submenu?: boolean;
/** Condition function name - setting only shown when true */
condition?: string;
}
interface BooleanDef {
type: "boolean";
default: boolean;
ui?: UiMetadata;
}
interface StringDef {
type: "string";
default: string | undefined;
ui?: UiMetadata;
}
interface NumberDef {
type: "number";
default: number;
ui?: UiMetadata;
}
interface EnumDef<T extends readonly string[]> {
type: "enum";
values: T;
default: T[number];
ui?: UiMetadata;
}
interface ArrayDef<T> {
type: "array";
default: T[];
ui?: UiMetadata;
}
interface RecordDef<T> {
type: "record";
default: Record<string, T>;
ui?: UiMetadata;
}
type SettingDef =
| BooleanDef
| StringDef
| NumberDef
| EnumDef<readonly string[]>
| ArrayDef<unknown>
| RecordDef<unknown>;
// ═══════════════════════════════════════════════════════════════════════════
// Schema Definition
// ═══════════════════════════════════════════════════════════════════════════
export const SETTINGS_SCHEMA = {
// ─────────────────────────────────────────────────────────────────────────
// Top-level settings
// ─────────────────────────────────────────────────────────────────────────
lastChangelogVersion: { type: "string", default: undefined },
"theme.dark": {
type: "string",
default: "titanium",
ui: {
tab: "display",
label: "Dark theme",
description: "Theme used when terminal has dark background",
submenu: true,
},
},
"theme.light": {
type: "string",
default: "light",
ui: {
tab: "display",
label: "Light theme",
description: "Theme used when terminal has light background",
submenu: true,
},
},
symbolPreset: {
type: "enum",
values: ["unicode", "nerd", "ascii"] as const,
default: "unicode",
ui: { tab: "display", label: "Symbol preset", description: "Icon/symbol style", submenu: true },
},
colorBlindMode: {
type: "boolean",
default: false,
ui: {
tab: "display",
label: "Color blind mode",
description: "Use blue instead of green for diff additions",
},
},
defaultThinkingLevel: {
type: "enum",
values: ["off", "minimal", "low", "medium", "high", "xhigh"] as const,
default: "off",
ui: {
tab: "agent",
label: "Thinking level",
description: "Reasoning depth for thinking-capable models",
submenu: true,
},
},
temperature: {
type: "number",
default: -1,
ui: {
tab: "agent",
label: "Temperature",
description: "Sampling temperature (0 = deterministic, 1 = creative, -1 = provider default)",
submenu: true,
},
},
hideThinkingBlock: {
type: "boolean",
default: false,
ui: { tab: "agent", label: "Hide thinking", description: "Hide thinking blocks in assistant responses" },
},
steeringMode: {
type: "enum",
values: ["all", "one-at-a-time"] as const,
default: "one-at-a-time",
ui: {
tab: "agent",
label: "Steering mode",
description: "How to process queued messages while agent is working",
},
},
followUpMode: {
type: "enum",
values: ["all", "one-at-a-time"] as const,
default: "one-at-a-time",
ui: {
tab: "agent",
label: "Follow-up mode",
description: "How to drain follow-up messages after a turn completes",
},
},
interruptMode: {
type: "enum",
values: ["immediate", "wait"] as const,
default: "immediate",
ui: { tab: "agent", label: "Interrupt mode", description: "When steering messages interrupt tool execution" },
},
doubleEscapeAction: {
type: "enum",
values: ["branch", "tree", "none"] as const,
default: "tree",
ui: {
tab: "input",
label: "Double-escape action",
description: "Action when pressing Escape twice with empty editor",
},
},
shellPath: { type: "string", default: undefined },
collapseChangelog: {
type: "boolean",
default: false,
ui: { tab: "input", label: "Collapse changelog", description: "Show condensed changelog after updates" },
},
normativeRewrite: {
type: "boolean",
default: false,
ui: {
tab: "agent",
label: "Normative rewrite",
description: "Rewrite tool call arguments to normalized format in session history",
},
},
repeatToolDescriptions: {
type: "boolean",
default: false,
ui: {
tab: "agent",
label: "Repeat tool descriptions",
description: "Render full tool descriptions in the system prompt instead of a tool name list",
},
},
readLineNumbers: {
type: "boolean",
default: false,
ui: {
tab: "config",
label: "Read line numbers",
description: "Prepend line numbers to read tool output by default",
},
},
readHashLines: {
type: "boolean",
default: true,
ui: {
tab: "config",
label: "Read hash lines",
description: "Include line hashes in read output for hashline edit mode (LINE:HASH|content)",
},
},
showHardwareCursor: {
type: "boolean",
default: true, // will be computed based on platform if undefined
ui: { tab: "display", label: "Hardware cursor", description: "Show terminal cursor for IME support" },
},
clearOnShrink: {
type: "boolean",
default: false,
ui: {
tab: "display",
label: "Clear on shrink",
description: "Clear empty rows when content shrinks (may cause flicker)",
},
},
extensions: { type: "array", default: [] as string[] },
enabledModels: { type: "array", default: [] as string[] },
disabledProviders: { type: "array", default: [] as string[] },
disabledExtensions: { type: "array", default: [] as string[] },
modelRoles: { type: "record", default: {} as Record<string, string> },
// ─────────────────────────────────────────────────────────────────────────
// Compaction settings
// ─────────────────────────────────────────────────────────────────────────
"compaction.enabled": {
type: "boolean",
default: true,
ui: {
tab: "agent",
label: "Auto-compact",
description: "Automatically compact context when it gets too large",
},
},
"compaction.reserveTokens": { type: "number", default: 16384 },
"compaction.keepRecentTokens": { type: "number", default: 20000 },
"compaction.autoContinue": { type: "boolean", default: true },
"compaction.remoteEndpoint": { type: "string", default: undefined },
// ─────────────────────────────────────────────────────────────────────────
// Branch summary settings
// ─────────────────────────────────────────────────────────────────────────
"branchSummary.enabled": {
type: "boolean",
default: false,
ui: { tab: "agent", label: "Branch summaries", description: "Prompt to summarize when leaving a branch" },
},
"branchSummary.reserveTokens": { type: "number", default: 16384 },
// ─────────────────────────────────────────────────────────────────────────
// Memories settings
// ─────────────────────────────────────────────────────────────────────────
"memories.enabled": {
type: "boolean",
default: false,
ui: {
tab: "agent",
label: "Memories",
description: "Enable autonomous memory extraction and consolidation",
},
},
"memories.maxRolloutsPerStartup": { type: "number", default: 64 },
"memories.maxRolloutAgeDays": { type: "number", default: 30 },
"memories.minRolloutIdleHours": { type: "number", default: 12 },
"memories.threadScanLimit": { type: "number", default: 300 },
"memories.maxRawMemoriesForGlobal": { type: "number", default: 200 },
"memories.stage1Concurrency": { type: "number", default: 8 },
"memories.stage1LeaseSeconds": { type: "number", default: 120 },
"memories.stage1RetryDelaySeconds": { type: "number", default: 120 },
"memories.phase2LeaseSeconds": { type: "number", default: 180 },
"memories.phase2RetryDelaySeconds": { type: "number", default: 180 },
"memories.phase2HeartbeatSeconds": { type: "number", default: 30 },
"memories.rolloutPayloadPercent": { type: "number", default: 0.7 },
"memories.fallbackTokenLimit": { type: "number", default: 16000 },
"memories.summaryInjectionTokenLimit": { type: "number", default: 5000 },
// ─────────────────────────────────────────────────────────────────────────
// Retry settings
// ─────────────────────────────────────────────────────────────────────────
"retry.enabled": { type: "boolean", default: true },
"retry.maxRetries": {
type: "number",
default: 3,
ui: {
tab: "agent",
label: "Retry max attempts",
description: "Maximum retry attempts on API errors",
submenu: true,
},
},
"retry.baseDelayMs": { type: "number", default: 2000 },
// ─────────────────────────────────────────────────────────────────────────
// Todo completion settings
// ─────────────────────────────────────────────────────────────────────────
"todo.reminders": {
type: "boolean",
default: false,
ui: { tab: "agent", label: "Todo reminders", description: "Remind agent to complete todos before stopping" },
},
"todo.reminders.max": {
type: "number",
default: 3,
ui: {
tab: "agent",
label: "Todo max reminders",
description: "Maximum reminders to complete todos before giving up",
submenu: true,
},
},
// ─────────────────────────────────────────────────────────────────────────
// Optional tools
// ─────────────────────────────────────────────────────────────────────────
"todo.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Todos", description: "Enable the todo_write tool for task tracking" },
},
"find.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Find", description: "Enable the find tool for file searching" },
},
"grep.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Grep", description: "Enable the grep tool for content searching" },
},
"grep.contextBefore": {
type: "number",
default: 1,
ui: {
tab: "tools",
label: "Grep context before",
description: "Lines of context before each grep match",
submenu: true,
},
},
"grep.contextAfter": {
type: "number",
default: 3,
ui: {
tab: "tools",
label: "Grep context after",
description: "Lines of context after each grep match",
submenu: true,
},
},
"notebook.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Notebook", description: "Enable the notebook tool for notebook editing" },
},
"fetch.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Fetch", description: "Enable the fetch tool for URL fetching" },
},
"web_search.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable Web Search", description: "Enable the web_search tool for web searching" },
},
"lsp.enabled": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Enable LSP", description: "Enable the lsp tool for language server protocol" },
},
"calc.enabled": {
type: "boolean",
default: false,
ui: {
tab: "tools",
label: "Enable Calculator",
description: "Enable the calculator tool for basic calculations",
},
},
"browser.enabled": {
type: "boolean",
default: true,
ui: {
tab: "tools",
label: "Enable Browser",
description: "Enable the browser tool (Ulixee Hero)",
},
},
"browser.headless": {
type: "boolean",
default: true,
ui: {
tab: "tools",
label: "Browser headless",
description: "Launch browser in headless mode (disable to show browser UI)",
},
},
// ─────────────────────────────────────────────────────────────────────────
// Task tool settings
// ─────────────────────────────────────────────────────────────────────────
"task.isolation.enabled": {
type: "boolean",
default: false,
ui: {
tab: "tools",
label: "Task isolation",
description: "Run subagents in isolated git worktrees",
submenu: true,
},
},
"task.maxConcurrency": {
type: "number",
default: 32,
ui: {
tab: "tools",
label: "Task max concurrency",
description: "Concurrent limit for subagents",
submenu: true,
},
},
"task.maxRecursionDepth": {
type: "number",
default: 2,
ui: {
tab: "tools",
label: "Task max recursion depth",
description: "How many levels deep subagents can spawn their own subagents",
submenu: true,
},
},
// ─────────────────────────────────────────────────────────────────────────
// Startup settings
// ─────────────────────────────────────────────────────────────────────────
"startup.quiet": {
type: "boolean",
default: false,
ui: { tab: "input", label: "Startup quiet", description: "Skip welcome screen and startup status messages" },
},
// ─────────────────────────────────────────────────────────────────────────
// Notification settings
// ─────────────────────────────────────────────────────────────────────────
"completion.notify": {
type: "enum",
values: ["on", "off"] as const,
default: "on",
ui: { tab: "input", label: "Completion notification", description: "Notify when the agent completes" },
},
// ─────────────────────────────────────────────────────────────────────────
// Ask settings
// ─────────────────────────────────────────────────────────────────────────
"ask.timeout": {
type: "number",
default: 30,
ui: {
tab: "input",
label: "Ask tool timeout",
description: "Auto-select recommended option after timeout (0 to disable)",
submenu: true,
},
},
"ask.notify": {
type: "enum",
values: ["on", "off"] as const,
default: "on",
ui: { tab: "input", label: "Ask notification", description: "Notify when ask tool is waiting for input" },
},
// ─────────────────────────────────────────────────────────────────────────
// Terminal settings
// ─────────────────────────────────────────────────────────────────────────
"terminal.showImages": {
type: "boolean",
default: true,
ui: {
tab: "display",
label: "Show images",
description: "Render images inline in terminal",
condition: "hasImageProtocol",
},
},
// ─────────────────────────────────────────────────────────────────────────
// Image settings
// ─────────────────────────────────────────────────────────────────────────
"images.autoResize": {
type: "boolean",
default: true,
ui: {
tab: "display",
label: "Auto-resize images",
description: "Resize large images to 2000x2000 max for better model compatibility",
},
},
"images.blockImages": {
type: "boolean",
default: false,
ui: { tab: "display", label: "Block images", description: "Prevent images from being sent to LLM providers" },
},
// ─────────────────────────────────────────────────────────────────────────
// Skills settings
// ─────────────────────────────────────────────────────────────────────────
"skills.enabled": { type: "boolean", default: true },
"skills.enableSkillCommands": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Skill commands", description: "Register skills as /skill:name commands" },
},
"skills.enableCodexUser": { type: "boolean", default: true },
"skills.enableClaudeUser": { type: "boolean", default: true },
"skills.enableClaudeProject": { type: "boolean", default: true },
"skills.enablePiUser": { type: "boolean", default: true },
"skills.enablePiProject": { type: "boolean", default: true },
"skills.customDirectories": { type: "array", default: [] as string[] },
"skills.ignoredSkills": { type: "array", default: [] as string[] },
"skills.includeSkills": { type: "array", default: [] as string[] },
// ─────────────────────────────────────────────────────────────────────────
// Commands settings
// ─────────────────────────────────────────────────────────────────────────
"commands.enableClaudeUser": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Claude user commands", description: "Load commands from ~/.claude/commands/" },
},
"commands.enableClaudeProject": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "Claude project commands", description: "Load commands from .claude/commands/" },
},
// ─────────────────────────────────────────────────────────────────────────
// Provider settings
// ─────────────────────────────────────────────────────────────────────────
"providers.webSearch": {
type: "enum",
values: ["auto", "exa", "brave", "jina", "zai", "perplexity", "anthropic"] as const,
default: "auto",
ui: { tab: "services", label: "Web search provider", description: "Provider for web search tool", submenu: true },
},
"providers.image": {
type: "enum",
values: ["auto", "gemini", "openrouter"] as const,
default: "auto",
ui: {
tab: "services",
label: "Image provider",
description: "Provider for image generation tool",
submenu: true,
},
},
"providers.kimiApiFormat": {
type: "enum",
values: ["openai", "anthropic"] as const,
default: "anthropic",
ui: {
tab: "services",
label: "Kimi API format",
description: "API format for Kimi Code provider",
submenu: true,
},
},
"providers.openaiWebsockets": {
type: "enum",
values: ["auto", "off", "on"] as const,
default: "auto",
ui: {
tab: "services",
label: "OpenAI websockets",
description: "Websocket policy for OpenAI Codex models (auto uses model defaults, on forces, off disables)",
submenu: true,
},
},
// ─────────────────────────────────────────────────────────────────────────
// Exa settings
// ─────────────────────────────────────────────────────────────────────────
"exa.enabled": {
type: "boolean",
default: true,
ui: { tab: "services", label: "Exa enabled", description: "Master toggle for all Exa search tools" },
},
"exa.enableSearch": {
type: "boolean",
default: true,
ui: { tab: "services", label: "Exa search", description: "Basic search, deep search, code search, crawl" },
},
"exa.enableLinkedin": {
type: "boolean",
default: false,
ui: { tab: "services", label: "Exa LinkedIn", description: "Search LinkedIn for people and companies" },
},
"exa.enableCompany": {
type: "boolean",
default: false,
ui: { tab: "services", label: "Exa company", description: "Comprehensive company research tool" },
},
"exa.enableResearcher": {
type: "boolean",
default: false,
ui: { tab: "services", label: "Exa researcher", description: "AI-powered deep research tasks" },
},
"exa.enableWebsets": {
type: "boolean",
default: false,
ui: { tab: "services", label: "Exa websets", description: "Webset management and enrichment tools" },
},
// ─────────────────────────────────────────────────────────────────────────
// Bash interceptor settings
// ─────────────────────────────────────────────────────────────────────────
"bash.virtualTerminal": {
type: "enum",
values: ["on", "off"] as const,
default: "off",
ui: {
tab: "bash",
label: "Virtual terminal",
description: "Use PTY-backed interactive execution for bash",
submenu: true,
},
},
"bashInterceptor.enabled": {
type: "boolean",
default: false,
ui: { tab: "bash", label: "Interceptor", description: "Block shell commands that have dedicated tools" },
},
"bashInterceptor.simpleLs": {
type: "boolean",
default: true,
ui: {
tab: "bash",
label: "Intercept ls",
description: "Intercept bare ls commands (when interceptor is enabled)",
},
},
// bashInterceptor.patterns is complex - handle separately
// ─────────────────────────────────────────────────────────────────────────
// MCP settings
// ─────────────────────────────────────────────────────────────────────────
"mcp.enableProjectConfig": {
type: "boolean",
default: true,
ui: { tab: "tools", label: "MCP project config", description: "Load .mcp.json/mcp.json from project root" },
},
// ─────────────────────────────────────────────────────────────────────────
// LSP settings
// ─────────────────────────────────────────────────────────────────────────
"lsp.formatOnWrite": {
type: "boolean",
default: false,
ui: {
tab: "lsp",
label: "Format on write",
description: "Automatically format code files using LSP after writing",
},
},
"lsp.diagnosticsOnWrite": {
type: "boolean",
default: true,
ui: { tab: "lsp", label: "Diagnostics on write", description: "Return LSP diagnostics after writing code files" },
},
"lsp.diagnosticsOnEdit": {
type: "boolean",
default: false,
ui: { tab: "lsp", label: "Diagnostics on edit", description: "Return LSP diagnostics after editing code files" },
},
// ─────────────────────────────────────────────────────────────────────────
// Python settings
// ─────────────────────────────────────────────────────────────────────────
"python.toolMode": {
type: "enum",
values: ["ipy-only", "bash-only", "both"] as const,
default: "both",
ui: { tab: "config", label: "Python tool mode", description: "How Python code is executed" },
},
"python.kernelMode": {
type: "enum",
values: ["session", "per-call"] as const,
default: "session",
ui: {
tab: "config",
label: "Python kernel mode",
description: "Whether to keep IPython kernel alive across calls",
},
},
"python.sharedGateway": {
type: "boolean",
default: true,
ui: {
tab: "config",
label: "Python shared gateway",
description: "Share IPython kernel gateway across pi instances",
},
},
// ─────────────────────────────────────────────────────────────────────────
// STT settings
// ─────────────────────────────────────────────────────────────────────────
"stt.enabled": {
type: "boolean",
default: false,
ui: { tab: "input", label: "Speech-to-text", description: "Enable speech-to-text input via microphone" },
},
"stt.language": {
type: "string",
default: "en",
ui: {
tab: "input",
label: "STT language",
description: "Language code for transcription (e.g., en, es, fr)",
submenu: true,
},
},
"stt.modelName": {
type: "enum",
values: ["tiny", "tiny.en", "base", "base.en", "small", "small.en", "medium", "medium.en", "large"] as const,
default: "base.en",
ui: {
tab: "input",
label: "STT model",
description: "Whisper model size (larger = more accurate but slower)",
submenu: true,
},
},
// ─────────────────────────────────────────────────────────────────────────
// Edit settings
// ─────────────────────────────────────────────────────────────────────────
"edit.fuzzyMatch": {
type: "boolean",
default: true,
ui: {
tab: "config",
label: "Edit fuzzy match",
description: "Accept high-confidence fuzzy matches for whitespace differences",
},
},
"edit.fuzzyThreshold": {
type: "number",
default: 0.95,
ui: {
tab: "config",
label: "Edit fuzzy threshold",
description: "Similarity threshold for fuzzy matches",
submenu: true,
},
},
"edit.mode": {
type: "enum",
values: ["replace", "patch", "hashline"] as const,
default: "hashline",
ui: {
tab: "config",
label: "Edit mode",
description: "Select the edit tool variant (replace, patch, or hashline)",
},
},
"edit.streamingAbort": {
type: "boolean",
default: false,
ui: {
tab: "config",
label: "Edit streaming abort",
description: "Abort streaming edit tool calls when patch preview fails",
},
},
// edit.modelVariants is complex - handle separately
// ─────────────────────────────────────────────────────────────────────────
// TTSR settings
// ─────────────────────────────────────────────────────────────────────────
"ttsr.enabled": {
type: "boolean",
default: true,
ui: {
tab: "ttsr",
label: "TTSR enabled",
description: "Time Traveling Stream Rules: interrupt agent when output matches patterns",
},
},
"ttsr.contextMode": {
type: "enum",
values: ["discard", "keep"] as const,
default: "discard",
ui: { tab: "ttsr", label: "TTSR context mode", description: "What to do with partial output when TTSR triggers" },
},
"ttsr.repeatMode": {
type: "enum",
values: ["once", "after-gap"] as const,
default: "once",
ui: {
tab: "ttsr",
label: "TTSR repeat mode",
description: "How rules can repeat: once per session or after a message gap",
},
},
"ttsr.repeatGap": {
type: "number",
default: 10,
ui: {
tab: "ttsr",
label: "TTSR repeat gap",
description: "Messages before a rule can trigger again",
submenu: true,
},
},
// ─────────────────────────────────────────────────────────────────────────
// Commit settings (no UI - advanced)
// ─────────────────────────────────────────────────────────────────────────
"commit.mapReduceEnabled": { type: "boolean", default: true },
"commit.mapReduceMinFiles": { type: "number", default: 4 },
"commit.mapReduceMaxFileTokens": { type: "number", default: 50000 },
"commit.mapReduceTimeoutMs": { type: "number", default: 120000 },
"commit.mapReduceMaxConcurrency": { type: "number", default: 5 },
"commit.changelogMaxDiffChars": { type: "number", default: 120000 },
// ─────────────────────────────────────────────────────────────────────────
// Thinking budgets (no UI - advanced)
// ─────────────────────────────────────────────────────────────────────────
"thinkingBudgets.minimal": { type: "number", default: 1024 },
"thinkingBudgets.low": { type: "number", default: 2048 },
"thinkingBudgets.medium": { type: "number", default: 8192 },
"thinkingBudgets.high": { type: "number", default: 16384 },
// ─────────────────────────────────────────────────────────────────────────
// Status line settings
// ─────────────────────────────────────────────────────────────────────────
"statusLine.preset": {
type: "enum",
values: ["default", "minimal", "compact", "full", "nerd", "ascii", "custom"] as const,
default: "default",
ui: { tab: "status", label: "Preset", description: "Pre-built status line configurations", submenu: true },
},
"statusLine.separator": {
type: "enum",
values: ["powerline", "powerline-thin", "slash", "pipe", "block", "none", "ascii"] as const,
default: "powerline-thin",
ui: {
tab: "status",
label: "Separator style",
description: "Style of separators between segments",
submenu: true,
},
},
"statusLine.showHookStatus": {
type: "boolean",
default: true,
ui: {
tab: "status",
label: "Show extension status",
description: "Display hook status messages below status line",
},
},
"statusLine.leftSegments": { type: "array", default: [] as StatusLineSegmentId[] },
"statusLine.rightSegments": { type: "array", default: [] as StatusLineSegmentId[] },
"statusLine.segmentOptions": { type: "record", default: {} as Record<string, unknown> },
} as const;
// ═══════════════════════════════════════════════════════════════════════════
// Type Inference
// ═══════════════════════════════════════════════════════════════════════════
type Schema = typeof SETTINGS_SCHEMA;
/** All valid setting paths */
export type SettingPath = keyof Schema;
/** Infer the value type for a setting path */
export type SettingValue<P extends SettingPath> = Schema[P] extends { type: "boolean" }
? boolean
: Schema[P] extends { type: "string" }
? string | undefined
: Schema[P] extends { type: "number" }
? number
: Schema[P] extends { type: "enum"; values: infer V }
? V extends readonly string[]
? V[number]
: never
: Schema[P] extends { type: "array"; default: infer D }
? D
: Schema[P] extends { type: "record"; default: infer D }
? D
: never;
/** Get the default value for a setting path */
export function getDefault<P extends SettingPath>(path: P): SettingValue<P> {
return SETTINGS_SCHEMA[path].default as SettingValue<P>;
}
/** Check if a path has UI metadata (should appear in settings panel) */
export function hasUi(path: SettingPath): boolean {
return "ui" in SETTINGS_SCHEMA[path];
}
/** Get UI metadata for a path (undefined if no UI) */
export function getUi(path: SettingPath): UiMetadata | undefined {
const def = SETTINGS_SCHEMA[path];
return "ui" in def ? (def.ui as UiMetadata) : undefined;
}
/** Get all paths for a specific tab */
export function getPathsForTab(tab: SettingTab): SettingPath[] {
return (Object.keys(SETTINGS_SCHEMA) as SettingPath[]).filter(path => {
const ui = getUi(path);
return ui?.tab === tab;
});
}
/** Get the type of a setting */
export function getType(path: SettingPath): SettingDef["type"] {
return SETTINGS_SCHEMA[path].type;
}
/** Get enum values for an enum setting */
export function getEnumValues(path: SettingPath): readonly string[] | undefined {
const def = SETTINGS_SCHEMA[path];
return "values" in def ? (def.values as readonly string[]) : undefined;
}
// ═══════════════════════════════════════════════════════════════════════════
// Derived Types from Schema
// ═══════════════════════════════════════════════════════════════════════════
/** Status line preset - derived from schema */
export type StatusLinePreset = SettingValue<"statusLine.preset">;
/** Status line separator style - derived from schema */
export type StatusLineSeparatorStyle = SettingValue<"statusLine.separator">;
// ═══════════════════════════════════════════════════════════════════════════
// Typed Group Definitions
// ═══════════════════════════════════════════════════════════════════════════
export interface CompactionSettings {
enabled: boolean;
reserveTokens: number;
keepRecentTokens: number;
autoContinue: boolean;
remoteEndpoint: string | undefined;
}
export interface RetrySettings {
enabled: boolean;
maxRetries: number;
baseDelayMs: number;
}
export interface MemoriesSettings {
enabled: boolean;
maxRolloutsPerStartup: number;
maxRolloutAgeDays: number;
minRolloutIdleHours: number;
threadScanLimit: number;
maxRawMemoriesForGlobal: number;
stage1Concurrency: number;
stage1LeaseSeconds: number;
stage1RetryDelaySeconds: number;
phase2LeaseSeconds: number;
phase2RetryDelaySeconds: number;
phase2HeartbeatSeconds: number;
rolloutPayloadPercent: number;
fallbackTokenLimit: number;
summaryInjectionTokenLimit: number;
}
export interface TodoCompletionSettings {
enabled: boolean;
maxReminders: number;
}
export interface BranchSummarySettings {
enabled: boolean;
reserveTokens: number;
}
export interface SkillsSettings {
enabled?: boolean;
enableSkillCommands?: boolean;
enableCodexUser?: boolean;
enableClaudeUser?: boolean;
enableClaudeProject?: boolean;
enablePiUser?: boolean;
enablePiProject?: boolean;
customDirectories?: string[];
ignoredSkills?: string[];
includeSkills?: string[];
}
export interface CommitSettings {
mapReduceEnabled: boolean;
mapReduceMinFiles: number;
mapReduceMaxFileTokens: number;
mapReduceTimeoutMs: number;
mapReduceMaxConcurrency: number;
changelogMaxDiffChars: number;
}
export interface TtsrSettings {
enabled: boolean;
contextMode: "discard" | "keep";
repeatMode: "once" | "after-gap";
repeatGap: number;
}
export interface ExaSettings {
enabled: boolean;
enableSearch: boolean;
enableLinkedin: boolean;
enableCompany: boolean;
enableResearcher: boolean;
enableWebsets: boolean;
}
export interface StatusLineSettings {
preset: StatusLinePreset;
separator: StatusLineSeparatorStyle;
showHookStatus: boolean;
leftSegments: StatusLineSegmentId[];
rightSegments: StatusLineSegmentId[];
segmentOptions: Record<string, unknown>;
}
export interface ThinkingBudgetsSettings {
minimal: number;
low: number;
medium: number;
high: number;
}
export interface SttSettings {
enabled: boolean;
language: string | undefined;
modelName: string;
whisperPath: string | undefined;
modelPath: string | undefined;
}
export interface BashInterceptorRule {
pattern: string;
flags?: string;
tool: string;
message: string;
allowSubcommands?: string[];
}
/** Map group prefix -> typed settings interface */
export interface GroupTypeMap {
compaction: CompactionSettings;
retry: RetrySettings;
memories: MemoriesSettings;
branchSummary: BranchSummarySettings;
skills: SkillsSettings;
commit: CommitSettings;
ttsr: TtsrSettings;
exa: ExaSettings;
statusLine: StatusLineSettings;
thinkingBudgets: ThinkingBudgetsSettings;
stt: SttSettings;
modelRoles: Record<string, string>;
}
export type GroupPrefix = keyof GroupTypeMap;