/** * Serializable configuration and defaults for the pi-tui terminal mode. Loader * schema validation normally fills defaults; {@link resolveTuiConfig} applies * the same defaults for direct callers that bypass the Loader. * @module @deepseek-ai/dsh-tui/config */ import z from 'schemastery' import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, } from './chat/file-autocomplete.ts' /** Theme and prompt-template settings for the pi-tui terminal mode. */ export interface TuiThemeConfig { /** Apply the built-in ANSI color palette. */ color?: boolean /** Paint the startup banner with the 24-bit DeepSeek brand gradient. */ truecolor?: boolean /** Left-aligned template on the row above the editor. */ leftPrompt?: string /** Right-aligned template on the row above the editor. */ rightPrompt?: string /** Template used as the editor's first-line prefix. */ inputPrompt?: string /** Static placeholder shown in an empty editor while the agent is running. */ inputPlaceholder?: string } /** Interaction and presentation settings for the pi-tui terminal mode. */ export interface TuiConfig { /** Render model reasoning blocks. */ showReasoning?: boolean /** Maximum tool-card body lines retained in its collapsed head/tail preview. */ maxToolOutputLines?: number /** Maximum options visible at once in a user-question panel. */ maxQuestionOptions?: number /** Maximum models visible at once in the model selector. */ maxModelOptions?: number /** Maximum sessions visible at once in the resume selector. */ maxResumeOptions?: number /** User-question panel width in terminal columns, clamped to the terminal. */ questionDialogWidth?: number /** User-question panel maximum height in terminal rows. */ questionDialogMaxHeight?: number /** Model-selector width in terminal columns. */ modelDialogWidth?: number /** Model-selector maximum height in terminal rows. */ modelDialogMaxHeight?: number /** Maximum fuzzy file candidates displayed for one `@` query. */ fileSearchMaxResults?: number /** Maximum paths retained in one `@` workspace index. */ fileSearchMaxEntries?: number /** Directory basenames excluded from `@` traversal and completion. */ fileSearchExcludedDirectories?: string[] /** Show the terminal's hardware cursor at the pi editor's IME marker. */ showHardwareCursor?: boolean /** Color and prompt-template settings. */ theme?: TuiThemeConfig /** Terminal window title while the UI is mounted; a logged session title prefixes it. */ title?: string } const showReasoningSchema = z.boolean().default(true) const maxToolOutputLinesSchema = z.number().step(1).min(1).default(6) const maxQuestionOptionsSchema = z.number().step(1).min(1).default(8) const maxModelOptionsSchema = z.number().step(1).min(1).default(8) const maxResumeOptionsSchema = z.number().step(1).min(1).default(8) const questionDialogWidthSchema = z.number().step(1).min(20).default(200) const questionDialogMaxHeightSchema = z.number().step(1).min(6).default(20) const modelDialogWidthSchema = z.number().step(1).min(20).default(76) const modelDialogMaxHeightSchema = z.number().step(1).min(6).default(20) const fileSearchMaxResultsSchema = z.number().step(1).min(1).default(DEFAULT_FILE_SEARCH_MAX_RESULTS) const fileSearchMaxEntriesSchema = z.number().step(1).min(1).default(DEFAULT_FILE_SEARCH_MAX_ENTRIES) const fileSearchExcludedDirectoriesSchema = z.array(z.string()).default([...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]) const showHardwareCursorSchema = z.boolean().default(false) const colorSchema = z.boolean().default(true) // No default: an unset value auto-detects truecolor from COLORTERM in `apply`. const truecolorSchema = z.boolean() const DEFAULT_LEFT_PROMPT = '${cwd}${git/worktree}${model}${token_meter/cache_hit_rate}${context}' const DEFAULT_RIGHT_PROMPT = '${queued}' const DEFAULT_INPUT_PROMPT = '${symbol} ${indicator}' const DEFAULT_INPUT_PLACEHOLDER = 'press enter to steer and esc to cancel' const TuiThemeConfigSchema: z = z.object({ color: colorSchema, truecolor: truecolorSchema, leftPrompt: z.string().default(DEFAULT_LEFT_PROMPT), rightPrompt: z.string().default(DEFAULT_RIGHT_PROMPT), inputPrompt: z.string().default(DEFAULT_INPUT_PROMPT), inputPlaceholder: z.string().default(DEFAULT_INPUT_PLACEHOLDER), }) const titleSchema = z.string().default('DeepSeek Harness') const tuiConfigSchemaFields = { showReasoning: showReasoningSchema, maxToolOutputLines: maxToolOutputLinesSchema, maxQuestionOptions: maxQuestionOptionsSchema, maxModelOptions: maxModelOptionsSchema, maxResumeOptions: maxResumeOptionsSchema, questionDialogWidth: questionDialogWidthSchema, questionDialogMaxHeight: questionDialogMaxHeightSchema, modelDialogWidth: modelDialogWidthSchema, modelDialogMaxHeight: modelDialogMaxHeightSchema, fileSearchMaxResults: fileSearchMaxResultsSchema, fileSearchMaxEntries: fileSearchMaxEntriesSchema, fileSearchExcludedDirectories: fileSearchExcludedDirectoriesSchema, showHardwareCursor: showHardwareCursorSchema, theme: TuiThemeConfigSchema, title: titleSchema, } /** Schemastery schema for presentation settings embedded by app bundles. */ export const TuiConfigSchema: z = z.object(tuiConfigSchemaFields) /** Serializable plugin configuration. */ export interface Config extends TuiConfig { /** Banner subtitle line. When absent, the banner has no subtitle and sweeps in on start. */ welcome?: string /** Exact shared agent/session identity driven by this terminal. Defaults to `main`. */ sessionId?: string /** * Skill name auto-invoked as this session's first user turn, exactly as if * the user typed `/skill:`. Set only by a launcher for a fresh * skill-guided session (`dsh migrate`/`dsh upgrade`); absent leaves the first * turn to the user. */ initialSkill?: string } /** Schemastery schema for the full plugin configuration. */ export const Config: z = z.object({ welcome: z.string(), sessionId: z.string().default('main'), initialSkill: z.string(), showReasoning: tuiConfigSchemaFields.showReasoning, maxToolOutputLines: tuiConfigSchemaFields.maxToolOutputLines, maxQuestionOptions: tuiConfigSchemaFields.maxQuestionOptions, maxModelOptions: tuiConfigSchemaFields.maxModelOptions, maxResumeOptions: tuiConfigSchemaFields.maxResumeOptions, questionDialogWidth: tuiConfigSchemaFields.questionDialogWidth, questionDialogMaxHeight: tuiConfigSchemaFields.questionDialogMaxHeight, modelDialogWidth: tuiConfigSchemaFields.modelDialogWidth, modelDialogMaxHeight: tuiConfigSchemaFields.modelDialogMaxHeight, fileSearchMaxResults: tuiConfigSchemaFields.fileSearchMaxResults, fileSearchMaxEntries: tuiConfigSchemaFields.fileSearchMaxEntries, fileSearchExcludedDirectories: tuiConfigSchemaFields.fileSearchExcludedDirectories, showHardwareCursor: tuiConfigSchemaFields.showHardwareCursor, theme: tuiConfigSchemaFields.theme, title: tuiConfigSchemaFields.title, }) /** Fully defaulted TUI theme settings. */ export interface ResolvedTuiThemeConfig { color: boolean truecolor: boolean leftPrompt: string rightPrompt: string inputPrompt: string inputPlaceholder: string } /** Fully defaulted TUI presentation settings. */ export interface ResolvedTuiConfig { showReasoning: boolean maxToolOutputLines: number maxQuestionOptions: number maxModelOptions: number maxResumeOptions: number questionDialogWidth: number questionDialogMaxHeight: number modelDialogWidth: number modelDialogMaxHeight: number fileSearchMaxResults: number fileSearchMaxEntries: number fileSearchExcludedDirectories: string[] showHardwareCursor: boolean theme: ResolvedTuiThemeConfig title: string } /** * Apply direct-call defaults after Loader schema validation has normally run. * * @param config - Deployment-provided terminal presentation settings. * @returns Complete settings consumed by the TUI renderer. */ export function resolveTuiConfig(config: TuiConfig | undefined): ResolvedTuiConfig { return { showReasoning: config?.showReasoning ?? true, maxToolOutputLines: config?.maxToolOutputLines ?? 6, maxQuestionOptions: config?.maxQuestionOptions ?? 8, maxModelOptions: config?.maxModelOptions ?? 8, maxResumeOptions: config?.maxResumeOptions ?? 8, questionDialogWidth: config?.questionDialogWidth ?? 200, questionDialogMaxHeight: config?.questionDialogMaxHeight ?? 20, modelDialogWidth: config?.modelDialogWidth ?? 76, modelDialogMaxHeight: config?.modelDialogMaxHeight ?? 20, fileSearchMaxResults: config?.fileSearchMaxResults ?? DEFAULT_FILE_SEARCH_MAX_RESULTS, fileSearchMaxEntries: config?.fileSearchMaxEntries ?? DEFAULT_FILE_SEARCH_MAX_ENTRIES, fileSearchExcludedDirectories: [...(config?.fileSearchExcludedDirectories ?? DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES)], showHardwareCursor: config?.showHardwareCursor ?? false, theme: { color: config?.theme?.color ?? true, truecolor: config?.theme?.truecolor ?? false, leftPrompt: config?.theme?.leftPrompt ?? DEFAULT_LEFT_PROMPT, rightPrompt: config?.theme?.rightPrompt ?? DEFAULT_RIGHT_PROMPT, inputPrompt: config?.theme?.inputPrompt ?? DEFAULT_INPUT_PROMPT, inputPlaceholder: config?.theme?.inputPlaceholder ?? DEFAULT_INPUT_PLACEHOLDER, }, title: config?.title ?? 'DeepSeek Harness', } }