@x-otto/tui 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WizardDialog-BGWYjvqR.js","names":["stringWidthLib","stringWidth","graphemeSegmenter","stringWidth","scopeMatches","scopeMatches","toneColorFor","SPINNER_FRAME_MS"],"sources":["../src/theme/colors.ts","../src/term/cjk-locale.ts","../src/term/insert-history.ts","../src/theme/figures.ts","../src/theme/borders.ts","../src/theme/spacing.ts","../src/theme/presets/dark.ts","../src/theme/presets/light.ts","../src/theme/theme-registry.ts","../src/theme/theme-ref.ts","../src/theme/ThemeProvider.tsx","../src/term/stdin-encoding.ts","../src/theme/detect-terminal-theme.ts","../src/input/keyhints.ts","../src/layouts/DialogFrame.tsx","../src/layouts/DialogTitle.tsx","../src/components/Kbd.tsx","../src/layouts/HintBar.tsx","../src/router/Tabable.tsx","../src/router/FocusBus.tsx","../src/components/primitives/RovingFocusGroup.tsx","../src/components/primitives/Menu.tsx","../src/input/cursor.ts","../src/input/text-input-logic.ts","../src/input/ime-coalesce.ts","../src/input/use-measured-cursor-origin.ts","../src/input/use-physical-cursor.ts","../src/input/TextInput.tsx","../src/keybindings/registry.ts","../src/keybindings/overrides.ts","../src/keybindings/match.ts","../src/keybindings/control-registry.ts","../src/keybindings/conflict.ts","../src/router/key-dispatch.ts","../src/router/router-ref.ts","../src/router/Router.tsx","../src/components/ScrollIndicators.tsx","../src/components/Typography.tsx","../src/input/horizontal-step.ts","../src/components/Tag.tsx","../src/term/viewport-budget.ts","../src/components/progress-bar/progress-bar-model.ts","../src/components/progress-bar/progress-bar-chars.ts","../src/term/truncate-display.ts","../src/layouts/GroupDivider.tsx","../src/dialogs/select-row-window.ts","../src/utils/motion.ts","../src/utils/animation-scheduler.ts","../src/dialogs/select.ts","../src/dialogs/select-dialog-logic.ts","../src/dialogs/contrast.ts","../src/components/Button.tsx","../src/dialogs/SelectPrimitive.tsx","../src/dialogs/ConfirmDialog.tsx","../src/components/List.tsx","../src/components/DataList.tsx","../src/dialogs/InfoDialog.tsx","../src/components/progress-bar/ProgressBar.tsx","../src/dialogs/ProgressDialog.tsx","../src/dialogs/questions-dialog-logic.ts","../src/dialogs/QuestionsDialog.tsx","../src/dialogs/WizardDialog.tsx"],"sourcesContent":["/**\n * colors.ts — 调色板、语义色、色值工具函数。\n */\n\n/** 基础调色板。 */\nexport const PALETTE = {\n ink: '#0D0E11',\n carbon: '#16181D',\n white: '#F4F3EF',\n /** 品牌三色:远航藏青 Brand Navy / 拾月紫 Accent Purple / 沧溟墨 Ink Gray。 */\n brandNavy: '#14348C',\n brandPurple: '#A28EF9',\n brandInk: '#12152B',\n /** 拾月紫亮/暗端。 */\n purpleBright: '#D1BCFF',\n purpleDim: '#4F3E8C',\n /** 远航藏青\"交互焦点\"亮端——focusBorder/selection/suggestion/permission 专用。 */\n navyBright: '#2D59C8',\n navyDim: '#0F2A73',\n /** 注释蓝灰(品牌手册官方灰阶 gray-8)。 */\n comment: '#9498B8',\n} as const\n\n/** 状态五色(取自语法高亮 One-Dark 系)。 */\nexport const STATUS = {\n success: '#98C379',\n error: '#E06C75',\n warning: '#E5C07B',\n info: '#61AFEF',\n special: '#C678DD',\n} as const\n\n/**\n * 语义角色 → 具体色。消费方只引语义名,便于整体换肤。\n * 分组:品牌琥珀 / 文本 / 状态 / 信息蓝 / diff / chrome。\n */\nexport interface ThemeColors {\n /** 品牌字标(Welcome \"otto\")。 */\n brand: string\n /** 品牌字标渐变亮端。 */\n brandShimmer: string\n accent: string\n /** 呼吸/glow 渐变暗端(hex)。 */\n accentDim: string\n /** 呼吸/glow 渐变亮端。 */\n accentBright: string\n /** 主文本——undefined=终端默认前景(自适应明暗)。 */\n text: string | undefined\n inactive: string\n /** 次级文本——比 inactive 深,用于工具名/面板 label 等需要明确可读但非主角的内容。 */\n secondary: string\n /** 预留:比 inactive 更弱的次级文本(当前同 inactive 值;保留供分级弱化)。 */\n subtle: string\n /** 助手回复行首前缀 `>` 的颜色——与用户前缀(inactive 灰)区分。 */\n replyPrefix: string\n success: string\n error: string\n warning: string\n special: string\n /** 自动补全/提示/标识符高亮(模型名、/命令、语言标签)。 */\n suggestion: string\n /** 权限请求高亮(工具名/命令)。 */\n permission: string\n /** 行内 `code` 强调色——青/蓝系,区别于 *italic*(斜体)与 **bold**(加粗);用青系区分 codespan。 */\n codeText: string\n diffAdd: string\n diffRemove: string\n /** diff 行背景 tint(GitHub 风)。 */\n diffAddBg: string\n diffRemoveBg: string\n /** 浮层/面板统一边框色——所有弹层/面板的上下灰线(配合 PANEL_BORDER 形状常量)。 */\n panelBorder: string\n /** 浮层/面板暗底——所有 dialog/toast 的统一背景色(hex)。 */\n overlayBg: string\n /** 内联标签(Tag 组件)的浅色底——比 overlayBg 稍亮的微高亮块,衬托 `[label]`。 */\n tagBg: string\n /** 工具调用 / Thought 块文字色——同 secondary。\n * @deprecated 新代码用 THEME.secondary 替代,toolText 保留兼容旧消费方。 */\n toolText: string\n /** 聚焦态高亮——亮蓝色:弹窗标题文字、配置引导提示等(边框已统一为 panelBorder 灰,不再用蓝)。 */\n focusBorder: string\n /** 选中/激活前景——亮蓝色(与 focusBorder 同色系,语义独立):列表/菜单/树选中行、激活 tab/按钮。 */\n selection: string\n}\n\n/** 固定配色——所有 AA 正常文字 ≥4.5:1。 */\nexport const THEME: ThemeColors = {\n brand: PALETTE.brandPurple,\n brandShimmer: PALETTE.purpleBright,\n accent: PALETTE.brandPurple,\n accentDim: PALETTE.purpleDim,\n accentBright: PALETTE.purpleBright,\n text: undefined,\n inactive: PALETTE.comment,\n secondary: '#C8CBDB',\n subtle: PALETTE.comment,\n replyPrefix: '#8F7FD6',\n success: '#7EE095',\n error: '#FF9592',\n warning: '#FFC966',\n special: '#DD78C9',\n suggestion: '#6187EF',\n permission: '#6187EF',\n codeText: '#56B6C2',\n diffAdd: '#7EE095',\n diffRemove: '#FF9592',\n diffAddBg: '#183820',\n diffRemoveBg: '#3C181A',\n panelBorder: PALETTE.comment,\n overlayBg: PALETTE.brandInk,\n tagBg: '#262A3D',\n toolText: '#C8CBDB',\n focusBorder: '#4D7CFF',\n selection: PALETTE.brandPurple, // RFC-273 M2:选中色统一为品牌紫(原焦点蓝 #4D7CFF)——复议 RFC-188 accent/selection 分离\n}\n\n/**\n * 把语义色(可能是 hex 或命名色)解析为 [r,g,b];非 hex(ansi 命名色)返回 null。\n * 供需要真彩插值的动画(呼吸/glow)用——拿不到 rgb 时退化为不插值。\n */\nexport function toRGB(color: string | undefined): [number, number, number] | null {\n if (!color || color[0] !== '#') return null\n const h = color.slice(1)\n const n =\n h.length === 3\n ? h\n .split('')\n .map((c) => c + c)\n .join('')\n : h\n if (n.length !== 6) return null\n const v = parseInt(n, 16)\n if (Number.isNaN(v)) return null\n return [(v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff]\n}\n\n/** 把 [r,g,b] 转回 #rrggbb(分量自动钳到 0–255)。 */\nexport function fromRGB([r, g, b]: [number, number, number]): string {\n return (\n '#' +\n [r, g, b]\n .map((v) =>\n Math.round(Math.max(0, Math.min(255, v)))\n .toString(16)\n .padStart(2, '0'),\n )\n .join('')\n )\n}\n\n/**\n * riskColor — 风险级别 → 语义色。\n */\nexport function riskColor(risk: 'low' | 'medium' | 'high'): string {\n return risk === 'high' ? THEME.error : risk === 'medium' ? THEME.warning : THEME.success\n}\n","/**\n * cjk-locale.ts — CJK 终端环境检测(单一真源)。\n *\n * `string-width` 默认将 ambiguous-width 字符(U+2500-257F box-drawing、U+2190-21FF\n * arrows 等)计为宽度 1,但 CJK locale 终端渲染为宽度 2。本模块统一检测当前进程\n * 是否处于 CJK locale,供 `displayWidth`(insert-history.ts)与 `stringWidth`\n * (cursor.ts)共享,避免两处各写一份检测逻辑导致漂移。\n *\n * 检测逻辑:遍历 LC_ALL → LC_CTYPE → LANG,首个匹配 `zh_/ja_/ko_` 前缀者判定 CJK。\n * macOS 默认 `LC_CTYPE=UTF-8` 不匹配(UTF-8 是编码名非 locale)。\n * 进程生命周期内不变,模块加载时执行一次。\n */\nconst CJK_LOCALE_RE = /^(zh|ja|ko)[_-]/i\n\nexport const isCjkLocale: boolean = (() => {\n for (const key of ['LC_ALL', 'LC_CTYPE', 'LANG'] as const) {\n const v = process.env[key]\n if (v && CJK_LOCALE_RE.test(v)) return true\n }\n return false\n})()\n\n/** `string-width` 的 CJK 感知选项(非 CJK 环境为 undefined → 用库默认值)。 */\nexport const stringWidthOpts: { countAnsiEscapeCodes: false; ambiguousIsNarrow?: boolean } = {\n countAnsiEscapeCodes: false,\n}\nif (isCjkLocale) stringWidthOpts.ambiguousIsNarrow = false\n","/**\n * insert-history.ts — RFC-021 P2: 直写终端 scrollback 的历史插入器。\n *\n * 背景(见 RFC-021):stock Ink 的 `log-update` 在「长转录 + 多行流式高频重绘」时,会把滚出视口的\n * 行留在 scrollback → 整篇重复 / 逐帧洪水。根治方案:**已完成的转录不再进 Ink 的 `<Static>`,\n * 由我们用 ANSI 滚动区(DECSTBM)把完成行直接插进终端原生 scrollback**,Ink 只保留底部恒小 live 区。\n *\n * 本模块只负责「写出转义序列」这一纯函数层(P2-01/02/04):\n * - `insertHistorySequence(lines, opts)` → 返回应写入 stdout 的转义字符串(纯函数,易测)。\n * - `insertHistory(out, lines, opts)` → 写入流。\n * - `preWrapLines` / `pickInsertMode` → 预换行(CJK 宽字符 / URL 不断裂)与模式选择。\n *\n * 与 Ink live 区的光标/时序协调(在 Ink 帧前/后插、live 区高度测量)属 P3,不在本文件。\n *\n * 标准模式(对标 insert_history.rs:193-245):\n * ┌─Screen────────────────┐\n * │┌╌Scroll region╌╌╌╌╌╌┐│ ← `\\x1b[1;T r`:只滚动 [1, liveTop] 这块(视口之上)\n * │█╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘│ ← MoveTo(0, T-1):光标落到滚动区最后一行\n * │╭─Live region (Ink)──╮│ ← liveTop..rows-1,**不在滚动区**,原样保留\n * │╰────────────────────╯│\n * └────────────────────────┘\n * 每写一行:`\\r\\n`(在滚动区底把整块上滚一行、顶行进 scrollback)+ 写该行。末尾复位滚动区、还原光标。\n */\nimport stringWidth from 'string-width'\nimport wrapAnsi from 'wrap-ansi'\nimport { stringWidthOpts } from './cjk-locale'\n\nconst ESC = '\\x1b'\n\n/** DECSTBM 设滚动区(1-based inclusive)。 */\nconst setScrollRegion = (top1: number, bottom1: number): string => `${ESC}[${top1};${bottom1}r`\n/** 复位滚动区为整屏。 */\nconst resetScrollRegion = (): string => `${ESC}[r`\n/** CUP 绝对定位(入参 0-based,输出 1-based)。 */\nconst moveTo = (x0: number, y0: number): string => `${ESC}[${y0 + 1};${x0 + 1}H`\n/** EL:清到行尾。 */\nconst clearToEol = (): string => `${ESC}[K`\n/** ED:清到屏幕底。 */\nconst clearBelow = (): string => `${ESC}[J`\n/** SGR 复位(防颜色跨行渗漏)。 */\nconst sgrReset = (): string => `${ESC}[0m`\nconst savePos = (): string => `${ESC}7`\nconst restorePos = (): string => `${ESC}8`\n\nexport type InsertHistoryMode = 'standard' | 'raw'\nexport type WrapPolicy = 'pre' | 'terminal'\n\nexport interface InsertHistoryOptions {\n /** 终端总行数。 */\n rows: number\n /** 终端总列数(= 预换行宽度)。 */\n columns: number\n /** Ink live 区顶部所在屏幕行(0-based)。历史插在它之上。\n * live 区紧贴底部时 = rows - liveHeight。 */\n liveTop: number\n /** 插入后光标还原位置(缺省 = live 区左上角 {0, liveTop})。 */\n cursor?: { x: number; y: number }\n /** 标准(DECSTBM 滚动区)/ raw(Zellij/老终端兜底,直写+预留空行)。 */\n mode?: InsertHistoryMode\n /** pre = 我们按宽度预换行;terminal = 交给终端软换行。缺省 pre。 */\n wrap?: WrapPolicy\n}\n\n/** URL-ish token 探测(保 URL 不被硬断 → 终端可识别为可点链接)。 */\nconst URL_RE = /(?:https?:\\/\\/|www\\.)\\S+|\\b[\\w-]+(?:\\.[\\w-]+)+\\/\\S+/i\n\nconst STRIP_RE = new RegExp(\n `${ESC}\\\\][^\\\\x07${ESC}]*(?:\\\\x07|${ESC}\\\\\\\\)|${ESC}\\\\[[0-9;?]*[ -/]*[@-~]`,\n 'g',\n)\nconst stripAnsi = (s: string): string => s.replace(STRIP_RE, '')\n\n/** 可视宽度(剥 ANSI + CJK 宽字符占 2 + ambiguous-width 按终端 locale 校准)。 */\nexport const displayWidth = (s: string): number => stringWidth(s, stringWidthOpts)\n\nconst lineContainsUrlLike = (line: string): boolean => URL_RE.test(stripAnsi(line))\n\n/**\n * 把一条逻辑行预换行成若干物理行。\n * - `terminal` 策略 / 宽度 ≤ 0:原样返回(交给终端软换行)。\n * - 含 URL 的行:软换行(`hard:false`)——长 URL token 不被切断,可保持可点。\n * - 普通散文行:硬换行(`hard:true`)——任何超宽行都被切到 ≤ 宽度,渲染可预测。\n * 保留行首缩进(`trim:false`);空行→单个空物理行(保留段落间距)。\n */\nexport function preWrapLine(line: string, width: number, policy: WrapPolicy = 'pre'): string[] {\n if (policy === 'terminal' || width <= 0) return [line]\n if (line === '') return ['']\n const url = lineContainsUrlLike(line)\n const wrapped = wrapAnsi(line, width, { hard: !url, trim: false, wordWrap: true })\n return wrapped.length === 0 ? [''] : wrapped.split('\\n')\n}\n\n/** 把多条逻辑行展开为物理行序列。 */\nexport function preWrapLines(\n lines: readonly string[],\n width: number,\n policy: WrapPolicy = 'pre',\n): string[] {\n const out: string[] = []\n for (const line of lines) out.push(...preWrapLine(line, width, policy))\n return out\n}\n\n/**\n * 写一条已预换行的物理行:宽行(URL 溢出)先清续行、再 `\\x1b[K` 清当前行、写内容、复位 SGR。\n * 调用方负责先发 `\\r\\n` 与光标定位。\n */\nfunction writeRow(row: string, width: number): string {\n const physical = Math.max(1, Math.ceil(displayWidth(row) / width))\n let s = ''\n if (physical > 1) {\n s += savePos()\n for (let i = 1; i < physical; i++) s += `${ESC}[1B${ESC}[1G${clearToEol()}`\n s += restorePos()\n }\n s += clearToEol() + row + sgrReset()\n return s\n}\n\n/**\n * Raw 兜底(对标 ZellijRaw, insert_history.rs:162-192):不靠 DECSTBM,\n * 从 live 区顶清屏向下 → 直写历史行 → 补 liveHeight 个空行把视口顶下移 → 还原光标。\n * 用于 Zellij / 不可靠透传滚动区的终端。\n */\nfunction rawInsert(\n wrapped: readonly string[],\n opts: InsertHistoryOptions,\n width: number,\n cursor: { x: number; y: number },\n): string {\n const liveHeight = Math.max(1, opts.rows - opts.liveTop)\n let s = moveTo(0, opts.liveTop) + clearBelow() + moveTo(0, opts.liveTop)\n wrapped.forEach((row, i) => {\n if (i > 0) s += '\\r\\n'\n s += writeRow(row, width)\n })\n for (let i = 0; i < liveHeight; i++) s += `\\r\\n${clearToEol()}`\n s += moveTo(cursor.x, cursor.y)\n return s\n}\n\n/**\n * 生成把 `lines` 插进 scrollback(live 区之上)的转义序列。纯函数,不触 I/O。\n * 返回空串表示无可写内容。\n */\nexport function insertHistorySequence(\n lines: readonly string[],\n opts: InsertHistoryOptions,\n): string {\n const width = Math.max(1, opts.columns)\n const wrap = opts.wrap ?? 'pre'\n const mode = opts.mode ?? 'standard'\n const cursor = opts.cursor ?? { x: 0, y: opts.liveTop }\n\n const wrapped = preWrapLines(lines, width, wrap)\n if (wrapped.length === 0) return ''\n\n if (mode === 'raw' || opts.liveTop < 1) return rawInsert(wrapped, opts, width, cursor)\n\n const T = opts.liveTop\n let out = setScrollRegion(1, T) + moveTo(0, T - 1)\n for (const row of wrapped) out += '\\r\\n' + writeRow(row, width)\n out += resetScrollRegion() + moveTo(cursor.x, cursor.y)\n return out\n}\n\n/** 写出历史插入序列到流(薄封装)。 */\nexport function insertHistory(\n out: NodeJS.WriteStream,\n lines: readonly string[],\n opts: InsertHistoryOptions,\n): void {\n const seq = insertHistorySequence(lines, opts)\n if (seq) out.write(seq)\n}\n\n/** 按环境选模式:Zellij 走 raw,其余走标准 DECSTBM。 */\nexport function pickInsertMode(env: NodeJS.ProcessEnv = process.env): InsertHistoryMode {\n return env['ZELLIJ'] ? 'raw' : 'standard'\n}\n","/**\n * figures.ts — 符号常量、spinner/呼吸帧、宽度校验。\n */\nimport { displayWidth } from '../term/insert-history'\n\n/** 符号常量(统一字形)。渲染层引这里,不写裸字形。 */\nconst DEFAULT_FIGURES = {\n /** 助手回复行首前缀 ●(实心圆,与用户前缀 `>` 异符异色,强化对话角色区分)。 */\n bullet: '●',\n /** 工具结果折叠角标 ⎿ */\n toolResult: '⎿',\n /** thinking 前缀 ∴ */\n thinking: '∴',\n /** 引用竖线 ▎ */\n quoteBar: '▎',\n /** 用户输入前缀 > */\n userPrompt: '>',\n /** 本地命令回显前缀 ❯ */\n commandPrompt: '❯',\n /** 水平分隔 ─(细线) */\n hr: '─',\n /** 成功/失败标记 */\n check: '✓',\n cross: '✗',\n /** 任务列表复选框:未勾 ☐ / 已勾 ☑ */\n checkboxOff: '☐',\n checkboxOn: '☑',\n /** 任务状态:未开始 ○ / 进行中 ◐ / 完成 ● */\n todoPending: '○',\n todoActive: '◐',\n todoDone: '●',\n /** 呼吸圆圈:暗(空心)○ / 亮(实心)● */\n breatheDim: '○',\n breatheBright: '●',\n /** 省略号 */\n ellipsis: '…',\n /** 列表/typeahead 选中行指示箭头 ▶ */\n pointer: '▶',\n /** 开关态:开 ◉ / 关 ○ */\n toggleOn: '◉',\n toggleOff: '○',\n /** 告警符号 ⚠ */\n warn: '⚠',\n /** 对话选中态保护——用户手动冻结指示 ❄ */\n frozen: '❄',\n /** 等待输入态 ◑ */\n needsInput: '◑',\n /** 状态圆点 ● */\n statusDot: '●',\n /** 条目激活标记 ▸ */\n itemActive: '▸',\n /** 注意/待处理菱形 ◆ */\n attention: '◆',\n /** Sparkline 八阶块字符 */\n sparkline: '▁▂▃▄▅▆▇█',\n /** LineGauge 已完成段 ━ */\n gaugeFilled: '━',\n /** LineGauge 未完成段 ╌ */\n gaugeRest: '╌',\n /** Scrollbar thumb 右半块 ▐ */\n scrollThumb: '▐',\n /** 委托任务识别到的服务前缀 ↕ */\n service: '↕',\n /** 已编辑文件标记 ✎(RFC-309 SummaryStrip) */\n edited: '✎',\n /** 定时/计划任务标记 ◷(RFC-309 SummaryStrip) */\n scheduled: '◷',\n /** 剪贴板信号 ✓(复制成功提示) */\n clipboard: '✓',\n /** LLM 调用阶段箭头:requesting ↑,responding ↓ */\n requesting: '↑',\n responding: '↓',\n} as const\n\nexport type FiguresMap = typeof DEFAULT_FIGURES\n\n/** 符号常量表(固定值,不再支持运行时覆盖)。 */\nexport const FIGURES: FiguresMap = DEFAULT_FIGURES\n\n/** 单字符且显示宽度为 1(CJK/emoji/宽字符均排除)。 */\nexport function isSingleWidthChar(s: string): boolean {\n return [...s].length === 1 && displayWidth(s) === 1\n}\n\n/** `sparkline` 是 8 字符字符串——长度恒为 8 且每个字符单宽度。 */\nexport function isValidSparkline(s: string): boolean {\n const chars = [...s]\n return chars.length === 8 && chars.every((c) => displayWidth(c) === 1)\n}\n\n/** 参与 `.repeat(width)` 精确宽度计算的键——必须强制单字符宽度。 */\nexport const WIDTH_CRITICAL_KEYS: ReadonlySet<string> = new Set(['hr', 'gaugeFilled', 'gaugeRest'])\n\n/** Spinner 帧——otto 原创 braille 旋转。平滑 10 帧。 */\nexport const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']\n\n/** 呼吸圆圈帧——暗 ○ ↔ 亮 ● 交替,模拟吸气-呼气。三角波 10 帧平滑过渡。 */\nconst _UP = Array.from({ length: 6 }, (_, i) => i / 5)\nconst _CYCLE = [..._UP, ..._UP.slice(1, -1).reverse()]\n\nexport const BREATH_FRAMES: ReadonlyArray<string> = _CYCLE.map((t) =>\n t < 0.5 ? FIGURES.breatheDim : FIGURES.breatheBright,\n)\n","/**\n * borders.ts — 面板/弹层边框形态常量。\n */\n\n/**\n * 内联面板统一边框形状——仅上下两条单线(无左右)。\n *\n * **单一真源**:所有内联 UI 元素写 `<Box {...PANEL_BORDER} borderColor={THEME.panelBorder}>`。\n */\nexport const PANEL_BORDER = {\n borderStyle: 'single',\n borderTop: true,\n borderBottom: true,\n borderLeft: false,\n borderRight: false,\n} as const\n\n/**\n * PANEL_BORDER_TOP — 仅顶部单线分隔(PANEL_BORDER 的上半)。\n * 用于内容块上方的分隔线(如 Home 首屏问候块)。\n */\nexport const PANEL_BORDER_TOP = {\n borderStyle: 'single',\n borderTop: true,\n borderBottom: false,\n borderLeft: false,\n borderRight: false,\n} as const\n\n/**\n * PANEL_BORDER_BOTTOM — 仅底部单线分隔(PANEL_BORDER 的下半)。\n * 用于内容块下方的分隔线(如会话头 SessionHeader)。\n */\nexport const PANEL_BORDER_BOTTOM = {\n borderStyle: 'single',\n borderTop: false,\n borderBottom: true,\n borderLeft: false,\n borderRight: false,\n} as const\n\n/**\n * DIALOG_BORDER — 所有弹层/对话框的统一边框形态(四边圆角 `╭─╮`)。\n *\n * **单一真源**:所有经 DialogFrame 呈现的弹层写此常量。历史上曾有 `BORDER_VARIANTS`\n * 三档(hard/nav/overlay)按界面层级分不同形态(RFC-183 D1),但 2026-07-22 commit\n * 9466cb7c9「统一所有弹层为四边圆角」有意将三档收敛为同一形态;RFC-266 D1 据此删除\n * 无差异的 variant API,收敛为本单一常量(语义分层由 focus-registry band 承载,边框层\n * 不再重复编码)。将来若需按层级再分化边框,以新 RFC 重新设计,不保留投机性 API。\n */\nexport const DIALOG_BORDER = {\n borderStyle: 'round',\n borderTop: true,\n borderBottom: true,\n borderLeft: true,\n borderRight: true,\n} as const\n","/**\n * spacing.ts — 间距 scale 与布局裕量常量。\n */\n\n/**\n * SPACING — 间距 scale(对标 Radix Themes 9 级 spacing,缩减为终端字符网格 4 级)。\n *\n * **单一真源**:全部 margin/padding/gap 系 Ink Box props 引用本常量,不再写裸数字。\n */\nexport const SPACING = {\n none: 0,\n tight: 1,\n section: 2,\n loose: 4,\n} as const\n\n/**\n * LAYOUT — 宽度裕量常量。\n */\nexport const LAYOUT = {\n /** 通用内容裕量——最常见的 `columns - 4` 场景。 */\n contentInset: 4,\n /** 边框内裕量——已在 DialogFrame/PANEL_BORDER 内部的场景(`columns - 2`)。 */\n borderInset: 2,\n} as const\n","/**\n * dark.ts — Dark 主题预设(One-Dark 变体,RFC-263)。\n *\n * 与 `colors.ts` 的静态 `THEME` 同源(终局 review F-t2:此前两处逐行手抄重复,\n * 改色须记得改两处——别名化后 `THEME` 是唯一真源,DARK_THEME 仅是 Context 语义视图)。\n */\nimport { THEME } from '../colors'\nimport type { ThemeColors } from '../colors'\n\nexport const DARK_THEME: ThemeColors = THEME\n","/**\n * light.ts — Light 主题预设(RFC-263)。\n * 浅色背景下的对比度校准(WCAG AA ≥4.5:1)。\n */\nimport { PALETTE } from '../colors'\nimport type { ThemeColors } from '../colors'\n\nexport const LIGHT_THEME: ThemeColors = {\n brand: PALETTE.brandNavy,\n brandShimmer: PALETTE.navyBright,\n accent: PALETTE.brandNavy,\n accentDim: PALETTE.navyDim,\n accentBright: PALETTE.navyBright,\n text: undefined,\n inactive: '#6B7280',\n secondary: '#374151',\n subtle: '#6B7280',\n replyPrefix: '#5B21B6',\n success: '#16A34A',\n error: '#DC2626',\n warning: '#CA8A04',\n special: '#9333EA',\n suggestion: '#2563EB',\n permission: '#2563EB',\n codeText: '#0891B2',\n diffAdd: '#16A34A',\n diffRemove: '#DC2626',\n diffAddBg: '#DCFCE7',\n diffRemoveBg: '#FEE2E2',\n panelBorder: '#D1D5DB',\n overlayBg: '#F9FAFB',\n tagBg: '#E5E7EB',\n toolText: '#374151',\n focusBorder: '#2563EB',\n selection: PALETTE.brandNavy, // RFC-273 M2:选中色统一为品牌色(light 用藏青 accent,原 #2563EB)\n}\n","import type { ThemeColors } from './colors'\nimport { DARK_THEME, LIGHT_THEME } from './presets'\n\nexport type ThemeAppearance = 'dark' | 'light'\ntype ThemeResolveMode = ThemeAppearance | 'system'\n\nexport type ThemePresetSource =\n | { kind: 'builtin' }\n | { kind: 'extension'; extensionId: string }\n\nexport interface MarkdownThemeOverrides {\n heading1?: keyof ThemeColors\n heading2?: keyof ThemeColors\n headingWeak?: keyof ThemeColors\n listMarker?: keyof ThemeColors\n listMarkerMuted?: keyof ThemeColors\n quoteBar?: keyof ThemeColors\n quoteText?: keyof ThemeColors\n link?: keyof ThemeColors\n inlineCode?: keyof ThemeColors\n codeFence?: keyof ThemeColors\n tableHeader?: keyof ThemeColors\n tableDivider?: keyof ThemeColors\n listIndent?: 2 | 3\n listMarkers?: readonly [string, string, string, string]\n codeBlockDivider?: 'hr' | 'none'\n}\n\nexport interface ThemePresetInput {\n /** Builtin may pass id directly; extension ids are generated as `${extensionId}/${localId}`. */\n id?: string\n localId?: string\n label: string\n appearance: ThemeAppearance\n colors: ThemeColors\n markdown?: MarkdownThemeOverrides\n meta?: {\n author?: string\n description?: string\n version?: string\n }\n}\n\nexport interface ThemePreset extends Omit<ThemePresetInput, 'id'> {\n id: string\n source: ThemePresetSource\n}\n\nexport interface ThemePresetSelection {\n dark?: string\n light?: string\n}\n\nfunction sourceKey(source: ThemePresetSource): string {\n return source.kind === 'builtin' ? 'builtin' : `extension:${source.extensionId}`\n}\n\nfunction isKebabId(id: string): boolean {\n return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)\n}\n\nfunction effectivePresetId(source: ThemePresetSource, input: ThemePresetInput): string {\n if (source.kind === 'builtin') {\n const id = input.id ?? input.localId\n if (!id) throw new Error('Builtin theme preset requires id')\n return id\n }\n const localId = input.localId ?? input.id\n if (!localId) throw new Error('Extension theme preset requires localId')\n if (!isKebabId(localId)) throw new Error(`Invalid extension theme preset localId: ${localId}`)\n return `${source.extensionId}/${localId}`\n}\n\nexport class ThemeRegistry {\n private readonly presets = new Map<string, ThemePreset>()\n private readonly presetSourceKeys = new Map<string, string>()\n\n register(source: ThemePresetSource, input: ThemePresetInput): ThemePreset {\n const id = effectivePresetId(source, input)\n const existing = this.presets.get(id)\n if (existing) {\n const isBuiltin = existing.source.kind === 'builtin' || source.kind === 'builtin'\n throw new Error(isBuiltin ? `Cannot override builtin theme preset: ${id}` : `Duplicate theme preset id: ${id}`)\n }\n const preset: ThemePreset = {\n ...input,\n id,\n source,\n }\n this.presets.set(id, preset)\n this.presetSourceKeys.set(id, sourceKey(source))\n return preset\n }\n\n unregisterBySource(source: ThemePresetSource): number {\n if (source.kind === 'builtin') return 0\n const key = sourceKey(source)\n let removed = 0\n for (const [id, presetSourceKey] of [...this.presetSourceKeys]) {\n if (presetSourceKey !== key) continue\n this.presetSourceKeys.delete(id)\n if (this.presets.delete(id)) removed += 1\n }\n return removed\n }\n\n get(id: string): ThemePreset | undefined {\n return this.presets.get(id)\n }\n\n list(): ThemePreset[] {\n return [...this.presets.values()]\n }\n\n resolve(mode: ThemeResolveMode, systemAppearance?: ThemeAppearance, selection?: ThemePresetSelection): ThemePreset {\n const appearance: ThemeAppearance = mode === 'system' ? (systemAppearance ?? 'dark') : mode\n const selectedId = selection?.[appearance] ?? appearance\n const selected = this.presets.get(selectedId)\n if (selected && selected.appearance === appearance) return selected\n\n const fallback = this.presets.get(appearance)\n if (!fallback) throw new Error(`Missing builtin theme preset fallback for ${appearance}`)\n return fallback\n }\n}\n\nexport function createBuiltinThemeRegistry(): ThemeRegistry {\n const registry = new ThemeRegistry()\n registry.register({ kind: 'builtin' }, { id: 'dark', label: 'Dark', appearance: 'dark', colors: DARK_THEME })\n registry.register({ kind: 'builtin' }, { id: 'light', label: 'Light', appearance: 'light', colors: LIGHT_THEME })\n return registry\n}\n","/**\n * theme-ref.ts — 全局主题引用(供 React 树外的 bootstrap 层使用)。\n *\n * RFC-283:删除 setTheme(mode 选择层已移除)。仅保留:\n * - setPresetSelection:/preference theme 的 preset 预览\n * - setSystemTheme:RFC-282 的 OSC 11 运行时跟随\n */\nimport type { ThemePresetSelection } from './theme-registry'\n\nexport interface ThemeRef {\n setPresetSelection: (selection: ThemePresetSelection | undefined) => void\n setSystemTheme: (appearance: 'dark' | 'light') => void\n}\n\nlet themeRef: ThemeRef | null = null\n\nexport function setThemeRef(ref: ThemeRef | null): void {\n themeRef = ref\n}\n\nexport function getThemeRef(): ThemeRef | null {\n return themeRef\n}\n","/**\n * ThemeProvider.tsx — React Context 主题分发(RFC-263 → RFC-283 简化)。\n *\n * RFC-283:删除 dark/light/system 三态选择,始终根据终端背景色(OSC 11 探测)\n * 自动决定深浅。用户不再能手动选深浅——选错了会导致文字不可读。\n * 仍保留 preset 选择层(RFC-277):用户可在自动探测到的深/浅色下选不同配色方案。\n */\nimport React, { createContext, useContext, useEffect, useMemo, useState } from 'react'\nimport { DARK_THEME } from './presets'\nimport { createBuiltinThemeRegistry, type ThemePresetSelection, type ThemeRegistry } from './theme-registry'\nimport { setThemeRef } from './theme-ref'\nimport type { ThemeColors } from './colors'\n\nexport interface ThemeContextValue {\n /** 实际生效的深浅(从终端背景色派生,非用户选择) */\n resolvedTheme: 'dark' | 'light'\n resolvedAppearance?: 'dark' | 'light'\n presetId?: string\n colors: ThemeColors\n /** RFC-282:运行时 OSC 11 更新终端背景色 */\n setSystemTheme: (appearance: 'dark' | 'light') => void\n /** RFC-277:preset 选择(与深浅正交) */\n setPresetSelection: (selection: ThemePresetSelection | undefined) => void\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({\n resolvedTheme: 'dark',\n resolvedAppearance: 'dark',\n presetId: 'dark',\n colors: DARK_THEME,\n setSystemTheme: () => {},\n setPresetSelection: () => {},\n})\n\nexport interface ThemeProviderProps {\n children: React.ReactNode\n /** 终端主题检测结果(OSC 11 探测,始终提供;undefined = 未检测到 → fallback dark) */\n systemTheme?: 'dark' | 'light'\n themeRegistry?: ThemeRegistry\n presetSelection?: ThemePresetSelection\n}\n\nexport function ThemeProvider({\n children,\n systemTheme: systemThemeProp,\n themeRegistry,\n presetSelection: initialPresetSelection,\n}: ThemeProviderProps): React.ReactElement {\n // RFC-282:systemTheme 是可变 state——运行时 OSC 11 重探时经 setSystemTheme 桥接更新。\n // RFC-283:systemTheme 是深浅的唯一驱动源(不再有 ThemeMode 选择层)。\n const [systemTheme, setSystemTheme] = useState<'dark' | 'light' | undefined>(systemThemeProp)\n const [presetSelection, setPresetSelectionState] = useState<ThemePresetSelection | undefined>(initialPresetSelection)\n\n useEffect(() => {\n setThemeRef({ setPresetSelection: setPresetSelectionState, setSystemTheme })\n return () => setThemeRef(null)\n }, [])\n\n const registry = useMemo(() => themeRegistry ?? createBuiltinThemeRegistry(), [themeRegistry])\n // RFC-283:始终用 systemTheme 驱动(不再有 mode 选择层)\n const preset = registry.resolve('system', systemTheme, presetSelection)\n const resolvedTheme = preset.appearance\n const resolvedAppearance = preset.appearance\n const colors = preset.colors\n\n const value = useMemo<ThemeContextValue>(\n () => ({ resolvedTheme, resolvedAppearance, presetId: preset.id, colors, setSystemTheme, setPresetSelection: setPresetSelectionState }),\n [resolvedTheme, resolvedAppearance, preset.id, colors],\n )\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n}\n\nexport function useTheme(): ThemeContextValue {\n return useContext(ThemeContext)\n}\n\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n","/**\n * stdin-encoding.ts — stdin 编码态归一化(Buffer 化 + 解码器复位)。\n *\n * 背景:Ink 在 `handleSetRawMode` 里对 stdin 调 `stdin.setEncoding('utf8')`\n * (ink/build/components/App.js),而 **该编码在 unmount 后不会被复位**——Node 的\n * `Readable` 没有\"清除 encoding\"的公开 API(`setEncoding(null)` 实测不生效,\n * `readableEncoding` 仍为 'utf8')。\n *\n * 于是任何\"先于主 TUI 用一次性 Ink render 弹窗\"(WorkspaceTrustPrompt /\n * ParentWorkspacePrompt)的流程结束后,process.stdin 会永久停留在 utf8 解码模式,\n * 后续所有 `on('data')` 消费方收到的是 **string 而非 Buffer**:\n * - `Buffer.concat([str])` → TypeError ERR_INVALID_ARG_TYPE(进程崩溃)\n * - `chunk[i] === 0x1b` → 恒 false(字节比较静默失效,如焦点过滤 / Ctrl+C 拦截)\n *\n * 因此所有 stdin 入口必须先过本函数归一化。\n *\n * 编码选择:string 来自 utf8 解码器,故用 'utf8' 反编码可**逐字节还原**原始输入\n * (latin1 会破坏多字节字符,如中文输入/粘贴)。\n */\nexport function toChunkBuffer(chunk: Buffer | string): Buffer {\n return Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf8')\n}\n\n/**\n * 把流的 readable 编码复位回 buffer 模式(`setEncoding` 的逆操作)。\n *\n * Node 未提供公开 API:`setEncoding(null)` / `setEncoding(undefined)` 都是 no-op\n * (实测 `readableEncoding` 仍为 'utf8'),`'buffer'` 会抛 ERR_UNKNOWN_ENCODING。\n * 唯一途径是清 `_readableState` 上的 decoder/encoding 两个字段。\n *\n * 调用点:一次性 Ink render(`WorkspaceTrustPrompt` / `ParentWorkspacePrompt`)unmount\n * 之后,把 Ink 留下的 utf8 解码态还给后续消费方。`toChunkBuffer` 是防御纵深,本函数是根治。\n */\nexport function resetStdinEncoding(stream: NodeJS.ReadStream = process.stdin): void {\n const state = (\n stream as unknown as {\n _readableState?: {\n decoder?: StringDecoderInternals | null\n encoding?: string | null\n }\n }\n )._readableState\n if (!state) return\n\n // 若解码器正扣着一个被切断的多字节字符的前缀字节(如 CJK 粘贴恰好跨 read 边界),\n // 直接丢弃 decoder 会静默吞掉这些字节。先取回再 unshift,保证零字节丢失。\n const held = salvagePartialBytes(state.decoder)\n\n state.decoder = null\n state.encoding = null\n\n if (held && held.length > 0) {\n try {\n stream.unshift(held)\n } catch {\n // 流已 ended/destroyed —— 此时无人再读,丢弃无影响。\n }\n }\n}\n\n/** StringDecoder 的内部残留字节字段(Node 未公开,但结构长期稳定)。 */\ninterface StringDecoderInternals {\n lastChar?: Buffer\n lastTotal?: number\n lastNeed?: number\n}\n\nfunction salvagePartialBytes(decoder: StringDecoderInternals | null | undefined): Buffer | null {\n if (!decoder?.lastNeed || !decoder.lastChar || typeof decoder.lastTotal !== 'number') return null\n const haveCount = decoder.lastTotal - decoder.lastNeed\n if (haveCount <= 0) return null\n return Buffer.from(decoder.lastChar.subarray(0, haveCount))\n}\n","/**\n * detect-terminal-theme.ts — OSC 11 终端背景色检测(RFC-263 M5)。\n *\n * 通过 OSC 11(报告背景色)查询终端当前配色方案:\n * - 发送 \\x1b]11;?\\x07\n * - 终端回复 \\x1b]11;rgb:RRRR/GGGG/BBBB\\x07\n * - 根据亮度判断 dark/light\n *\n * 支持:iTerm2, kitty, alacritty, WezTerm, GNOME Terminal, Windows Terminal\n * 不支持/超时:tmux(多层嵌套)、某些旧终端\n */\n\nimport { toChunkBuffer } from '../term/stdin-encoding'\n\nexport type DetectedTheme = 'dark' | 'light'\n\n/**\n * 检测终端背景主题。超时返回 undefined(无法检测 → 调用方用 fallback)。\n * @param timeoutMs 等待终端回复的超时时间(默认 200ms)\n */\nexport function detectTerminalTheme(timeoutMs = 200): Promise<DetectedTheme | undefined> {\n return new Promise((resolve) => {\n // 非 TTY(如 pipe/redirect)→ 无法检测\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n resolve(undefined)\n return\n }\n\n const timer = setTimeout(() => {\n cleanup()\n resolve(undefined)\n }, timeoutMs)\n\n const originalRawMode = process.stdin.isRaw\n process.stdin.setRawMode(true)\n process.stdin.resume()\n\n const chunks: Buffer[] = []\n\n // Ink 的一次性预渲染弹窗(信任/父 workspace)会把 process.stdin 永久置为 utf8 解码,\n // 此后 'data' 事件传的是 string —— 直接 Buffer.concat 会 throw(见 term/stdin-encoding.ts)。\n const onData = (data: Buffer | string) => {\n chunks.push(toChunkBuffer(data))\n const combined = Buffer.concat(chunks).toString()\n // 匹配 OSC 11 回复:\\x1b]11;rgb:RRRR/GGGG/BBBB\\x07 或 \\x1b]11;rgb:RRRR/GGGG/BBBB\\x1b\\\\\n const match = combined.match(/\\x1b\\]11;rgb:([0-9a-fA-F]+)\\/([0-9a-fA-F]+)\\/([0-9a-fA-F]+)/)\n if (match) {\n cleanup()\n const r = parseInt(match[1]!.slice(0, 2), 16)\n const g = parseInt(match[2]!.slice(0, 2), 16)\n const b = parseInt(match[3]!.slice(0, 2), 16)\n // 相对亮度公式 (ITU-R BT.709)\n const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b\n resolve(luminance < 128 ? 'dark' : 'light')\n }\n }\n\n const cleanup = () => {\n clearTimeout(timer)\n process.stdin.removeListener('data', onData)\n process.stdin.setRawMode(originalRawMode ?? false)\n process.stdin.pause()\n }\n\n process.stdin.on('data', onData)\n // 发送 OSC 11 查询\n process.stdout.write('\\x1b]11;?\\x07')\n })\n}\n","/**\n * keyhints.ts — 平台相关的按键提示标签(图标优先 + ctrl/command 按系统区分).\n *\n * 原则:\n * - 尽量用按键图标:Enter→↵、↑/↓,修饰键在 macOS 用原生符号 ⌃(control)/⌘(command)。\n * - ⌘ 只用于「终端真会下发、绑定也真吃」的组合——即提交补充键(useTextInput 接受 meta)。\n * ctrl+o/ctrl+r 等在终端走的是 Ctrl(⌘+字母 终端通常不转发),故 macOS 也用 ⌃ 而非 ⌘,\n * 避免提示了一个按了没反应的键。\n * - Windows/Linux 无 Command,统一回落 `Ctrl+X`。\n */\nexport type PlatformKind = 'macos' | 'windows' | 'linux'\n\nexport function currentPlatform(): PlatformKind {\n if (process.platform === 'darwin') return 'macos'\n if (process.platform === 'win32') return 'windows'\n return 'linux'\n}\n\n/** 跨平台一致的按键图标。 */\nexport const KEY = {\n enter: '↵',\n up: '↑',\n down: '↓',\n left: '←',\n right: '→',\n esc: 'Esc',\n tab: '⇥',\n} as const\n\n/**\n * Ctrl 系组合键标签:macOS=`⌃+X`,Windows/Linux=`Ctrl+X`。\n * 用于 ctrl+o/ctrl+r/ctrl+c 等真正走 Control 的绑定。\n */\nexport function ctrlChord(keyChar: string, p: PlatformKind = currentPlatform()): string {\n const k = keyChar.length === 1 ? keyChar.toUpperCase() : keyChar\n return p === 'macos' ? `⌃+${k}` : `Ctrl+${k}`\n}\n\n/**\n * 提交补充键标签:macOS=`⌘+↵`(终端下发 ⌘+Enter→meta),Windows/Linux=`Ctrl+↵`。\n * 这是唯一真正走 Command 的绑定。\n */\nexport function submitChordHint(p: PlatformKind = currentPlatform()): string {\n return p === 'macos' ? `⌘+${KEY.enter}` : `Ctrl+${KEY.enter}`\n}\n\n/**\n * followUp(排队下一轮)键标签:`⇧+↵`(Shift+Enter),全平台一致。\n * 用户需求改绑(原 Option+Enter):Option/⌥+Enter 在部分系统/终端环境里被系统级快捷键\n * 占用(如输入特殊字符),冲突比 Shift 更常见;stock 终端与 Ink 均可靠区分 shift+return\n * (useTextInput.ts 的 return 分支已识别 shift 修饰)。\n */\nexport function followUpChordHint(_p: PlatformKind = currentPlatform()): string {\n return `⇧+${KEY.enter}`\n}\n","/**\n * DialogFrame.tsx — 满高带框独立 dialog 外壳(RFC-031 呈现层 · 方案 A)\n *\n * 统一「列表里 Enter 一行 → 弹出该项独立 dialog」的视觉范式:\n * - 四边圆角灰线 `╭─╮` + overlayBg 碳底 → 与对话流明确隔开\n * - 顶部 header 行(调用方提供,可带颜色)+ body(调用方提供,照旧滚动)\n *\n * 当前消费方:EditedFileFullPane(编辑文件 diff)。同壳供 Subagents 详情等列表项 Enter 复用。\n * 注意:本壳只负责「框 + 底 + 内边距」,不接键盘——键盘仍由内容组件自己的 useInput / 全局键路由处理。\n * border 四边各占 1 行/列,故内容组件按可见高度算行数时须额外减 2(见 EditedFileFullPane visibleRows),\n * 宽度需额外扣 2 列(左右边框各 1 字符)。\n *\n * RFC-096 D3:`paddingY` 可选 prop,**默认 `SPACING.none`(不开)**——现有 ~19 个消费方里\n * 多个已经用手动 `<Box marginTop={SPACING.tight} />` 空盒子模拟 header→body 间距\n * (如 SelectDialog.tsx:210/233、ContextPane.tsx:40),若默认开启会与这些手动间距叠加造成\n * 双倍空白(真实视觉回归,超出 RFC-096 承诺范围)。故收窄为 opt-in:只有明确需要统一内边距、\n * 且不持有硬编码 chrome 行数预算的新消费方(如迁移中的风险弹窗)显式传 `paddingY={SPACING.tight}`。\n * 持有 chrome 预算的消费方(FileContentViewer/ShellFullOutputDialog/DelegatesPane)永远不传\n * (保持默认 none,行为不变)。\n */\nimport React from 'react'\nimport { Box, Text } from 'ink'\n\nimport { SPACING, DIALOG_BORDER, useColors } from '../theme'\n\ntype BoxBorderStyle = React.ComponentProps<typeof Box>['borderStyle']\n\nexport interface DialogFrameProps {\n /** 标题行(调用方提供,可含颜色文本) */\n header: React.ReactNode\n /** 主体内容(滚动区等,调用方提供) */\n children: React.ReactNode\n /** 底部快捷键提示(dimColor 自动应用,调用方只需提供内容) */\n footer?: React.ReactNode\n /**\n * 垂直内边距(上下各一行空白)。默认 `SPACING.none`(不开,保持现有 ~19 消费方零视觉变化)。\n * 持有硬编码 chrome 行数预算的消费方(FileContentViewer/ShellFullOutputDialog/DelegatesPane)\n * 不得传入——见上方类型注释。\n */\n paddingY?: number\n /**\n * 可选的**总帧高硬上限**(含框线/header/body/footer/marginTop 全部)。\n * 传入时 yoga `height` 截断(stock Ink inline 模式下 height 会裁剪溢出——这正是\n * RFC-019/RFC-049 已坐实的手段),超出行的内容被截掉。消费方负责把 body 行数预算\n * 与 maxHeight 对齐(通常 = maxHeight − 内部 chrome),否则看到的是硬切断而不是\n * 内部滚动。不传则零行为变化(默认自适应高度)。\n */\n maxHeight?: number\n /**\n * 单组件级 borderStyle 覆盖(不改变 DIALOG_BORDER 的四边框配置,仅替换角/线字符)。\n * 用于极个别需要偏离默认圆角的消费方(如 Toast 需要方角)。\n */\n borderStyle?: BoxBorderStyle\n}\n\nexport function DialogFrame({\n header,\n children,\n footer,\n paddingY = SPACING.none,\n maxHeight,\n borderStyle,\n}: DialogFrameProps): React.ReactElement {\n const colors = useColors()\n return (\n <Box\n {...DIALOG_BORDER}\n borderStyle={borderStyle ?? DIALOG_BORDER.borderStyle}\n borderColor={colors.panelBorder}\n backgroundColor={colors.overlayBg}\n paddingX={SPACING.tight}\n paddingY={paddingY}\n marginTop={SPACING.tight}\n maxHeight={maxHeight}\n flexDirection='column'\n >\n {header}\n {children}\n {footer && (\n <Box marginTop={SPACING.tight}>\n <Text dimColor>{footer}</Text>\n </Box>\n )}\n </Box>\n )\n}\n","/**\n * DialogTitle.tsx — 弹窗标题行统一组件(RFC-098 D1).\n *\n * 抽取自 12 处 DialogFrame header 插槽内逐字重复的\n * `<Text color={THEME.secondary} bold>{title}</Text>` 模式(ConfirmDialog/AlertDialog/\n * InfoDialog/InputDialog/QuestionsDialog/MemoryDialog/PasteConfirmDialog/SelectDialog/\n * PermissionDialog/BashPermissionDialog/PromptDialog/ShellFullOutputDialog)。\n *\n * 风险色标题场景(ForcedUpgradeOverlay/TrustPromptDialog 的 THEME.warning 标题)显式传 color。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\n\nexport interface DialogTitleProps {\n children: React.ReactNode\n /** 覆盖默认 THEME.secondary(如风险色标题)。默认 THEME.secondary。 */\n color?: string\n}\n\nexport function DialogTitle({ children, color }: DialogTitleProps): React.ReactElement {\n const colors = useColors()\n return (\n <Text color={color ?? colors.secondary} bold>\n {children}\n </Text>\n )\n}\n","/**\n * Kbd.tsx — 键位标签(RFC-269 增量,对标 web `<kbd>` / AntD Typography.Keyboard / Radix Kbd)。\n *\n * **键位\"视觉\"的单一真源**:把\"键位标签怎么画\"(当前=`secondary` 次级强调色)收进本组件,\n * 与\"键位标签叫什么\"(`KEY` 常量表 + `ctrlChord()`/`submitChordHint()`/`followUpChordHint()`\n * 跨平台构造,见 `input/keyhints.ts`)分离——前者归组件层、后者归常量/函数层。\n *\n * 消费方:\n * - `HintBar`(弹层 footer 的 `[键位, 动作]` 元组行,43 文件间接消费)——内部改用本组件\n * - 行内键位提示(如 SigilPane 底部的 `↵ 确认 Esc 取消`)\n *\n * **不能用 `bold` 实现强调**(HintBar 头注释记载的实测教训):`dimColor`/`bold` 共用 ANSI\n * 强度通道(`\\x1b[2m`/`\\x1b[1m`),关闭时统一复位为 `\\x1b[22m`,会把外层 `dimColor` 一并\n * 提前复位,导致键位之后的文案不再变暗。故用 `color`(独立 SGR 色彩通道)做强调——\n * 这正是本组件封装的价值:该教训固化在一处,消费方不必重新踩。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\n\nexport interface KbdProps {\n /** 键位文本(来自 `KEY.*` 或 `ctrlChord()`/`submitChordHint()`/`followUpChordHint()`)。 */\n children?: React.ReactNode\n}\n\nexport function Kbd({ children }: KbdProps): React.ReactElement {\n const colors = useColors()\n // 色彩通道强调(非强度通道)——见头注释的 \\x1b[22m 教训。\n return <Text color={colors.secondary}>{children}</Text>\n}\n","/**\n * HintBar.tsx — 弹窗 footer 操作提示行统一组件(RFC-098 D3,键位高亮 RFC-098 D5).\n *\n * 抽取自约 9 处手写 `KEY` + `STR` 片段拼接的 footer 模式(如\n * `← → {STR.hint.select} · enter {STR.hint.confirm} · y/n/a {STR.hint.shortcut}`)。\n * 分隔符统一为 `·`(此前 AlertDialog 用 `/`,已改回)。\n *\n * 组件本身不内置任何文案字符串——调用方传入 `[键位标签, 动作文案]` 元组数组,\n * i18n 单一真源仍在 STR 对象,HintBar 只负责拼装格式。\n *\n * **键位高亮(真实用户反馈修复)**:此前整行统一 `dimColor`,键位标签(`Esc`/`↑↓/g/G`)\n * 与动作文案(`close`/`scroll`)视觉权重完全相同,用户扫读时难以第一时间定位\"按什么键\"。\n * 现在键位标签比动作文案更亮——但**不能用 `bold` 实现**(见下方技术注意事项),改用\n * `colors.secondary`(对话框标题同款次级强调色,非新增色彩语义)给键位标签上色,动作文案\n * 与分隔符 `·` 维持 `dimColor`。\n *\n * **技术注意事项(曾用 `bold` 实现,实测 ANSI 输出后放弃)**:本组件绝大多数消费方经\n * `DialogFrame` 的 `footer` 插槽渲染,`DialogFrame` 会用外层 `<Text dimColor>{footer}</Text>`\n * 整体包裹——`dimColor`/`bold` 底层都是\"强度\"通道 SGR 码(`\\x1b[2m`/`\\x1b[1m`),\n * 关闭时统一复位为 `\\x1b[22m`(reset intensity,ECMA-48 规范,同时清除 bold 与 dim 两者)。\n * 若键位标签用 `bold`(不叠 dimColor),实测发现该标签结束处的 `\\x1b[22m` 会把外层 dim\n * 也提前复位,导致标签之后的动作文案不再被\"暗\"——整行的暗/亮对比在中途被打断而非仅键位\n * 本身变亮。改用 `color`(独立于强度通道的 SGR 30-37/38/39 码组)后,键位标签的\n * 颜色开关只影响自己的色彩段,不触碰强度通道,外层 dim 从行首到行尾保持完整闭合\n * (已用 `ink-testing-library` 实测 ANSI 输出验证两种写法差异,未来若改动本组件样式,\n * 务必同样实测渲染后的原始 ANSI 字符串,不能只凭观感判断)。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { Kbd } from '../components/Kbd'\n\nexport interface HintBarProps {\n /** 每项一个 [键位标签, 动作文案] 元组。 */\n items: Array<[string, string]>\n /** 整行暗色(内联面板无 DialogFrame 外层 dimColor 包裹时用)。 */\n dim?: boolean\n}\n\nexport function HintBar({ items, dim = false }: HintBarProps): React.ReactElement {\n return (\n <Text dimColor={dim || undefined}>\n {items.map(([k, v], i) => (\n <Text key={`${k}-${i}`}>\n {i > 0 ? ' · ' : ''}\n <Kbd>{k}</Kbd>\n {` ${v}`}\n </Text>\n ))}\n </Text>\n )\n}\n","/**\n * Tabable.tsx — 焦点系统核心原语 + 自动聚焦样式注入。\n *\n * 对照浏览器:\n * 浏览器 :focus { outline } → Tabable 自动加聚焦视觉(▶/背景/边框/光标)\n * 浏览器 :focus-visible → 只在键盘导航时显示(Tab 触发,点击不触发)\n * outline: none + 自定义 → focusStyle=\"none\" + useTabableContext 自绘\n *\n * focusStyle 变体(对齐全仓已验证的聚焦视觉规范):\n * 'pointer' — 左侧 ▶ + THEME.selection 蓝 + bold(默认,列表选项标准)\n * 'highlight' — 整行 THEME.selection 蓝 + bold(无 ▶,紧凑行)\n * 'background' — 背景填充 + contrastText 反色(Button 标准)\n * 'border' — 边框色变为 THEME.selection(容器/面板)\n * 'cursor' — 光标可见指示(Input 标准)\n * 'none' — 不注入样式(组件通过 useTabableContext 自绘)\n */\nimport React, { createContext, useContext, useEffect, useRef, useMemo } from 'react'\nimport { Box, Text } from 'ink'\nimport { FIGURES, useColors } from \"../theme\"\n\n// ═══════════════════ 类型 ═══════════════════\n\nexport type FocusStyle = 'pointer' | 'highlight' | 'background' | 'border' | 'cursor' | 'none'\n\n// ═══════════════════ TabableContext ═══════════════════\n\nexport interface TabableContextValue {\n /** 是否拥有焦点 */\n focused: boolean\n /** 注册 ID */\n id: string\n /** 是否 disabled */\n disabled: boolean\n /** 当前聚焦样式 */\n focusStyle: FocusStyle\n}\n\nconst TabableContext = createContext<TabableContextValue>({\n focused: false,\n id: '',\n disabled: false,\n focusStyle: 'pointer',\n})\n\n/**\n * 子组件读取最近 <Tabable> 的聚焦状态。\n * focusStyle='none' 时组件自己根据 focused 自绘。\n */\nexport function useTabableContext(): TabableContextValue {\n return useContext(TabableContext)\n}\n\n// ═══════════════════ 焦点样式常量 ═══════════════════\n\n/** 聚焦背景色(Button background 变体用) */\nconst FOCUS_BG = '#4D7CFF'\n/** 背景上的对比文字色 */\nconst FOCUS_BG_TEXT = '#FFFFFF'\n/** 聚焦指示符 */\nconst POINTER = FIGURES.pointer\n\n// ═══════════════════ Registry ═══════════════════\n\nexport interface TabableEntry {\n id: string\n tabIndex: number\n disabled: boolean\n order: number\n routePath: string\n autoFocus: boolean\n /** Enter 拦截器。返回 true = 阻止默认穿透。 */\n onAction?: () => boolean | void\n /** Esc 拦截器。返回 true = 阻止默认 goBack。 */\n onEscape?: () => boolean | void\n /** Tab 拦截器。返回 true = 阻止默认焦点切换。 */\n onTab?: (shift: boolean) => boolean | void\n}\n\nlet orderCounter = 0\n\nexport class TabableRegistry {\n private entries = new Map<string, TabableEntry>()\n private focusMemory = new Map<string, string>()\n\n register(entry: TabableEntry): void {\n this.entries.set(entry.id, entry)\n }\n\n unregister(id: string): void {\n this.entries.delete(id)\n }\n\n getTabable(routePath: string): TabableEntry[] {\n return [...this.entries.values()]\n .filter((e) => e.routePath === routePath && e.tabIndex >= 0 && !e.disabled)\n .sort((a, b) => a.tabIndex - b.tabIndex || a.order - b.order)\n }\n\n getInitialFocus(routePath: string): string | null {\n const remembered = this.focusMemory.get(routePath)\n if (remembered && this.entries.has(remembered) && !this.entries.get(remembered)!.disabled) {\n return remembered\n }\n const all = [...this.entries.values()].filter((e) => e.routePath === routePath && !e.disabled)\n const af = all.find((e) => e.autoFocus)\n if (af) return af.id\n const tabbable = this.getTabable(routePath)\n return tabbable[0]?.id ?? null\n }\n\n next(currentId: string, routePath: string): string | null {\n const list = this.getTabable(routePath)\n if (list.length === 0) return null\n const idx = list.findIndex((e) => e.id === currentId)\n return list[(idx + 1) % list.length]?.id ?? null\n }\n\n prev(currentId: string, routePath: string): string | null {\n const list = this.getTabable(routePath)\n if (list.length === 0) return null\n const idx = list.findIndex((e) => e.id === currentId)\n return list[(idx - 1 + list.length) % list.length]?.id ?? null\n }\n\n rememberFocus(routePath: string, id: string): void {\n this.focusMemory.set(routePath, id)\n }\n\n get(id: string): TabableEntry | undefined {\n return this.entries.get(id)\n }\n\n static nextOrder(): number {\n return orderCounter++\n }\n}\n\n// ═══════════════════ RouteContext ═══════════════════\n\nexport interface RouteContextValue {\n routePath: string\n currentPath: string\n activeId: string | null\n registry: TabableRegistry\n onFocusFallback?: (nextId: string | null) => void\n /** true when inside a real <Router> provider (vs default context) */\n hasRouter?: boolean\n}\n\nexport const RouteContext = createContext<RouteContextValue>({\n routePath: '/',\n currentPath: '/',\n activeId: null,\n registry: new TabableRegistry(),\n hasRouter: false,\n})\n\n// ═══════════════════ useTabable hook ═══════════════════\n\nexport interface UseTabableOptions {\n tabIndex?: number\n disabled?: boolean\n autoFocus?: boolean\n /** Enter 拦截器。返回 true = 阻止默认穿透。 */\n onAction?: () => boolean | void\n /** Esc 拦截器。返回 true = 阻止默认 goBack。 */\n onEscape?: () => boolean | void\n /** Tab 拦截器。返回 true = 阻止默认焦点切换。 */\n onTab?: (shift: boolean) => boolean | void\n id?: string\n}\n\nlet autoIdCounter = 0\n\nexport function useTabable(opts: UseTabableOptions = {}): { focused: boolean; id: string } {\n const {\n tabIndex = 0,\n disabled = false,\n autoFocus = false,\n onAction,\n onEscape,\n onTab,\n id,\n } = opts\n\n const stableId = useRef(id ?? `tabable-${++autoIdCounter}`).current\n const routeCtx = useContext(RouteContext)\n const focused = routeCtx.activeId === stableId && !disabled\n\n const onActionRef = useRef(onAction)\n onActionRef.current = onAction\n const onEscapeRef = useRef(onEscape)\n onEscapeRef.current = onEscape\n const onTabRef = useRef(onTab)\n onTabRef.current = onTab\n /**\n * routeCtx 的**实时**引用——供 effect cleanup 读取,不可用闭包捕获的那个。\n *\n * 事故形态(实测复现):`routeCtx` 是 Router 里的 `useMemo`,`activeId` 一变就是新对象;\n * 而下面 effect 的依赖数组**不含 activeId**(含了会导致每次焦点移动都重新 register/\n * unregister,抖动且丢 order)。于是 cleanup 闭包里的 `routeCtx` 停留在**最后一次\n * effect 运行时**的快照:\n * ① 组件挂载,此时 activeId=其他元素 → 捕获 routeCtx(activeId=其他)\n * ② 用户 Tab 聚焦到本组件 → activeId=本组件,但 effect 不重跑\n * ③ 本组件卸载 → cleanup 读到陈旧的 activeId ≠ stableId → **跳过焦点回落**\n * 结果 activeId 悬空指向已卸载元素,`dispatchIntercept` 用它查 registry 得 undefined\n * 直接 return false —— 表现为 Enter/Esc/Tab 全部失灵,且无任何报错。\n *\n * 这与 RouterRef 的陈旧快照是同一类事故(见 router-ref.ts 与 Router 的 stackRef):\n * **任何\"注册后延迟执行\"的闭包,读取型字段都必须经 ref 实时取,不能绑渲染期快照**。\n */\n const routeCtxRef = useRef(routeCtx)\n routeCtxRef.current = routeCtx\n\n useEffect(() => {\n const entry: TabableEntry = {\n id: stableId,\n tabIndex,\n disabled,\n order: TabableRegistry.nextOrder(),\n routePath: routeCtx.routePath,\n autoFocus,\n get onAction() { return onActionRef.current },\n get onEscape() { return onEscapeRef.current },\n get onTab() { return onTabRef.current },\n }\n routeCtx.registry.register(entry)\n return () => {\n // 全程经 routeCtxRef.current 读**当前**值——闭包捕获的 routeCtx 可能已陈旧\n // (见 routeCtxRef 注释:activeId 变化不会重跑本 effect)。\n const ctx = routeCtxRef.current\n ctx.registry.unregister(stableId)\n if (ctx.activeId === stableId) {\n const next = ctx.registry.getInitialFocus(ctx.routePath)\n Promise.resolve().then(() => {\n // 微任务里再取一次:卸载到微任务之间可能又有焦点变更。\n const latest = routeCtxRef.current\n if (latest.activeId === stableId) {\n latest.onFocusFallback?.(next)\n }\n })\n }\n }\n }, [stableId, tabIndex, disabled, routeCtx.routePath, autoFocus])\n\n return { focused, id: stableId }\n}\n\n// ═══════════════════ <Tabable> 组件 + 自动聚焦样式 ═══════════════════\n\nexport interface TabableProps {\n tabIndex?: number\n disabled?: boolean\n autoFocus?: boolean\n onAction?: () => boolean | void\n onEscape?: () => boolean | void\n onTab?: (shift: boolean) => boolean | void\n id?: string\n /** 聚焦时的视觉样式。默认 'pointer'(▶ + 蓝色 + bold) */\n focusStyle?: FocusStyle\n children: React.ReactNode\n}\n\nexport function Tabable({\n tabIndex = 0,\n disabled = false,\n autoFocus = false,\n onAction,\n onEscape,\n id,\n focusStyle = 'pointer',\n children,\n}: TabableProps): React.ReactElement {\n const { focused, id: registeredId } = useTabable({\n tabIndex,\n disabled,\n autoFocus,\n onAction,\n onEscape,\n id,\n })\n\n const ctxValue = useMemo<TabableContextValue>(\n () => ({ focused, id: registeredId, disabled, focusStyle }),\n [focused, registeredId, disabled, focusStyle],\n )\n\n return (\n <TabableContext.Provider value={ctxValue}>\n <FocusStyleWrapper focused={focused} disabled={disabled} style={focusStyle}>\n {children}\n </FocusStyleWrapper>\n </TabableContext.Provider>\n )\n}\n\n// ═══════════════════ FocusStyleWrapper(自动注入聚焦视觉) ═══════════════════\n\nfunction FocusStyleWrapper({ focused, disabled, style, children }: {\n focused: boolean\n disabled: boolean\n style: FocusStyle\n children: React.ReactNode\n}): React.ReactElement {\n const colors = useColors()\n if (style === 'none' || disabled) {\n return <>{children}</>\n }\n\n switch (style) {\n case 'pointer':\n return (\n <Box flexDirection='row'>\n <Text color={focused ? colors.selection : colors.inactive} bold={focused}>\n {focused ? `${POINTER} ` : ' '}\n </Text>\n <Box flexDirection='column'>\n <Text color={focused ? colors.selection : undefined} bold={focused}>\n {children}\n </Text>\n </Box>\n </Box>\n )\n\n case 'highlight':\n return (\n <Text color={focused ? colors.selection : undefined} bold={focused}>\n {children}\n </Text>\n )\n\n case 'background':\n return focused ? (\n <Text backgroundColor={FOCUS_BG} color={FOCUS_BG_TEXT} bold>\n {` `}{children}{` `}\n </Text>\n ) : (\n <Text color={colors.inactive}>\n {`[ `}{children}{` ]`}\n </Text>\n )\n\n case 'border':\n return (\n <Box\n borderStyle='single'\n borderColor={focused ? colors.selection : colors.inactive}\n >\n {children}\n </Box>\n )\n\n case 'cursor':\n // Input 组件自己通过 useTabableContext().focused 控制光标显示/闪烁\n // wrapper 层不加任何装饰——Input 的光标是可编辑光标,不是装饰性闪烁块\n return <>{children}</>\n\n default:\n return <>{children}</>\n }\n}\n\n","/**\n * FocusBus.tsx — 统一键盘派发(浏览器事件冒泡模型的 TUI 版)。\n *\n * 对照浏览器:\n * document.activeElement ← FocusBus 只调聚焦元素的 handler\n * keydown → activeElement → 冒泡 ← dispatch 按优先级链,handler 返回 true = stopPropagation\n * element.focus() ← router.focus(id)\n *\n * 优先级:\n * 100 聚焦元素(useTabableInput 注册)\n * 50 Router(Tab/Esc 焦点导航)\n * 10 外层兜底(WizardDialog goBack 等)\n *\n * 用法:\n * // App 层(唯一 useInput)\n * function App() {\n * const { focusBus } = useContext(FocusBusContext)\n * useInput((input, key) => focusBus.dispatch(input, key))\n * }\n *\n * // 组件内部\n * function SearchInput() {\n * const { focused } = useTabable({ tabIndex: 0 })\n * useTabableInput(focused, (input, key) => {\n * if (key.upArrow) return true // 吞掉,不冒泡\n * // ... 处理按键\n * return true\n * })\n * }\n */\nimport React, { createContext, useContext, useEffect, useRef } from 'react'\nimport { useInput, type Key } from 'ink'\nimport { RouteContext } from './Tabable'\n\n// ═══════════════════ 类型 ═══════════════════\n\nexport type KeyHandler = (input: string, key: Key) => boolean | void\n\ninterface FocusBusEntry {\n id: string\n handler: KeyHandler\n priority: number\n}\n\n// ═══════════════════ FocusBus ═══════════════════\n\nexport class FocusBus {\n private entries: FocusBusEntry[] = []\n\n /** 注册 handler,返回注销函数 */\n register(id: string, handler: KeyHandler, priority: number): () => void {\n const entry = { id, handler, priority }\n this.entries.push(entry)\n // 高优先级在前(优先级数字大的先执行)\n this.entries.sort((a, b) => b.priority - a.priority)\n // 注销按 entry 引用移除,不按 id——多个组件共享同一 id(如 4 个 FileContentViewer\n // 消费方都用 'file-content-viewer')时,按 id 过滤会把同名的存活 handler 一起删掉,\n // 表现为\"另一个查看器的 Esc 突然失效\"(本次 paste 查看面板 Esc 关不掉的根因)。\n return () => {\n this.entries = this.entries.filter((e) => e !== entry)\n }\n }\n\n /** 按优先级派发。handler 返回 true = 已消费;false = 冒泡。 */\n dispatch(input: string, key: Key): boolean {\n for (const entry of this.entries) {\n if (entry.handler(input, key)) return true\n }\n return false\n }\n\n /** 调试用:列出当前注册的 handler */\n debug(): string[] {\n return this.entries.map((e) => `[${e.priority}] ${e.id}`)\n }\n}\n\n// ═══════════════════ Context ═══════════════════\n\nexport const PRIORITY_GLOBAL = 1000\n/**\n * 格子选区层(RFC-315)——**高于焦点、低于全局**。\n *\n * 为何自成一层而非复用 FOCUSED/FALLBACK:选区不属于焦点模型,也不属于任何路由。\n * 它由 `SelectionController` 直写 stdout 绘制(React 树之外、Router 之外),在视觉上\n * 恒是**最顶层**——盖在当前路由、弹层、面板之上。既然它画在最上面,取消它的键就该\n * 排在这些层之前,否则会出现「ESC 关掉了下面的弹层,而盖在上面的反显还留着」。\n *\n * 为何不放 PRIORITY_GLOBAL(1000):RFC-296 I2 规定该层只准放「任何状态下都必须响应的\n * 逃生/中断通道」(当前仅 Ctrl+C),判据是「若 handler 需靠某数据是否非空来决定要不要\n * 消费,它就不是全局键」。选区 ESC 恰以 `hasSelection()` 为门,按定义不合格,\n * 且有架构门禁 `tests/global-key-layer-purity.test.ts` 强制。\n *\n * ⚠️ 本层只准放选区取消一项。它的消费**必须**以「确实有选区」为前提——无选区时\n * 立刻返回 false 放行,否则会静默吞掉中断/关弹层的 ESC(回归门见\n * `tests/selection-escape.test.ts`)。\n */\nexport const PRIORITY_SELECTION = 200\nexport const PRIORITY_FOCUSED = 100\n/**\n * Router 的**拦截器分派**段(RFC-281 D1)——代理聚焦元素的 onTab/onEscape/onAction,\n * 对标浏览器 event target 的 listener,必须高于组件兜底层。沿用原 PRIORITY_ROUTER 数值 50,\n * 保证与 FOCUSED/FALLBACK 的既有相对关系不变。\n */\nexport const PRIORITY_ROUTER_INTERCEPT = 50\n/** @deprecated 语义已拆分(RFC-281 D1)。保留为 PRIORITY_ROUTER_INTERCEPT 别名以兼容既有 import。 */\nexport const PRIORITY_ROUTER = PRIORITY_ROUTER_INTERCEPT\nexport const PRIORITY_FALLBACK = 10\n/**\n * Router 的**默认导航**段(RFC-281 D1)——Esc→goBack、Tab→路由内焦点循环。\n * 这是\"无人处理时的兜底\",对标浏览器 default action:必须排在所有具体 handler **之后**,\n * 组件在 FALLBACK 层 `return true` 即可阻止它(等价 preventDefault)。\n *\n * 取值 5:严格低于 FALLBACK(10) 是本设计的目的;严格高于 APP_FALLBACK(0) 是为保留\n * Tab 两级语义(Router 路由内循环 → 穿透到 app 级面板循环,RFC-265 场景 1/2)。\n */\nexport const PRIORITY_ROUTER_DEFAULT = 5\nexport const PRIORITY_APP_FALLBACK = 0\n\nexport interface FocusBusContextValue {\n focusBus: FocusBus\n}\n\nconst FocusBusContext = createContext<FocusBusContextValue | null>(null)\n\nexport function FocusBusProvider({ children }: { children: React.ReactNode }): React.ReactElement {\n const focusBus = React.useMemo(() => new FocusBus(), [])\n return (\n <FocusBusContext.Provider value={{ focusBus }}>\n {children}\n </FocusBusContext.Provider>\n )\n}\n\nexport function useFocusBus(): FocusBus {\n const ctx = useContext(FocusBusContext)\n if (!ctx) throw new Error('useFocusBus() must be used inside <FocusBusProvider>')\n return ctx.focusBus\n}\n\nfunction useOptionalFocusBus(): FocusBus | null {\n return useContext(FocusBusContext)?.focusBus ?? null\n}\n\n// ═══════════════════ useTabableInput ═══════════════════\n\n/**\n * 组件注册键盘 handler——只有 focused=true 时向 FocusBus 注册。\n *\n * 对照浏览器:只有 activeElement 才收到 keydown。\n *\n * handler 返回 true = 已消费(stopPropagation)。\n * handler 返回 false = 不消费(冒泡到下一优先级)。\n *\n * 用法:\n * const { focused } = useTabable({ tabIndex: 0 })\n * useTabableInput(focused, (input, key) => {\n * if (key.return) { submit(); return true }\n * if (key.upArrow) return true // 吞掉\n * return false // 冒泡\n * })\n */\nexport function useTabableInput(\n focused: boolean,\n handler: KeyHandler,\n id?: string,\n): void {\n const focusBus = useOptionalFocusBus()\n const handlerRef = useRef(handler)\n handlerRef.current = handler\n const stableId = useRef(id ?? `tabable-input-${++inputIdCounter}`).current\n\n // 独立渲染场景(Provider 外)回退到 Ink useInput,避免 standalone dialogs 崩溃。\n useInput((input, key) => {\n if (!focusBus && focused) handlerRef.current(input, key)\n }, { isActive: !focusBus && focused })\n\n useEffect(() => {\n if (!focusBus || !focused) return // 不聚焦不注册——等价浏览器 inactiveElement 不收 keydown\n return focusBus.register(stableId, (i, k) => handlerRef.current(i, k), PRIORITY_FOCUSED)\n }, [focused, focusBus, stableId])\n}\n\nexport function useGlobalInput(id: string, handler: KeyHandler, opts?: { isActive?: boolean }): void {\n const focusBus = useOptionalFocusBus()\n const handlerRef = useRef(handler)\n handlerRef.current = handler\n const isActive = opts?.isActive ?? true\n\n useInput((input, key) => {\n if (!focusBus && isActive) handlerRef.current(input, key)\n }, { isActive: !focusBus && isActive })\n\n useEffect(() => {\n if (!focusBus || !isActive) return\n return focusBus.register(id, (i, k) => handlerRef.current(i, k), PRIORITY_GLOBAL)\n }, [focusBus, id, isActive])\n}\n\nlet inputIdCounter = 0\n\n// ═══════════════════ useFallbackInput(外层兜底) ═══════════════════\n\n/**\n * 注册一个低优先级 handler——只在聚焦元素和 Router 都不消费时执行。\n *\n * 用法(WizardDialog 等外层组件):\n * useFallbackInput('wizard-esc', (input, key) => {\n * if (key.escape) { goBack(); return true }\n * return false\n * })\n */\nfunction usePriorityInput(id: string, handler: KeyHandler, priority: number, opts?: { isActive?: boolean; ignoreRoute?: boolean }): void {\n const focusBus = useOptionalFocusBus()\n const routeCtx = useContext(RouteContext)\n const handlerRef = useRef(handler)\n handlerRef.current = handler\n // 前缀路由(`<Route prefix>`,如 `/viewer/file`)的 `routePath` 固定为基路径,但嵌套\n // find 子路由(`/viewer/file/find`,见 RFC-262)push 后 `currentPath` 会移到子路径——\n // 严格相等判断会在 find 模式激活的瞬间把这里判假,禁用掉正在处理查找输入的 handler\n // 本身(表现为 find toolbar 打不出字:Esc 走 Router 默认导航层不受此门控,独立生效,\n // 掩盖了输入被吞的事实)。故允许 currentPath 是 routePath 的前缀匹配。\n const routeActive =\n opts?.ignoreRoute ||\n !routeCtx.hasRouter ||\n routeCtx.routePath === routeCtx.currentPath ||\n routeCtx.currentPath.startsWith(`${routeCtx.routePath}/`)\n const isActive = (opts?.isActive ?? true) && routeActive\n\n // 独立渲染场景(Provider 外)回退到 Ink useInput,保持旧 useInput 语义。\n useInput((input, key) => {\n if (!focusBus && isActive) handlerRef.current(input, key)\n }, { isActive: !focusBus && isActive })\n\n useEffect(() => {\n if (!focusBus || !isActive) return\n return focusBus.register(id, (i, k) => handlerRef.current(i, k), priority)\n }, [focusBus, id, isActive, priority])\n}\n\nexport function useFallbackInput(id: string, handler: KeyHandler, opts?: { isActive?: boolean; ignoreRoute?: boolean }): void {\n usePriorityInput(id, handler, PRIORITY_FALLBACK, opts)\n}\n\n/**\n * 注册选区层 handler(`PRIORITY_SELECTION`,见该常量的注释)——仅供 ESC 取消选区使用。\n *\n * `ignoreRoute: true` 是必需的:选区画在所有路由之上,弹层/面板在场时同样要能取消,\n * 若受路由门控则「打开面板后选区取消不掉」。\n */\nexport function useSelectionInput(id: string, handler: KeyHandler, opts?: { isActive?: boolean }): void {\n usePriorityInput(id, handler, PRIORITY_SELECTION, { ...opts, ignoreRoute: true })\n}\n\n/**\n * 注册一个**只读按键观察者**——收到每一次按键但**从不消费**(handler 恒不返回 true)。\n *\n * 用途:需要感知「用户此刻有动作」但不参与键位分派的消费方(当前:空闲 tips 计时重置)。\n *\n * 为何不复用 useAppFallbackInput:那一层会参与分派链,语义上是\"兜底处理键\";本 hook\n * 明确表达\"我只旁听\"。实现上注册在 APP_FALLBACK(0) 之下不可能(那已是最低),故仍挂\n * 该层,但**内部强制忽略 handler 返回值**——调用方即使误写 `return true` 也不会吞键。\n *\n * @param onActivity 每次按键触发(不传参——观察者不该依赖具体键值做分支,那是分派层的事)\n */\nexport function useKeyActivityObserver(id: string, onActivity: () => void, opts?: { isActive?: boolean }): void {\n const cbRef = useRef(onActivity)\n cbRef.current = onActivity\n usePriorityInput(\n id,\n () => {\n cbRef.current()\n return false // 恒不消费——本层只旁听\n },\n PRIORITY_APP_FALLBACK,\n { ...opts, ignoreRoute: true },\n )\n}\n\nexport function useAppFallbackInput(id: string, handler: KeyHandler, opts?: { isActive?: boolean; ignoreRoute?: boolean }): void {\n usePriorityInput(id, handler, PRIORITY_APP_FALLBACK, opts)\n}\n","/**\n * RovingFocusGroup.tsx — 组内焦点漫游原语(RFC-268 D5,对标 Radix RovingFocus /\n * WAI-ARIA roving tabindex)。\n *\n * web 焦点模型:**组 = 一个 tab stop,组内 = 方向键漫游**。本组件向 Tabable 注册\n * **一个**条目(占一个 Tab 坑位);focused 时方向键在组内条目间移动 active index,\n * 非 focused 时组内选中态保留(re-focus 恢复,对齐 Radix currentTabStop 行为)。\n *\n * 键盘契约(RFC-268 D2/D5):\n * - ↑↓(direction='vertical',默认)或 ←→('horizontal'):移动 active(循环)\n * - 数字 1-9:**onPick 一步直触发**(bypass roving 态,不走 select→confirm 两步——\n * 对齐基准 TrustPromptDialog 数字直选与既有 Choices shortcut 行为);仅覆盖前 9 项\n * - 字母 shortcut(item 定义,禁数字字符):onPick 直触发\n * - Enter:onPick(active)\n * - Esc:escapeIndex 定义时 onPick(escapeIndex),否则不拦截(透传上层)\n * - Tab:不拦截——归 Router 焦点循环(RFC-261);单 Tabable 路由下循环回自己是\n * no-op(registry.next 幂等),不会误触 onPick\n *\n * controlled/uncontrolled 双模式(Radix 标准 pattern,评审 P1#3):\n * - 受控:传 `activeIndex` + `onActiveChange`(Choices 兼容皮用此桥接消费方 selected state)\n * - 非受控:缺省内部自持(收编方新代码省样板)\n *\n * 无 Router/FocusBusProvider 场景(Trust/ParentWorkspace 启动前独立 Ink render):\n * useFallbackInput 回退裸 useInput(FocusBus.tsx 已内建降级),键盘链路不断。\n */\nimport { useState, useContext } from 'react'\nimport type { Key } from 'ink'\nimport { useTabable, RouteContext } from '../../router/Tabable'\nimport { useTabableInput, useFallbackInput } from '../../router/FocusBus'\n\nexport interface RovingItem {\n /** 稳定 key(渲染与 onPick 索引对齐用)。 */\n key: string\n /** 快捷键字符(小写字母;**禁数字**——数字保留给 1-9 直选,见 D5)。 */\n shortcut?: string\n disabled?: boolean\n}\n\nexport interface RovingFocusGroupOptions {\n items: readonly RovingItem[]\n /** 选中项被确认(Enter/数字/shortcut/escapeIndex 触发)。 */\n onPick: (index: number) => void\n /** 方向:vertical=↑↓(默认,基准范式);horizontal=←→(预留 subtitleActions/buttons 迁移)。 */\n direction?: 'vertical' | 'horizontal'\n /** 受控 active index(与 onActiveChange 成对);缺省非受控内部自持。 */\n activeIndex?: number\n onActiveChange?: (index: number) => void\n /** Esc 触发的选项 index;undefined = Esc 不拦截。 */\n escapeIndex?: number\n /** Tab 坑位(默认 0 参与 Tab 切换;-1 不参与)。 */\n tabIndex?: number\n /** 稳定 id(Tabable 注册 + fallback input 命名)。 */\n id?: string\n}\n\nexport interface RovingFocusGroupState {\n /** 当前 active index(受控时 = activeIndex prop;非受控 = 内部态)。 */\n active: number\n /** 组是否持有焦点(Router 场景下 Tabable focused;无 Router 恒 false,键盘走 fallback)。 */\n focused: boolean\n}\n\n/**\n * useRovingFocus — RovingFocusGroup 的 hook 形态(组件形态由 Menu.Root 等渲染层组合)。\n *\n * 返回 active/focused 供渲染层画 pointer/高亮;键盘处理内部完成。\n */\nexport function useRovingFocus(opts: RovingFocusGroupOptions): RovingFocusGroupState {\n const {\n items,\n onPick,\n direction = 'vertical',\n activeIndex,\n onActiveChange,\n escapeIndex,\n tabIndex = 0,\n id,\n } = opts\n\n const n = items.length\n const controlled = activeIndex !== undefined\n const [internalActive, setInternalActive] = useState(0)\n const active = controlled ? activeIndex : internalActive\n\n const setActive = (i: number): void => {\n if (!controlled) setInternalActive(i)\n onActiveChange?.(i)\n }\n\n const routeCtx = useContext(RouteContext)\n const hasRouter = routeCtx.hasRouter === true\n\n // 组 = 一个 Tabable 条目(占一个 Tab 坑位)。Enter 经 onAction 声明式处理(RFC-265)。\n const { focused } = useTabable({\n tabIndex,\n disabled: !hasRouter || tabIndex < 0,\n id,\n onAction: () => {\n const item = items[active]\n if (item && !item.disabled) onPick(active)\n return true\n },\n })\n\n const move = (delta: number): void => {\n if (n === 0) return\n // 跳过 disabled 项(最多绕一圈)。\n let next = (active + delta + n) % n\n for (let guard = 0; guard < n && items[next]?.disabled; guard++) {\n next = (next + delta + n) % n\n }\n setActive(next)\n }\n\n const handler = (inputChar: string, key: Key): boolean => {\n const prev = direction === 'vertical' ? key.upArrow : key.leftArrow\n const nextK = direction === 'vertical' ? key.downArrow : key.rightArrow\n if (prev) {\n move(-1)\n return true\n }\n if (nextK) {\n move(1)\n return true\n }\n if (key.return) {\n const item = items[active]\n if (item && !item.disabled) onPick(active)\n return true\n }\n if (key.escape) {\n if (escapeIndex !== undefined) {\n onPick(escapeIndex)\n return true\n }\n return false // 透传上层(Router goBack 等)\n }\n // 数字 1-9 直触发(bypass roving,一步 onPick——D5)。\n if (inputChar >= '1' && inputChar <= '9') {\n const i = inputChar.charCodeAt(0) - '1'.charCodeAt(0)\n if (i < n && !items[i]?.disabled) {\n onPick(i)\n return true\n }\n return false\n }\n // 字母 shortcut 直触发。\n const c = inputChar.toLowerCase()\n const si = items.findIndex((it) => it.shortcut === c && !it.disabled)\n if (si >= 0) {\n onPick(si)\n return true\n }\n return false\n }\n\n // Router 场景:focused 时经 focusBus 高优先接管;非 focused 不响应(组外键盘归属他人)。\n useTabableInput(focused && hasRouter, handler)\n // 无 Router / Router 未聚焦本组:fallback 兜底(无 Provider 时降级裸 useInput——FocusBus 内建)。\n useFallbackInput(\n `roving-${id ?? items.map((i) => i.key).join('-')}`,\n handler,\n { isActive: !focused || !hasRouter },\n )\n\n return { active, focused }\n}\n","/**\n * Menu.tsx — 竖排编号选择菜单(RFC-268,对标 Radix Menu/RadioGroup 组合式)。\n *\n * 基准范式(用户指定:TrustPromptDialog/ParentWorkspaceDialog 启动弹窗):\n * ▶ 1. Trust — full access ← FIGURES.pointer + `{i+1}.` 编号\n * 2. Read-only this session 选中行 color+bold(三重强化),未选中 inactive\n * 3. No, exit\n * 交互:↑↓ 漫游 / 1-9 直选 / 字母 shortcut 直选 / Enter 确认 / Esc(escapeIndex)\n *\n * 组合式 API(描述符编译,对齐 SelectPrimitive 模式):\n * <Menu.Root onPick={...}>\n * <Menu.Item label=\"Trust\" color={THEME.success} /> ← Item 不渲染,纯数据描述符\n * <Menu.Item label=\"Read-only\" color=\"accent\" /> ← color: 语义枚举 | hex 双类型(D4)\n * </Menu.Root>\n * 注:label 为 **prop 字符串**(非 children 文本)——规避 SelectPrimitive textOf() 递归\n * 提取复杂度(评审 #2);key 用 index(防重复 value)。\n *\n * 焦点/键盘归 useRovingFocus 原语(D5):组=单 Tab 坑位、controlled/uncontrolled 双模式、\n * 数字一步直触发、无 Router 场景 fallback 降级。本组件只做渲染。\n */\nimport React, { createElement } from 'react'\nimport { Box, Text } from 'ink'\nimport { useRovingFocus } from './RovingFocusGroup'\nimport { useColors, FIGURES } from '../../theme'\nimport type { ThemeColors } from '../../theme'\nimport type { ButtonColor } from '../Button'\n\n// ═══════════════════ D4: 色彩映射(文字前景独立映射) ═══════════════════\n\n/**\n * resolveMenuColor — 语义枚举 → THEME 文字前景色;hex/THEME 值直通。\n *\n * **不复用 Button 的 resolveFg**(其 `accent→colors.selection` 焦点蓝是按钮背景语义)——\n * 竖排文字前景 accent 必须落品牌紫 `colors.accent`(RFC-188 accent/selection 语义分离;\n * 否则收编基准弹窗时 THEME.accent 会变焦点蓝,违背视觉保真——对抗复审 F1)。\n * gray 落 inactive(文字场景),非 Button 的 panelBorder(边框场景)。\n */\nexport function resolveMenuColor(c: ButtonColor | string | undefined, colors: ThemeColors): string {\n switch (c) {\n case undefined:\n case 'accent':\n return colors.accent\n case 'amber':\n return colors.warning\n case 'red':\n return colors.error\n case 'green':\n return colors.success\n case 'gray':\n return colors.inactive\n default:\n return c // hex / THEME.* 直通(收编方零色差)\n }\n}\n\n// ═══════════════════ Menu.Item 描述符 ═══════════════════\n\nexport interface MenuItemProps {\n /** 条目文案(prop 字符串,非 children——见头注释)。 */\n label: string\n /** 选中态色:语义枚举(ButtonColor)或 hex/THEME 值直通(D4 双类型)。缺省 accent。 */\n color?: ButtonColor | string\n /** 快捷键字符(小写字母;禁数字——数字保留给 1-9 直选)。 */\n shortcut?: string\n /** 次级说明行(缩进渲染于条目下方,同 SelectPrimitive 的 detail 语义)。 */\n detail?: string\n disabled?: boolean\n}\n\n/** 纯数据描述符——不渲染任何节点,由 Menu.Root 经 React.Children 编译。 */\nfunction MenuItem(_props: MenuItemProps): null {\n return null\n}\nMenuItem.displayName = 'Menu.Item'\n\n// ═══════════════════ Menu.Root ═══════════════════\n\nexport interface MenuRootProps {\n /** Menu.Item 描述符列表(JSX children 或 createElement rest 参数)。 */\n children?: React.ReactNode\n /** 条目被确认(Enter/数字/shortcut/escapeIndex)。 */\n onPick: (index: number) => void\n /** 受控 active(Choices 兼容皮桥接消费方 selected state 用);缺省非受控。 */\n activeIndex?: number\n onActiveChange?: (index: number) => void\n /** Esc 触发的 index;undefined = Esc 透传。 */\n escapeIndex?: number\n tabIndex?: number\n id?: string\n}\n\n/** 编译 children 描述符 → MenuItemProps[](对齐 SelectPrimitive compileOptions 模式)。 */\nfunction compileItems(children: React.ReactNode): MenuItemProps[] {\n const items: MenuItemProps[] = []\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return\n if ((child.type as { displayName?: string }).displayName === 'Menu.Item') {\n items.push(child.props as MenuItemProps)\n }\n })\n return items\n}\n\nfunction MenuRoot({\n children,\n onPick,\n activeIndex,\n onActiveChange,\n escapeIndex,\n tabIndex = 0,\n id,\n}: MenuRootProps): React.ReactElement {\n const colors = useColors()\n const items = compileItems(children)\n\n const { active } = useRovingFocus({\n items: items.map((it, i) => ({ key: `${i}`, shortcut: it.shortcut, disabled: it.disabled })),\n onPick,\n direction: 'vertical',\n activeIndex,\n onActiveChange,\n escapeIndex,\n tabIndex,\n id,\n })\n\n return createElement(\n Box,\n { flexDirection: 'column' },\n ...items.map((it, i) => {\n const sel = i === active\n const row = createElement(\n Text,\n {\n color: it.disabled ? colors.inactive : sel ? resolveMenuColor(it.color, colors) : colors.inactive,\n bold: sel,\n dimColor: it.disabled,\n },\n `${sel ? FIGURES.pointer : ' '} ${i + 1}. ${it.label}`,\n )\n if (!it.detail) return createElement(Box, { key: `item-${i}`, flexDirection: 'column' }, row)\n // detail 子行:缩进对齐条目文案(pointer 1 + 空格 1 + 编号 2 + 空格 1 = 5 列)\n return createElement(\n Box,\n { key: `item-${i}`, flexDirection: 'column' },\n row,\n createElement(\n Box,\n { marginLeft: 5 },\n createElement(Text, { color: colors.inactive, dimColor: true }, it.detail),\n ),\n )\n }),\n )\n}\nMenuRoot.displayName = 'Menu.Root'\n\nexport const Menu = {\n Root: MenuRoot,\n Item: MenuItem,\n}\n","/**\n * cursor.ts — Immutable Cursor model (CC utils/Cursor.ts port).\n * Intl.Segmenter grapheme boundaries, CJK width 2, ZWJ-aware via string-width v8.x.\n * Two deltas: tab→width 1 special case; per-grapheme width cache (4096 entries).\n */\n\nimport stringWidthLib from 'string-width'\nimport { isCjkLocale } from '../term/cjk-locale'\n\nconst WIDTH_CACHE_MAX_ENTRIES = 4096\nconst widthCache = new Map<string, number>()\n\nfunction computeWidth(str: string): number {\n // `string-width` treats `\\t` as a zero-width non-printing cluster (its Control-category\n // skip rule). Every caller here needs a literal tab (`sanitizeTextChunk` intentionally\n // preserves `\\t` from pasted/typed text) to occupy at least one display column, or column\n // math (softWrap wrapping, Cursor up/down column-preservation via `lineColToOffset`/\n // `offsetToLineCol`) silently under-counts and the cursor lands in the wrong place — the\n // same symptom class this fix targets, just tab-triggered instead of emoji-triggered.\n // Substituting a single space per tab before delegating preserves additive width (matches\n // the pre-fix behavior of counting tab as width 1) for strings with tabs anywhere in them,\n // not just isolated single-tab calls.\n const s = str.includes('\\t') ? str.replace(/\\t/g, ' ') : str\n return isCjkLocale ? stringWidthLib(s, { ambiguousIsNarrow: false }) : stringWidthLib(s)\n}\n\nexport function stringWidth(str: string): number {\n // Memoize only short strings (grapheme-cluster-sized calls from the hot paths above);\n // long strings (e.g. a full line/paragraph) are called once, not worth caching, and\n // could otherwise blow the cache budget on a single call.\n if (str.length > 0 && str.length <= 16) {\n const cached = widthCache.get(str)\n if (cached !== undefined) return cached\n const w = computeWidth(str)\n if (widthCache.size < WIDTH_CACHE_MAX_ENTRIES) widthCache.set(str, w)\n return w\n }\n return computeWidth(str)\n}\n\nconst graphemeSegmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })\nconst wordSegmenter = new Intl.Segmenter('en', { granularity: 'word' })\n\n/** 统一 chip 正则——paste chip + @chip 全覆盖(单一真源,规则 2)。 */\nexport const CHIP_RE =\n /\\[(?:@paste:(?:#\\d+(?:\\s+\\+\\d+\\s+lines)?|image #\\d+)|@(?:file|agent|skill|mcp|person|plugin|model):[^\\]]+)\\]/g\n\nexport interface ChipMatch {\n start: number\n end: number\n text: string\n}\n\n/** 从 chip 文本提取 kind 用于着色映射。@paste: → 'paste';@file/agent/skill/mcp/person/plugin/model: → 对应 kind。 */\nexport function parseChipKind(chipText: string): string {\n if (/^\\[@paste:/.test(chipText)) return 'paste'\n const m = chipText.match(/^\\[@(file|agent|skill|mcp|person|plugin|model):/)\n if (m) return m[1]!\n return 'unknown'\n}\n\n/** 从 `[@paste:#N]`/`[@paste:#N +M lines]` chip 文本提取数字 id;非文本粘贴 chip\n * (如 `[@paste:image #N]`)或非 paste chip 返回 undefined(RFC-198 §5.3:失效样式\n * 判定只作用于文本粘贴占位符,image refs 是 pre-existing 边界,不在本机制处理范围)。 */\nexport function parsePasteChipId(chipText: string): number | undefined {\n const m = chipText.match(/^\\[@paste:#(\\d+)/)\n return m ? Number.parseInt(m[1]!, 10) : undefined\n}\n\nfunction findChipBefore(text: string, offset: number): ChipMatch | null {\n CHIP_RE.lastIndex = 0\n let m: RegExpExecArray | null\n let best: ChipMatch | null = null\n while ((m = CHIP_RE.exec(text)) !== null) {\n if (m.index + m[0].length === offset)\n best = { start: m.index, end: m.index + m[0].length, text: m[0] }\n }\n return best\n}\n\nfunction findChipAt(text: string, offset: number): ChipMatch | null {\n CHIP_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = CHIP_RE.exec(text)) !== null) {\n if (offset > m.index && offset < m.index + m[0].length)\n return { start: m.index, end: m.index + m[0].length, text: m[0] }\n }\n return null\n}\n\n/**\n * RFC-212 D2:查询是否有 chip 恰好**起始于** offset(\"胖字符\"选中态判据——光标经\n * 方向键天然停靠在 chip.start,见 `left()/right()`)。与 `findChipAt`(要求 offset\n * 严格落在 chip 内部)是不同谓词,故单独导出,但复用同一套 CHIP_RE 迭代基础设施\n * ——不新增第二遍独立遍历(里程碑完成审核 M2 修订:`TextInput.tsx` 侧此前自行\n * 维护了一份平行的 CHIP_RE 循环,与本函数/`findChipAt`/`findChipBefore` 形成三套\n * 边界探测逻辑,违反重要事项规则 2,现收口到此单一实现)。\n */\nexport function findChipStartingAt(text: string, offset: number): ChipMatch | null {\n CHIP_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = CHIP_RE.exec(text)) !== null) {\n if (m.index === offset) return { start: m.index, end: m.index + m[0].length, text: m[0] }\n }\n return null\n}\n\n/**\n * RFC-212 D2:若 offset 落在某 chip 内部(非边界),吸附到该 chip 的 `start`\n * (进入选中态)——与 `left()/right()` 的既有吸附行为一致。`up()/down()` 按列位置\n * 换行会产生任意 offset,落点可能恰好切进多字节/多字符宽度的 chip 内部,此处补齐\n * 与水平移动同款的原子化保证。\n */\nfunction snapOutOfChip(text: string, offset: number): number {\n const chip = findChipAt(text, offset)\n return chip ? chip.start : offset\n}\n\nfunction graphemes(str: string): Array<{ segment: string; index: number }> {\n return [...graphemeSegmenter.segment(str)]\n}\n\nexport class MeasuredText {\n readonly text: string\n readonly columns: number\n readonly lines: string[]\n private _graphemes?: Array<{ segment: string; index: number }>\n private _wordBreaks?: number[]\n\n constructor(text: string, columns: number) {\n const nfc = text.normalize('NFC')\n this.text = nfc === text ? text : nfc\n this.columns = Math.max(1, columns)\n this.lines = this.text === '' ? [''] : this.text.split('\\n')\n }\n\n get graphemeList(): Array<{ segment: string; index: number }> {\n if (!this._graphemes) this._graphemes = graphemes(this.text)\n return this._graphemes\n }\n\n get wordBreaks(): number[] {\n if (!this._wordBreaks) {\n const breaks: number[] = [0]\n for (const seg of wordSegmenter.segment(this.text)) {\n if (seg.isWordLike && seg.index > 0) breaks.push(seg.index)\n }\n breaks.push(this.text.length)\n this._wordBreaks = breaks\n }\n return this._wordBreaks\n }\n\n get lineCount(): number {\n return this.lines.length\n }\n\n offsetToLineCol(offset: number): { line: number; col: number } {\n let remaining = offset\n for (let i = 0; i < this.lines.length; i++) {\n const lineLen = this.lines[i]!.length + 1\n if (remaining < lineLen)\n return { line: i, col: stringWidth(this.lines[i]!.slice(0, remaining)) }\n remaining -= lineLen\n }\n const last = this.lines.length - 1\n return { line: last, col: stringWidth(this.lines[last]!) }\n }\n\n lineColToOffset(line: number, col: number): number {\n if (line >= this.lines.length) return this.text.length\n let offset = 0\n for (let i = 0; i < line; i++) offset += this.lines[i]!.length + 1\n const lineText = this.lines[line]!\n if (col <= 0) return offset\n if (col >= stringWidth(lineText)) return offset + lineText.length\n let curW = 0\n for (const g of graphemes(lineText)) {\n const segW = stringWidth(g.segment)\n if (curW + segW > col) return offset + g.index\n curW += segW\n }\n return offset + lineText.length\n }\n}\n\nexport class Cursor {\n readonly measuredText: MeasuredText\n readonly offset: number\n readonly selection: number\n\n private constructor(mt: MeasuredText, offset: number, selection = 0) {\n this.measuredText = mt\n this.offset = Math.max(0, Math.min(offset, mt.text.length))\n this.selection = selection\n }\n\n static fromText(text: string, columns: number, offset = 0): Cursor {\n return new Cursor(new MeasuredText(text, columns), offset)\n }\n\n static fromCursor(cursor: Cursor, columns?: number): Cursor {\n const mt =\n columns !== undefined && columns !== cursor.columns\n ? new MeasuredText(cursor.text, columns)\n : cursor.measuredText\n return new Cursor(mt, cursor.offset, cursor.selection)\n }\n\n get text(): string {\n return this.measuredText.text\n }\n get columns(): number {\n return this.measuredText.columns\n }\n get position(): { line: number; col: number } {\n return this.measuredText.offsetToLineCol(this.offset)\n }\n get hasSelection(): boolean {\n return this.selection !== 0\n }\n\n equals(other: Cursor): boolean {\n return this.offset === other.offset && this.measuredText === other.measuredText\n }\n\n right(): Cursor {\n if (this.offset >= this.text.length) return this\n const chip = findChipAt(this.text, this.offset + 1)\n if (chip && this.offset <= chip.start) return new Cursor(this.measuredText, chip.end)\n for (const g of this.measuredText.graphemeList) {\n if (g.index > this.offset) return new Cursor(this.measuredText, g.index)\n }\n return new Cursor(this.measuredText, this.text.length)\n }\n\n left(): Cursor {\n if (this.offset <= 0) return this\n const chip = findChipAt(this.text, this.offset - 1)\n if (chip) return new Cursor(this.measuredText, chip.start)\n let prev: { segment: string; index: number } | null = null\n for (const g of this.measuredText.graphemeList) {\n if (g.index >= this.offset) break\n prev = g\n }\n return new Cursor(this.measuredText, prev?.index ?? 0)\n }\n\n up(): Cursor {\n const pos = this.position\n if (pos.line <= 0) return this\n const offset = this.measuredText.lineColToOffset(pos.line - 1, pos.col)\n return new Cursor(this.measuredText, snapOutOfChip(this.text, offset))\n }\n\n down(): Cursor {\n const pos = this.position\n if (pos.line >= this.measuredText.lineCount - 1) return this\n const offset = this.measuredText.lineColToOffset(pos.line + 1, pos.col)\n return new Cursor(this.measuredText, snapOutOfChip(this.text, offset))\n }\n\n prevWord(): Cursor {\n for (let i = this.measuredText.wordBreaks.length - 1; i >= 0; i--) {\n if (this.measuredText.wordBreaks[i]! < this.offset)\n return new Cursor(this.measuredText, this.measuredText.wordBreaks[i]!)\n }\n return new Cursor(this.measuredText, 0)\n }\n\n nextWord(): Cursor {\n for (const b of this.measuredText.wordBreaks) {\n if (b > this.offset) return new Cursor(this.measuredText, b)\n }\n return new Cursor(this.measuredText, this.text.length)\n }\n\n insert(value: string): Cursor {\n if (value.length === 0) return this\n const t = this.text.slice(0, this.offset) + value + this.text.slice(this.offset)\n return new Cursor(new MeasuredText(t, this.columns), this.offset + value.length)\n }\n\n backspace(): Cursor {\n if (this.offset <= 0) return this\n // RFC-212 D2:选中态(光标停靠在 chip.start,即\"胖字符\"高亮态)优先于紧贴态——\n // 多 chip 相邻场景(如 `[@file:a][@agent:b]`,光标停在第二个 chip 的 start,\n // 此时第一个 chip 同时满足\"紧贴光标左侧\")下,用户此刻的光标明确停靠在第二个\n // chip 上,Backspace 应删除光标\"停靠\"的 chip,而非其左侧相邻的另一个 chip\n // (重要事项规则 5,判定顺序已在 RFC 阶段决策,不得在此重新讨论)。\n // `findChipAt` 要求 offset 严格落在 chip 内部(不含边界),故用 offset+1 探测\n // \"offset 恰好是某 chip 起点\"——与 `right()` 的既有判定手法一致(cursor.ts right())。\n const selected = findChipAt(this.text, this.offset + 1)\n if (selected && this.offset === selected.start) {\n const t = this.text.slice(0, selected.start) + this.text.slice(selected.end)\n return new Cursor(new MeasuredText(t, this.columns), selected.start)\n }\n const chip = findChipBefore(this.text, this.offset)\n if (chip) {\n const t = this.text.slice(0, chip.start) + this.text.slice(chip.end)\n return new Cursor(new MeasuredText(t, this.columns), chip.start)\n }\n const prev = this.left()\n const t = this.text.slice(0, prev.offset) + this.text.slice(this.offset)\n return new Cursor(new MeasuredText(t, this.columns), prev.offset)\n }\n\n delete(): Cursor {\n if (this.offset >= this.text.length) return this\n const next = this.right()\n const t = this.text.slice(0, this.offset) + this.text.slice(next.offset)\n return new Cursor(new MeasuredText(t, this.columns), this.offset)\n }\n\n killToLineEnd(): { cursor: Cursor; killed: string } {\n const pos = this.position\n let lineStart = 0\n for (let i = 0; i < pos.line; i++) lineStart += this.measuredText.lines[i]!.length + 1\n const lineEnd = lineStart + this.measuredText.lines[pos.line]!.length\n const killed = this.text.slice(this.offset, lineEnd)\n const t = this.text.slice(0, this.offset) + this.text.slice(lineEnd)\n return { cursor: new Cursor(new MeasuredText(t, this.columns), this.offset), killed }\n }\n\n killToLineStart(): { cursor: Cursor; killed: string } {\n const pos = this.position\n let lineStart = 0\n for (let i = 0; i < pos.line; i++) lineStart += this.measuredText.lines[i]!.length + 1\n\n if (this.offset === lineStart && pos.line > 0) {\n const newlinePos = lineStart - 1\n const t = this.text.slice(0, newlinePos) + this.text.slice(lineStart)\n return { cursor: new Cursor(new MeasuredText(t, this.columns), newlinePos), killed: '\\n' }\n }\n\n const killed = this.text.slice(lineStart, this.offset)\n const t = this.text.slice(0, lineStart) + this.text.slice(this.offset)\n return { cursor: new Cursor(new MeasuredText(t, this.columns), lineStart), killed }\n }\n\n killWordBefore(): { cursor: Cursor; killed: string } {\n const chip = findChipBefore(this.text, this.offset)\n if (chip) {\n const t = this.text.slice(0, chip.start) + this.text.slice(chip.end)\n return {\n cursor: new Cursor(new MeasuredText(t, this.columns), chip.start),\n killed: chip.text,\n }\n }\n const target = this.prevWord()\n const killed = this.text.slice(target.offset, this.offset)\n const t = this.text.slice(0, target.offset) + this.text.slice(this.offset)\n return { cursor: new Cursor(new MeasuredText(t, this.columns), target.offset), killed }\n }\n\n modifyText(end: number, ins = ''): Cursor {\n const t = this.text.slice(0, this.offset) + ins + this.text.slice(end)\n return new Cursor(new MeasuredText(t, this.columns), this.offset + ins.length)\n }\n\n deleteTokenBefore(): Cursor | null {\n if (this.offset <= 0) return null\n return this.backspace()\n }\n\n getViewportStartLine(maxVisible: number): number {\n const pos = this.position\n const half = Math.floor(maxVisible / 2)\n return Math.max(0, Math.min(pos.line - half, this.measuredText.lineCount - maxVisible))\n }\n\n toEnd(): Cursor {\n return new Cursor(this.measuredText, this.text.length)\n }\n toStart(): Cursor {\n return new Cursor(this.measuredText, 0)\n }\n\n toLineStart(): Cursor {\n let lineStart = 0\n const pos = this.position\n for (let i = 0; i < pos.line; i++) lineStart += this.measuredText.lines[i]!.length + 1\n return new Cursor(this.measuredText, lineStart)\n }\n\n toLineEnd(): Cursor {\n let offset = 0\n const pos = this.position\n for (let i = 0; i < pos.line; i++) offset += this.measuredText.lines[i]!.length + 1\n return new Cursor(this.measuredText, offset + this.measuredText.lines[pos.line]!.length)\n }\n}\n","/**\n * text-input-logic.ts — Emacs editing state machine (pure functions, no React).\n * ctrl+a/e/b/f/h/d/k/u/w, meta+b/f. Return→submit, Shift+Return→submit_force.\n * Multi-line: \\ + Return→newline. Zero React dependency.\n */\n\nimport { Cursor } from './cursor'\n\nexport interface EditKeyEvent {\n name: string\n ctrl: boolean\n shift: boolean\n alt: boolean\n meta: boolean\n}\n\nexport type EditAction =\n | 'insert'\n | 'delete_backward'\n | 'delete_forward'\n | 'kill_to_end'\n | 'kill_to_start'\n | 'kill_word_before'\n | 'move_to_start'\n | 'move_to_end'\n | 'move_left'\n | 'move_right'\n | 'move_word_left'\n | 'move_word_right'\n | 'submit'\n | 'submit_force'\n | 'none'\n | 'clear_text'\n\nexport interface EditResult {\n cursor: Cursor\n action: EditAction\n killed?: string\n}\n\nfunction isPrintable(name: string): boolean {\n if (name.length === 0 || name.length > 2) return false\n const cp = name.codePointAt(0) ?? 0\n if (cp < 0x20 || cp === 0x7f) return false\n if (name.length === 2) return name.charCodeAt(0) >= 0xd800 && name.charCodeAt(0) <= 0xdbff\n return true\n}\n\nexport function handleEditingKey(cursor: Cursor, key: EditKeyEvent): EditResult | null {\n const { name, ctrl, shift, alt, meta } = key\n\n if (ctrl && name === 'a') return { cursor: cursor.toStart(), action: 'move_to_start' }\n if (ctrl && name === 'e') return { cursor: cursor.toEnd(), action: 'move_to_end' }\n if (ctrl && name === 'b') {\n const n = cursor.left()\n return n.offset !== cursor.offset ? { cursor: n, action: 'move_left' } : null\n }\n if (ctrl && name === 'f') {\n const n = cursor.right()\n return n.offset !== cursor.offset ? { cursor: n, action: 'move_right' } : null\n }\n\n if ((alt || meta) && name === 'b') {\n const n = cursor.prevWord()\n return n.offset !== cursor.offset ? { cursor: n, action: 'move_word_left' } : null\n }\n if ((alt || meta) && name === 'f') {\n const n = cursor.nextWord()\n return n.offset !== cursor.offset ? { cursor: n, action: 'move_word_right' } : null\n }\n\n if ((ctrl && name === 'h') || name === 'backspace') {\n const n = cursor.backspace()\n if (n.offset !== cursor.offset || n.text !== cursor.text) {\n const killed = cursor.text.slice(n.offset, cursor.offset)\n return { cursor: n, action: 'delete_backward', killed }\n }\n return null\n }\n if (ctrl && name === 'd') {\n if (cursor.offset >= cursor.text.length) return null\n const n = cursor.delete()\n return n.text !== cursor.text ? { cursor: n, action: 'delete_forward' } : null\n }\n if (name === 'delete') {\n const n = cursor.delete()\n return n.text !== cursor.text ? { cursor: n, action: 'delete_forward' } : null\n }\n\n if (ctrl && name === 'k') {\n const r = cursor.killToLineEnd()\n return r.killed.length > 0\n ? { cursor: r.cursor, action: 'kill_to_end', killed: r.killed }\n : null\n }\n if (ctrl && name === 'u') {\n const r = cursor.killToLineStart()\n return r.killed.length > 0\n ? { cursor: r.cursor, action: 'kill_to_start', killed: r.killed }\n : null\n }\n if (ctrl && name === 'w') {\n const r = cursor.killWordBefore()\n return r.killed.length > 0\n ? { cursor: r.cursor, action: 'kill_word_before', killed: r.killed }\n : null\n }\n\n if (name === 'return' || name === 'enter') {\n // 用户需求:followUp/排队 从 Option+Enter 改绑 Shift+Enter(Option+Enter 在部分系统/\n // 终端里被系统级快捷键占用,冲突比 Shift 更常见)。原先 shift+return 的\"插入换行\"\n // 语义随之让位——多行输入唯一入口收窄为行尾 `\\` + Enter(handleEditingKeyExtended\n // 已有的兜底路径,非新增能力)。\n if (ctrl || meta || alt || shift) return { cursor, action: 'submit_force' }\n return { cursor, action: 'submit' }\n }\n\n if (isPrintable(name)) {\n if (ctrl) return null\n return { cursor: cursor.insert(name), action: 'insert' }\n }\n if (name === ' ') {\n return { cursor: cursor.insert(' '), action: 'insert' }\n }\n\n return null\n}\n\nconst KILL_RING_MAX = 10\n\ninterface KillRing {\n entries: string[]\n index: number\n lastAction: 'kill' | 'yank' | 'other'\n lastYankOffset: number\n lastYankLength: number\n}\n\nconst killRing: KillRing = {\n entries: [],\n index: -1,\n lastAction: 'other',\n lastYankOffset: 0,\n lastYankLength: 0,\n}\n\nexport function getKillRingEntries(): readonly string[] {\n return killRing.entries\n}\nexport function resetKillRing(): void {\n killRing.entries = []\n killRing.index = -1\n killRing.lastAction = 'other'\n}\n\nfunction pushToKillRing(text: string, prepend: boolean): void {\n if (prepend && killRing.entries.length > 0 && killRing.lastAction === 'kill') {\n killRing.entries[0] = text + killRing.entries[0]\n } else if (!prepend && killRing.entries.length > 0 && killRing.lastAction === 'kill') {\n killRing.entries[0] += text\n } else {\n killRing.entries.unshift(text)\n if (killRing.entries.length > KILL_RING_MAX) killRing.entries.length = KILL_RING_MAX\n }\n killRing.lastAction = 'kill'\n killRing.index = -1\n}\n\nfunction resetYankState(): void {\n killRing.lastAction = 'other'\n killRing.index = -1\n}\n\nexport function handleEditingKeyExtended(cursor: Cursor, key: EditKeyEvent): EditResult | null {\n const { name, ctrl, alt, meta, shift } = key\n\n if (ctrl && name === 'y') {\n if (killRing.entries.length === 0) return null\n const text = killRing.entries[0]!\n killRing.lastAction = 'yank'\n killRing.index = 0\n killRing.lastYankOffset = cursor.offset\n killRing.lastYankLength = text.length\n return { cursor: cursor.insert(text), action: 'insert' }\n }\n if ((alt || meta) && name === 'y') {\n if (killRing.lastAction !== 'yank' && killRing.lastAction !== 'kill') return null\n if (killRing.entries.length <= 1) return null\n killRing.index = (killRing.index + 1) % killRing.entries.length\n const newText = killRing.entries[killRing.index]!\n const final = Cursor.fromText(cursor.text, cursor.columns, killRing.lastYankOffset).modifyText(\n killRing.lastYankOffset + killRing.lastYankLength,\n newText,\n )\n killRing.lastYankLength = newText.length\n killRing.lastAction = 'yank'\n return { cursor: final, action: 'insert' }\n }\n\n if (name === 'escape') {\n if (cursor.text.length === 0) return null\n const pos = cursor.position\n let lineStart = 0\n for (let i = 0; i < pos.line; i++) lineStart += cursor.measuredText.lines[i]!.length + 1\n const lineEnd = lineStart + cursor.measuredText.lines[pos.line]!.length\n const isLast = pos.line >= cursor.measuredText.lineCount - 1\n const killEnd = isLast ? lineEnd : lineEnd + 1\n const newText = cursor.text.slice(0, lineStart) + cursor.text.slice(killEnd)\n const newOffset = Math.min(lineStart, newText.length)\n return { cursor: Cursor.fromText(newText, cursor.columns, newOffset), action: 'clear_text' }\n }\n\n if (name === 'return' || name === 'enter') {\n // same Shift+Enter bind described above\n if (cursor.offset > 0 && cursor.text[cursor.offset - 1] === '\\\\' && !shift) {\n const after = cursor.backspace()\n resetYankState()\n return { cursor: after.insert('\\n'), action: 'insert' }\n }\n resetYankState()\n return { cursor, action: alt || meta || ctrl || shift ? 'submit_force' : 'submit' }\n }\n\n const result = handleEditingKey(cursor, key)\n if (!result) return null\n\n if (result.action === 'kill_to_end' && result.killed) {\n pushToKillRing(result.killed, false)\n } else if (result.action === 'kill_to_start' && result.killed) {\n pushToKillRing(result.killed, true)\n } else if (result.action === 'kill_word_before' && result.killed) {\n pushToKillRing(result.killed, true)\n } else if (result.action === 'delete_backward') {\n resetYankState()\n } else if (result.action === 'insert') {\n resetYankState()\n }\n\n return result\n}\n\n/**\n * Sanitize a multi-char input chunk (IME commit / bracketed paste).\n * Strips ANSI escape sequences and control chars; normalizes CR/CRLF to LF.\n * Keeps \\n and \\t. Returns '' if nothing printable remains.\n *\n * OSC 序列(`ESC ] ... (BEL | ST)`)必须先于 CSI 剥离(覆盖漏洞修复,见\n * `docs/design/tui-theme-color-system-audit.md` 缺陷 E 关联事故):此前只处理\n * CSI(`ESC [ ... 字母`),OSC 完全未覆盖——终端查询响应(如 OSC 10/11 背景色探测,\n * `term/terminal-palette.ts`)因时序竞态迟到、混进 Ink 接管后的 stdin 时,只会被\n * 打头的单个 `\\x1b` 字节剥掉,payload(`]11;rgb:1e1e/1e1e/1e1e...`)原样回显进输入框。\n * 顺序:① 剥完整终止的 OSC(含 BEL/ST 终止符),② 剥**未终止**的 OSC 残段(探测超时/\n * 分片读取截断,只见 `ESC ]` 开头到 chunk 结尾都没有终止符——保守整段丢弃,不当作可信\n * 可打印文本回显,因为无法确认这段字节的语义边界),③ 剥 CSI(终止符是单个字母)。\n */\nexport function sanitizeTextChunk(input: string): string {\n return input\n .replace(/\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)/g, '')\n .replace(/\\x1b\\][^\\x1b]*$/g, '')\n .replace(/\\x1b\\[[0-9;]*[~a-zA-Z]/g, '')\n .replace(/\\r\\n?/g, '\\n')\n .replace(/[\\x00-\\x08\\x0b-\\x1f\\x7f]/g, '')\n}\n\nconst VIEWPORT_THRESHOLD = 10\n\nexport function shouldUseViewport(cursor: Cursor): boolean {\n return cursor.measuredText.lineCount > VIEWPORT_THRESHOLD\n}\n\nexport function getInputViewport(\n cursor: Cursor,\n maxVisible: number,\n): { startLine: number; endLine: number; visibleLines: string[]; cursorLine: number } | null {\n const lineCount = cursor.measuredText.lineCount\n if (lineCount <= maxVisible) return null\n const pos = cursor.position\n const startLine = cursor.getViewportStartLine(maxVisible)\n const endLine = Math.min(startLine + maxVisible, lineCount)\n return {\n startLine,\n endLine,\n visibleLines: cursor.measuredText.lines.slice(startLine, endLine),\n cursorLine: pos.line - startLine,\n }\n}\n","/**\n * ime-coalesce.ts — RFC-310 IME 抖动治理纯函数(M1)。\n *\n * 两个独立机制(对应 RFC-310 因果账本因子 1+2 与因子 3):\n *\n * 1. **keystroke 微批判定**(shouldCoalesceKeystroke):IME 组合(preedit)以高频连续\n * keystroke 形态到达(间隔典型 <16ms),每个都触发 applyText 5×setState → 一帧全量\n * 重绘。微批把窗口内连续可打印 keystroke 合并成一次 state 更新。控制键(Enter/Esc/\n * Backspace/方向等)永不合并——交互语义不可延迟。\n *\n * 2. **光标冻结闸**(cursorFreezeGate):IME 候选框跟随物理光标,组合期光标 (x,y) 逐帧\n * 位移 = 候选框抖动。闸门在坐标高频变化期间冻结输出(park-at-caret,对齐 CC 思想 /\n * ink#870 诉求),稳定超过阈值后一次移到位。正常打字(间隔 > 阈值)立即跟随。\n */\n\n/** 微批窗口(ms):低于一个 60Hz 显示帧,单键合并延迟人眼无感。 */\nexport const KEYSTROKE_COALESCE_MS = 16\n\n/** 光标冻结阈值(ms):坐标变化间隔小于此值视为 IME 组合中,冻结光标。 */\nexport const CURSOR_FREEZE_MS = 150\n\n/**\n * 冻结闸状态——坐标语义是**行内几何**(column / lineOffset),不含 origin。\n * 见 cursorFreezeGate 头注释「只冻 inline,绝不冻 origin」。\n */\nexport interface CursorFreezeState {\n /** 当前对外输出的坐标(冻结期间保持不变)。 */\n x: number\n y: number\n /** 最近一次**目标**坐标变化的时间戳。 */\n lastChangeAt: number\n /** 最新目标坐标(解冻时一次移到位)。 */\n targetX: number\n targetY: number\n}\n\nexport function initialCursorFreeze(x: number, y: number, now: number): CursorFreezeState {\n return { x, y, lastChangeAt: now, targetX: x, targetY: y }\n}\n\n/**\n * 光标冻结闸:输入新的**行内几何**(inline)、armed 与当前时间,返回下一状态。\n *\n * ## 只冻 inline,绝不冻 origin(真机 bug:提交后候选框跳到状态栏又弹回)\n * 光标最终坐标 = **origin(布局实测原点)+ inline(行内几何)**。冻结闸历史上冻的是\n * 二者之和,导致:提交消息时 `text` 从 N 变 0(收缩 → 误武装 armed=1),**同一时刻**\n * 对话流增高使 origin 从 y=61 跳到 y=71,闸把输出坐标冻在旧 origin 上 160ms——\n * 光标滞留在已成为状态栏区域的旧坐标,冻结窗过期才弹回。实测日志坐实\n * (out=2,61 vs target=2,71 持续三帧)。提交场景下\"清空\"与\"布局位移\"是同一操作的\n * 固有时序,**每次提交必撞**。\n *\n * origin 的全部变化来源——对话流增高/面板开合/Tips 出现/终端 resize——**都是确定性\n * 布局事件,无一会\"震荡回去\"**,冻结零收益纯伤害。需要平滑的只有 preedit 的\n * **列位反复横跳**(插入→擦除→重填),即 inline 分量。故本闸只吃 inline,调用方\n * 用**当前** origin 与冻结后的 inline 组装最终坐标。\n *\n * **armed 判据(调用方计算)**:近期发生过 text 变短(擦除)——IME preedit 震荡的独有\n * 特征。正常打字只增长、方向键不改长度、粘贴只增长,均不 arm → 立即跟随零滞后。\n * 退格与清空(len→0)由调用方显式解除武装(确定性操作,无需平滑)。\n *\n * - !armed:无条件立即跟随(并同步目标/时间戳)。\n * - armed 且目标变化且距上次变化 < CURSOR_FREEZE_MS:冻结输出,只记目标。\n * - armed 且目标稳定 ≥ CURSOR_FREEZE_MS:落位到目标。\n */\nexport function cursorFreezeGate(\n state: CursorFreezeState,\n targetX: number,\n targetY: number,\n now: number,\n armed: boolean,\n): CursorFreezeState {\n const targetChanged = targetX !== state.targetX || targetY !== state.targetY\n if (!armed) {\n // 未武装:立即跟随(若已同步则原样返回,避免无谓新对象)。\n if (!targetChanged && state.x === targetX && state.y === targetY) return state\n return { x: targetX, y: targetY, targetX, targetY, lastChangeAt: now }\n }\n if (!targetChanged) {\n // 目标稳定:超过冻结窗后把输出坐标落到目标(若尚未追平)。\n if ((state.x !== targetX || state.y !== targetY) && now - state.lastChangeAt >= CURSOR_FREEZE_MS) {\n return { ...state, x: targetX, y: targetY }\n }\n return state\n }\n const sinceLast = now - state.lastChangeAt\n if (sinceLast < CURSOR_FREEZE_MS) {\n // 组合中:冻结输出坐标,只记录新目标。\n return { ...state, targetX, targetY, lastChangeAt: now }\n }\n // 节奏放缓:跟随。\n return { x: targetX, y: targetY, targetX, targetY, lastChangeAt: now }\n}\n\n/**\n * keystroke 微批判定:仅可打印字符输入(无控制键修饰)允许进入合并批。\n * 控制键/组合键必须立即 flush——它们承载交互语义(提交/删除/导航),不可延迟。\n */\nexport function isCoalescableKeystroke(key: {\n ctrl?: boolean\n meta?: boolean\n return?: boolean\n escape?: boolean\n backspace?: boolean\n delete?: boolean\n tab?: boolean\n upArrow?: boolean\n downArrow?: boolean\n leftArrow?: boolean\n rightArrow?: boolean\n}): boolean {\n return !(\n key.ctrl ||\n key.meta ||\n key.return ||\n key.escape ||\n key.backspace ||\n key.delete ||\n key.tab ||\n key.upArrow ||\n key.downArrow ||\n key.leftArrow ||\n key.rightArrow\n )\n}\n","/**\n * use-measured-cursor-origin.ts — RFC-311 物理光标绝对原点实测。\n *\n * 职责划分(三方社区共识:ink-text-input#93 / ink-ui#24 的 cursorStart API 形态 +\n * CC useDeclaredCursor 的动态布局解析思想):**组件只管行内偏移,绝对位置由布局实测**。\n * 本 hook 是 CC nodeCache 的 stock-Ink 等价物——不 fork,沿 DOMElement 公开的\n * parentNode/yogaNode 链在 layout 提交后累加出节点相对 ink-root 的绝对左上角。\n *\n * 订阅时机与 stock ink useBoxMetrics 逐行同构(reconciler resetAfterCommit:\n * ①yoga calculateLayout → ②emitLayoutListeners(本 hook 回调)→ ③onRender 写 stdout)。\n * 打字帧 y 不变、x/行偏移由调用方同帧算出;布局位移帧 origin 慢一帧、下帧追平\n * (官方 useBoxMetrics 同款语义,benign)。\n *\n * layout 订阅接线:ink 的 exports 封死深路径 import(仅导出 index),但\n * `addLayoutListener` 的实现就是往 `rootNode.internal_layoutListeners` Set 增删回调\n * (dom.js:125-134,字段在 DOMElement 上可见)——此处直接操作该字段,行为逐行同构。\n * 若未来 ink 移除该字段,退化为仅\"自身重渲后刷新\"(PromptInput 每 keystroke 重渲,\n * 覆盖面足够,仅丢\"纯兄弟位移且自身零重渲\"的极端帧)。\n */\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { isShadowModeEnabled } from '@x-otto/env'\n\nimport type { DOMElement } from 'ink'\n\nexport interface CursorOrigin {\n x: number\n y: number\n}\n\n/** dom.js DOMElement 的 layout 监听字段(stock ink 公开可见,非 exports 导出)。 */\ntype LayoutListenerHost = DOMElement & {\n internal_layoutListeners?: Set<() => void>\n}\n\n/**\n * 沿 parentNode 链累加 yoga getComputedLeft/Top,得节点相对 ink-root 的绝对左上角。\n * 纯函数(单测覆盖)。布局未就绪(无 yogaNode)返回 null。\n */\nexport function resolveAbsoluteOrigin(node: DOMElement | null | undefined): CursorOrigin | null {\n if (!node?.yogaNode) return null\n let x = 0\n let y = 0\n let cur: DOMElement | undefined = node\n while (cur?.yogaNode) {\n x += cur.yogaNode.getComputedLeft()\n y += cur.yogaNode.getComputedTop()\n cur = cur.parentNode\n }\n return { x: Math.round(x), y: Math.round(y) }\n}\n\n/** 沿 parentNode 上溯到 ink-root(useBoxMetrics 同款)。 */\nfunction findRootNode(node: DOMElement | null | undefined): LayoutListenerHost | undefined {\n let cur: DOMElement | undefined = node ?? undefined\n while (cur) {\n if (!cur.parentNode) return cur.nodeName === 'ink-root' ? (cur as LayoutListenerHost) : undefined\n cur = cur.parentNode\n }\n return undefined\n}\n\n/**\n * layout 提交后实测 ref 节点的绝对原点。返回 null = 未挂载/布局未就绪。\n * Object.is 语义短路(坐标未变不触发重渲,防测量-重渲循环)。\n */\nlet originInstanceSeq = 0\n\n/** 诊断:描述 ref 指向的节点(父链 nodeName + 首个文本内容),用于定位实例归属。 */\nfunction describeNode(node: DOMElement | null): string {\n if (!node) return 'NULL'\n const names: string[] = []\n let cur: DOMElement | undefined = node\n for (let i = 0; i < 4 && cur; i++) {\n names.push(cur.nodeName ?? '?')\n cur = cur.parentNode as DOMElement | undefined\n }\n const firstText = (node.childNodes?.[0] as { nodeValue?: string } | undefined)?.nodeValue\n return `${names.join('<')}${firstText ? `:${JSON.stringify(firstText.slice(0, 12))}` : ''}`\n}\n\nexport function useMeasuredCursorOrigin(\n ref: React.RefObject<DOMElement | null>,\n): CursorOrigin | null {\n const [origin, setOrigin] = useState<CursorOrigin | null>(null)\n // 诊断:实例 id——区分\"同一实例值在抖\"与\"多个实例各自稳定但交替写日志\"。\n const instanceIdRef = React.useRef(0)\n if (instanceIdRef.current === 0) instanceIdRef.current = ++originInstanceSeq\n const update = useCallback(() => {\n // 卸载帧的**残余回调**:React 先置 ref.current=null,但该帧的 layout 提交可能\n // 早于 effect cleanup 执行 → update 仍在订阅集中被调用一次。此时测量必然失败,\n // 走\"保留旧值\"分支虽无害,但白跑一趟且污染诊断日志。直接早退。\n if (!ref.current) return\n const next = resolveAbsoluteOrigin(ref.current)\n setOrigin((prev) => {\n // **测量失败时保留最后已知值**(实测 bug 修复,勿回退为直接置 null):\n // Agent 工作中对话流每帧重渲、yoga 反复重排,`resolveAbsoluteOrigin` 会在\n // 测量间隙短暂返回 null(节点重挂/布局未提交)。若此时把 origin 清空,\n // 消费方(PromptInput)会 `setCursorPosition(undefined)` → 物理光标回落到\n // 终端默认位置(输出末尾,即状态栏下方)→ **IME 候选框整个掉到状态栏下面**。\n // 保留旧值最坏是慢一帧追平(位置略旧),远优于光标飞走。\n // 诊断(OTTO_TUI_CURSOR_DEBUG=<file>):抖动排查——记录布局层的测量流水。\n // 若 [ORG] 行显示 x/y 在连续帧间反复变化,抖动源在**布局**(yoga 重排),\n // 而非行内几何或冻结闸。\n // RFC-345:影子态零磁盘写入——诊断日志不落盘(同 ai/stream.ts OTTO_RECORD_PROVIDER 范式)。\n const dbg = isShadowModeEnabled() ? undefined : process.env['OTTO_TUI_CURSOR_DEBUG']\n if (dbg) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('node:fs').appendFileSync(\n dbg,\n `[ORG#${instanceIdRef.current}] t=${Date.now() % 100000} ` +\n `next=${next ? `${next.x},${next.y}` : 'NULL'} ` +\n `prev=${prev ? `${prev.x},${prev.y}` : 'NULL'} ` +\n `node=${describeNode(ref.current)}\\n`,\n )\n } catch {}\n }\n if (next === null) return prev\n if (prev !== null && prev.x === next.x && prev.y === next.y) return prev\n return next\n })\n }, [ref])\n // 自身重渲后刷新(本组件 state/props 变化路径)。\n useEffect(update)\n // 根 layout 提交订阅(兄弟驱动的位移路径——memo 跳过自身重渲时仍能收到)。\n useEffect(() => {\n const root = findRootNode(ref.current)\n if (!root) return\n root.internal_layoutListeners ??= new Set()\n root.internal_layoutListeners.add(update)\n return () => {\n root.internal_layoutListeners?.delete(update)\n }\n })\n return origin\n}\n","/**\n * use-physical-cursor.ts — RFC-317 M1:物理光标锚定能力(从 PromptInput 下沉)。\n *\n * ## 为何下沉到基础组件\n * 「光标该在文本的哪个位置」是**输入组件的固有知识**——只有它知道 softWrap 折行、\n * CJK 宽字符宽度、chip 段、mask 态。此前该能力全部长在 PromptInput 里,导致其余\n * TextInput 消费方(PulseSurveyOverlay / InspectorPane / SigilPane)没有物理光标,\n * 中文输入时 IME 候选框飘到屏幕底部(与 RFC-311 之前 PromptInput 的老 bug 同型)。\n *\n * 三层职责(RFC-317):\n * - 消费方:只声明「要不要光标」(通常即 focused)\n * - **本 hook(TextInput 内部)**:位置计算 + IME 冻结闸 + 意图声明\n * - ScreenBuffer(RFC-315):写出时机(hide/show 策略、突发抑制、租约语义)\n *\n * ## 关键语义(血泪教训,改动前必读)\n * - `setCursorPosition(undefined)` **不是\"隐藏光标\"而是\"交还控制权\"**——Ink 随即\n * 让物理光标回落输出末尾(状态栏下第一列),IME 候选框跟着掉下去。故\"输入框仍\n * 渲染但暂不接管键盘\"的场景(回看态/Tips 显示)**不应**交还控制权。\n * - Ink 的光标是**每帧租约**(cursorDirty 每 render 失效):必须每帧重新声明。\n * - 冻结闸的 armed 判据是\"text 变短\"(IME preedit 擦除重填特征),**退格同样变短**\n * → 必须用按键事件显式解除武装,否则删除时光标冻结在原位。\n */\nimport { useEffect, useRef, useState } from 'react'\nimport { useCursor } from 'ink'\n\nimport { isShadowModeEnabled } from '@x-otto/env'\n\nimport { CURSOR_FREEZE_MS, cursorFreezeGate, initialCursorFreeze } from './ime-coalesce'\nimport { useMeasuredCursorOrigin } from './use-measured-cursor-origin'\n\nimport type { DOMElement } from 'ink'\nimport type { CursorFreezeState } from './ime-coalesce'\n\nexport interface PhysicalCursorInline {\n /** 光标所在行内的列偏移(已计入 CJK 宽度与前缀宽度)。 */\n column: number\n /** 光标所在行相对输入框首行的行偏移(softWrap 折行后)。 */\n lineOffset: number\n}\n\nexport interface UsePhysicalCursorOptions {\n /** 是否声明物理光标。false → 交还控制权(Ink 让光标回落输出末尾)。 */\n active: boolean\n /** 输入框根节点 ref——yoga 布局实测绝对原点用(挂稳定节点,非每帧重建的行节点)。 */\n containerRef: React.RefObject<DOMElement | null>\n /** 当前文本(用于 preedit 收缩检测)。 */\n text: string\n /** 行内几何(列 + 行偏移)——由消费组件按自身渲染结构同帧算出。 */\n inline: PhysicalCursorInline\n /** 最近一次退格/删除键时刻(解除冻结闸武装用;0 = 从未)。 */\n deleteKeyAt: number\n}\n\n/**\n * 声明物理光标位置(render 阶段同步执行——useCursor 的 useInsertionEffect 在同次\n * commit 读取该值)。\n */\nlet cursorInstanceSeq = 0\n\nexport function usePhysicalCursor({\n active,\n containerRef,\n text,\n inline,\n deleteKeyAt,\n}: UsePhysicalCursorOptions): void {\n const { setCursorPosition } = useCursor()\n const origin = useMeasuredCursorOrigin(containerRef)\n // 诊断:实例 id——多个 TextInput 同时声明光标会互相覆盖(Ink 单消费者假设)。\n const instanceIdRef = useRef(0)\n if (instanceIdRef.current === 0) instanceIdRef.current = ++cursorInstanceSeq\n\n const freezeRef = useRef<CursorFreezeState | null>(null)\n /** 上一帧输出行与其构成分量——撕裂读取检测用(见下方\"撕裂读取保护\")。 */\n const lastOutRef = useRef<{ y: number; originY: number; inlineOffset: number } | null>(null)\n const prevTextLenRef = useRef(text.length)\n const shrinkAtRef = useRef(0)\n if (text.length < prevTextLenRef.current) shrinkAtRef.current = Date.now()\n prevTextLenRef.current = text.length\n\n // 解冻 tick:冻结中(输出≠目标)若无后续 state 变化就没有 render 来落位——\n // 安排一次冻结窗后的强制重渲(仅在确有未落位目标时,正常打字零开销)。\n const [, forceSettle] = useState(0)\n\n if (active && origin) {\n const now = Date.now()\n // **解除武装的两类确定性操作**(都会让 text 变短,但都不是 preedit 震荡):\n // ① 退格/删除键——单向收缩,光标应立即跟随(IME preedit 无按键,raw TTY 直接\n // 来字符,判据互斥可靠);\n // ② 清空(len 归零)——提交/Ctrl+U/preedit 全撤销,均为确定性回归空态。\n // 真机 bug:提交时 len→0 误武装,恰与对话流增高(origin 位移)同帧,\n // 光标被冻在旧 origin 上 160ms → 候选框跳到状态栏又弹回。\n const byDeleteKey = now - deleteKeyAt < CURSOR_FREEZE_MS * 2\n const byClear = text.length === 0\n const armed = !byDeleteKey && !byClear && now - shrinkAtRef.current < CURSOR_FREEZE_MS * 2\n // **闸只吃 inline**(行内几何),origin 用**当前值**即时组装——布局位移\n // (对话流增高/面板开合/Tips/resize)永远立即跟随,不受冻结影响。\n // 见 cursorFreezeGate 头注释「只冻 inline,绝不冻 origin」。\n const prev = freezeRef.current ?? initialCursorFreeze(inline.column, inline.lineOffset, now)\n const next = cursorFreezeGate(prev, inline.column, inline.lineOffset, now, armed)\n freezeRef.current = next\n const outX = origin.x + next.x\n let outY = origin.y + next.y\n\n // **撕裂读取保护**(真机:输入框换行瞬间光标跳下一行又弹回)。\n // origin 与 inline 来自**不同时刻**:inline 是本帧渲染算出的(新行数立即生效),\n // origin 却是**上一帧布局**的实测值(yoga 布局在 effect 阶段才提交)。\n // 输入框贴底增高时二者反向变化——lineOffset +1 的同一帧,origin.y 尚未 -1 →\n // 组装出 origin(70)+inline(2)=72(多一行),下一帧 origin 更新为 69 才回到 71。\n // 实测日志:out 在 72 与 71 间跳,且 origin.y+lineOffset 恒等于 71(贴底不变式)。\n //\n // 保护:lineOffset 增长而 origin.y 未同步下降的那一帧,**沿用上一帧的输出行**\n // ——下一帧 origin 落定后自然一致。仅影响换行瞬间的单帧,不引入持续滞后。\n const prevOut = lastOutRef.current\n if (\n prevOut !== null &&\n next.y > prevOut.inlineOffset && // 行内偏移增长(新增了一行)\n origin.y === prevOut.originY // 但 origin 还是旧值 → 撕裂\n ) {\n outY = prevOut.y\n }\n lastOutRef.current = { y: outY, originY: origin.y, inlineOffset: next.y }\n setCursorPosition({ x: outX, y: outY })\n // 诊断(OTTO_TUI_CURSOR_DEBUG=<file>):候选框抖动排查。\n // 记录每帧的目标 vs 实际输出坐标——抖动 = 连续帧间 x/y 反复变化。\n // 关键维度:origin(布局实测原点,抖 = 布局在动)、inline(行内几何,\n // 抖 = 折行/窗口在动)、armed(冻结闸是否介入)、out(最终声明值)。\n // RFC-345:影子态零磁盘写入——诊断日志不落盘(同 ai/stream.ts OTTO_RECORD_PROVIDER 范式)。\n const dbg = isShadowModeEnabled() ? undefined : process.env['OTTO_TUI_CURSOR_DEBUG']\n if (dbg) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('node:fs').appendFileSync(\n dbg,\n `[ON #${instanceIdRef.current}] t=${now % 100000} out=${outX},${outY} ` +\n `target=${origin.x + inline.column},${origin.y + inline.lineOffset} ` +\n `origin=${origin.x},${origin.y} inline=${inline.column},${inline.lineOffset} ` +\n `frozenInline=${next.x},${next.y} ` +\n `armed=${armed ? 1 : 0} byDel=${byDeleteKey ? 1 : 0} byClr=${byClear ? 1 : 0} ` +\n `len=${text.length}\\n`,\n )\n } catch {}\n }\n } else {\n freezeRef.current = null\n lastOutRef.current = null\n setCursorPosition(undefined)\n // RFC-345:影子态零磁盘写入。\n const dbg = isShadowModeEnabled() ? undefined : process.env['OTTO_TUI_CURSOR_DEBUG']\n if (dbg) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('node:fs').appendFileSync(\n dbg,\n `[OFF#${instanceIdRef.current}] t=${Date.now() % 100000} active=${active ? 1 : 0} ` +\n `origin=${origin ? `${origin.x},${origin.y}` : 'NULL'} len=${text.length}\\n`,\n )\n } catch {}\n }\n }\n\n useEffect(() => {\n const s = freezeRef.current\n if (!s || (s.x === s.targetX && s.y === s.targetY)) return\n const timer = setTimeout(() => forceSettle((n) => n + 1), CURSOR_FREEZE_MS + 10)\n return () => clearTimeout(timer)\n })\n}\n","/**\n * TextInput.tsx — Ink rendering layer for text input(CC BaseTextInput port, M48-08;\n * 更名 BaseTextInput→TextInput,通用化供 Dialog 复用,见下方 mask)。\n *\n * Pure rendering: mode prefix, cursor block, placeholder, multi-line viewport,\n * **soft-wrapping**(长行按 visibleCols 折行,解决超宽文本布局溢出)。\n * 光标位置精确落在折行后的 display-line 中。\n *\n * mask:遮罩渲染(API Key 等机密输入)——渲染前把每个 grapheme 替换为 '•',\n * chip 高亮在 mask 模式下整体跳过(遮罩场景不存在 @ 引用语义)。\n *\n * Usage:\n * <TextInput value=\"hello\" offset={5} columns={80} mode=\"send\" />\n * <TextInput value={apiKey} offset={o} columns={80} mode=\"dialog\" mask />\n */\n\nimport React from 'react'\nimport { Box, Text } from 'ink'\nimport { stringWidth, CHIP_RE, parseChipKind, parsePasteChipId, findChipStartingAt } from './cursor'\nimport { usePhysicalCursor } from './use-physical-cursor'\nimport { useColors } from '../theme'\nimport type { DOMElement } from 'ink'\nimport type { ThemeColors } from '../theme'\nimport { i18next } from '../i18n'\nimport { useTabable } from '../router/Tabable'\n\nexport type InputMode = 'send' | 'bash' | 'bash_async' | 'memory' | 'steer' | 'dialog'\n\nexport interface TextInputProps {\n value: string\n offset: number\n columns: number\n mode?: InputMode\n placeholder?: string\n maxVisibleLines?: number\n /** @deprecated 使用 tabIndex 替代。向后兼容:传入时覆盖 useTabable 结果。 */\n focused?: boolean\n /** tabIndex for focus traversal(默认 -1 = 不参与 Tab 切换;0+ = 参与)。 */\n tabIndex?: number\n /** 遮罩渲染(如 API Key)——显示为 '•' 占位,不回显明文;跳过 chip 高亮解析。 */\n mask?: boolean\n /**\n * 抑制模式前缀(RFC-273 D3)——调用方**自带前缀**时传 true,避免双前缀。\n * 例:SelectDialog 的 filter 框外部已渲染 `/ ` 搜索前缀,此时 TextInput 不应再加 `> `。\n */\n noPrefix?: boolean\n /** 行内 dim ghost 补全后缀(RFC-128 Fish-like autosuggestion)。 */\n ghostSuffix?: string\n /** RFC-198 §5.3:当前失效的粘贴引用 id 集合(`PasteStore.findMissingIds` 结果)——\n * 命中的 `[@paste:#N]` chip 渲染为失效样式(错误色 + 删除线),不覆盖 `[@paste:image #N]`\n * (image refs 不参与本机制,见 cursor.ts `parsePasteChipId` 头注释)。 */\n invalidPasteIds?: ReadonlySet<number>\n /** RFC-311:根 Box 节点 ref——useMeasuredCursorOrigin 实测光标绝对原点用。\n * 挂根(稳定节点)而非光标行(行节点 key 含内容、每帧重建);光标行相对根的\n * 偏移由 computeCursorInline 同帧算出(与渲染结构逐行同源)。 */\n containerRef?: React.Ref<DOMElement>\n /**\n * RFC-317:是否声明**物理光标**(IME 候选框锚定的前提)。\n *\n * 默认 false(既有消费方行为不变)。为 true 时 TextInput 用**自身渲染期的几何\n * 真值**(softWrap 结果 + 窗口化偏移)声明光标位置——不再由上层重算同一份几何,\n * 消除双源漂移;且任何消费方挂上即得正确的候选框位置(RFC-317 下沉主旨)。\n *\n * 需要配合 containerRef(原点实测锚点)。\n */\n physicalCursor?: boolean\n /** 最近一次退格/删除键时刻——解除 IME 冻结闸武装(退格与 preedit 同样使 text\n * 变短,必须用按键事件区分,否则删除时光标冻结在原位)。 */\n deleteKeyAt?: number\n}\n\nconst graphemeSegmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })\n\n/** 多行输入可视窗口默认最大行数(超出后光标居中滚动,见 computeInputWindow)。 */\nexport const DEFAULT_MAX_VISIBLE_LINES = 10\n\nexport interface InputWindow {\n /** 窗口内第一行的 wrap 行号(0-based)。 */\n startLine: number\n /** 窗口可见行数(≤ maxVisibleLines,多行超出时恒满)。 */\n visibleRows: number\n /** 光标在窗口内的行号(0-based)。 */\n cursorLineInWindow: number\n}\n\n/**\n * 多行输入可视窗口计算(单一真源——TextInput 渲染窗口与 PromptInput 光标锚定共用,\n * 防止两处漂移导致真实光标与反白块错位)。\n *\n * 语义(RFC-300 窗口化):wrap 行数 ≤ maxVisibleLines 时全显;超出时以光标行为中心\n * 开窗(half 向下取整),窗口不满时底部对齐。\n */\nexport function computeInputWindow(\n totalLines: number,\n cursorLine: number,\n maxVisibleLines: number,\n): InputWindow {\n let startLine = 0\n let visibleRows = totalLines\n let cursorLineInWindow = cursorLine\n if (totalLines > maxVisibleLines) {\n const half = Math.floor(maxVisibleLines / 2)\n startLine = Math.max(0, cursorLine - half)\n const endLine = Math.min(totalLines, startLine + maxVisibleLines)\n if (endLine - startLine < maxVisibleLines) startLine = Math.max(0, totalLines - maxVisibleLines)\n visibleRows = maxVisibleLines\n cursorLineInWindow = cursorLine - startLine\n }\n return { startLine, visibleRows, cursorLineInWindow }\n}\n\n/** 'dialog' 模式无前缀(Dialog 单行/多行输入框,非聊天模式行首指示符)。 */\nconst MODE_PREFIX: Record<InputMode, string> = {\n send: '> ',\n bash: '! ',\n bash_async: '!! ',\n memory: '# ',\n steer: '» ',\n dialog: '',\n}\nfunction modeColor(mode: InputMode, colors: ThemeColors): string {\n return mode === 'bash' || mode === 'bash_async'\n ? colors.warning\n : mode === 'memory'\n ? colors.special\n : mode === 'steer'\n ? colors.success\n : colors.suggestion\n}\n\n/**\n * 按 display width 折行(CJK/emoji 宽字符占 2;宽度计算委托 cursor.ts stringWidth)。\n *\n * `cursorCharOffset` 是 UTF-16 code unit 偏移(即 `Cursor.offset`/JS string `.length`\n * 口径——见 `cursor.ts` `MeasuredText`/`Cursor.offset`),**不是** grapheme 簇下标。二者\n * 对 ASCII/BMP 字符数值相同(掩盖了此前的 bug),但对占 2 个 UTF-16 units 的 astral\n * 字符(绝大多数 emoji 属此类,如 U+1F62F 😯)会不同——连续两个这类 emoji 时,光标真实\n * 偏移是 0/2/4,而 grapheme 下标只有 0/1,`ci === cursorCharOffset` 永远匹配不到 2\n * (光标夹在两个 emoji 之间的合法位置),导致光标被误渲染到文本末尾。用\n * `consumedUnits`(沿 grapheme 遍历累积的 UTF-16 单元数,与 `curLine.length` 同口径)\n * 替代 `ci` 做比较,修复此类多字节 grapheme 场景。\n */\nexport function softWrap(\n text: string,\n width: number,\n cursorCharOffset: number,\n): { lines: string[]; cursorLine: number; cursorCharInLine: number } {\n if (!text) return { lines: [''], cursorLine: 0, cursorCharInLine: 0 }\n const chars = [...graphemeSegmenter.segment(text)]\n const lines: string[] = []\n let curLine = ''\n let curWidth = 0\n let cursorLine = 0\n let cursorCharInLine = 0\n let consumedUnits = 0\n\n for (let ci = 0; ci < chars.length; ci++) {\n const g = chars[ci]!\n const gW = stringWidth(g.segment)\n\n if (consumedUnits === cursorCharOffset) {\n cursorLine = lines.length\n cursorCharInLine = curLine.length\n }\n\n if (g.segment === '\\n') {\n lines.push(curLine)\n curLine = ''\n curWidth = 0\n consumedUnits += g.segment.length\n continue\n }\n\n if (curWidth + gW > width && curLine.length > 0) {\n lines.push(curLine)\n curLine = ''\n curWidth = 0\n }\n\n curLine += g.segment\n curWidth += gW\n consumedUnits += g.segment.length\n }\n\n if (cursorCharOffset >= consumedUnits) {\n cursorLine = lines.length\n cursorCharInLine = curLine.length\n }\n\n lines.push(curLine)\n return { lines, cursorLine, cursorCharInLine }\n}\n\nexport interface ChipSegment {\n chunk: string\n isChip: boolean\n kind?: string\n /** RFC-198 §5.3:paste chip 的数字 id(`parsePasteChipId` 提取);非 paste chip 或\n * image paste chip(无数字 id 语义参与失效判定)为 undefined。 */\n pasteId?: number\n}\n\n/** 按 CHIP_RE 将一行文本切为 (普通文本 | chip)[] 段。 */\nexport function parseChips(line: string): ChipSegment[] {\n CHIP_RE.lastIndex = 0\n const segments: ChipSegment[] = []\n let lastEnd = 0\n let m: RegExpExecArray | null\n while ((m = CHIP_RE.exec(line)) !== null) {\n if (m.index > lastEnd) {\n segments.push({ chunk: line.slice(lastEnd, m.index), isChip: false })\n }\n const text = m[0]\n const kind = parseChipKind(text)\n segments.push({ chunk: text, isChip: true, kind, pasteId: kind === 'paste' ? parsePasteChipId(text) : undefined })\n lastEnd = m.index + text.length\n }\n if (lastEnd < line.length) {\n segments.push({ chunk: line.slice(lastEnd), isChip: false })\n }\n return segments\n}\n\n/** chip kind → THEME 色;非 chip → undefined(终端默认前景)。 */\nexport function chipColor(kind: string | undefined, colors: ThemeColors): string | undefined {\n switch (kind) {\n case 'file':\n return colors.suggestion\n case 'agent':\n return colors.accent\n case 'skill':\n case 'mcp':\n case 'plugin':\n return colors.special\n case 'person':\n return colors.success\n case 'paste':\n return colors.inactive\n default:\n return undefined\n }\n}\n\n/** RFC-198 §5.3:segment 是否为失效的粘贴引用——kind='paste' 且提取出的 id 落在\n * `invalidPasteIds` 集合中。image paste chip(`pasteId` 为 undefined)恒非失效\n * (不参与本机制,见 `cursor.ts` `parsePasteChipId` 头注释)。 */\nfunction isInvalidPasteSegment(seg: ChipSegment, invalidPasteIds?: ReadonlySet<number>): boolean {\n return seg.kind === 'paste' && seg.pasteId !== undefined && (invalidPasteIds?.has(seg.pasteId) ?? false)\n}\n\n/** 查询光标位置所在 chip 的 kind + pasteId;不在 chip 内返回 undefined。 */\nfunction chipAtOffset(line: string, offset: number): { kind: string; pasteId?: number } | undefined {\n CHIP_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = CHIP_RE.exec(line)) !== null) {\n if (offset > m.index && offset < m.index + m[0].length) {\n const kind = parseChipKind(m[0])\n return { kind, pasteId: kind === 'paste' ? parsePasteChipId(m[0]) : undefined }\n }\n }\n return undefined\n}\n\n/**\n * RFC-212 D2:查询是否有 chip 恰好**起始于** offset(\"胖字符\"选中态判据)。薄封装\n * `cursor.ts` 的 `findChipStartingAt`(单一边界探测实现,里程碑审核修订:此前本函数\n * 自行维护一遍独立的 CHIP_RE 循环,与 `findChipAt`/`chipAtOffset` 形成三套平行逻辑,\n * 违反重要事项规则 2,现收口——本函数只负责把边界结果转成渲染层需要的 kind/pasteId)。\n */\nfunction chipStartingAt(\n line: string,\n offset: number,\n): { text: string; kind: string; pasteId?: number } | undefined {\n const chip = findChipStartingAt(line, offset)\n if (!chip) return undefined\n const kind = parseChipKind(chip.text)\n return { text: chip.text, kind, pasteId: kind === 'paste' ? parsePasteChipId(chip.text) : undefined }\n}\n\n/**\n * mask 模式:整行替换为等长 '•'——按 UTF-16 code unit 数量 1:1 替换(而非 grapheme 计数),\n * 保持与 softWrap 产出的 cursorCharInLine(code-unit 索引,见 softWrap 内 curLine.length)\n * 索引口径一致,否则光标切分位置会因代理对/组合字符错位。API Key 等遮罩场景恒为 ASCII,\n * 此简化无实际视觉损失。\n */\nfunction maskLine(line: string): string {\n return '•'.repeat(line.length)\n}\n\nexport const TextInput = React.memo(function TextInput({\n value,\n offset,\n columns,\n mode = 'send',\n placeholder = '',\n maxVisibleLines = DEFAULT_MAX_VISIBLE_LINES,\n focused: focusedProp,\n tabIndex = -1,\n mask = false,\n noPrefix = false,\n ghostSuffix,\n invalidPasteIds,\n containerRef,\n physicalCursor = false,\n deleteKeyAt = 0,\n}: TextInputProps): React.ReactElement {\n // RFC-263 M6:从静态 THEME 迁到 useColors() Context——PromptInput 常驻可见,\n // 迁移后 mode 前缀色/chip 色随 `/theme` 切换实时更新。\n const colors = useColors()\n // RFC-252 M3: 内置 useTabable。tabIndex=-1 不参与 Tab 切换(向后兼容旧用法)。\n // focusedProp 传入时覆盖 tabFocused(向后兼容 @deprecated focused prop)。\n const { focused: tabFocused } = useTabable({ tabIndex, disabled: tabIndex < 0 })\n const focused = focusedProp ?? tabFocused\n\n // RFC-273 D3:dialog 输入框补 `> ` 前缀(与主输入框 send 模式一致)——此前\n // MODE_PREFIX['dialog']='' 使所有弹窗输入框无前缀,与 PromptInput 不一致(点2)。\n // mask 模式(API Key 等机密输入)例外:加前缀无语义收益且干扰遮罩对齐。\n // 前缀(`> ` 等)是**\"此处可输入\"的可供性标记**(用户 2026-08:失焦也显示——\n // 重定稿)——聚焦态显示主色、失焦态显示灰色(inactive),语意:\"窗口还在,\n // 回来可以继续输入\"。宽度恒定(prefixW 参与 visibleCols)——折叠避免折行跳动。\n const prefixChars = noPrefix ? '' : mode === 'dialog' && !mask ? '> ' : MODE_PREFIX[mode]\n const prefixW = stringWidth(prefixChars)\n const prefix = prefixChars // 恒显示(不随 focused 隐藏),颜色由下方按 focused 区分\n const visibleCols = Math.max(20, columns - prefixW)\n\n // 光标:聚焦时常驻反色块(不闪烁),失焦时消失\n // 对照浏览器 <input> 的 | 光标——常驻不闪\n const cursorVisible = focused\n\n const {\n lines: displayLines,\n cursorLine: wrappedCursorLine,\n cursorCharInLine,\n } = React.useMemo(() => softWrap(value, visibleCols, offset), [value, visibleCols, offset])\n\n // RFC-317:物理光标锚定。内部持根 ref(原点实测),并把外部 containerRef 合并\n // 转发——消费方仍可拿到根节点(如 PromptInput 的既有用途)。\n const rootRef = React.useRef<DOMElement | null>(null)\n const mergedRootRef = React.useCallback(\n (node: DOMElement | null) => {\n rootRef.current = node\n if (typeof containerRef === 'function') containerRef(node)\n else if (containerRef) (containerRef as React.RefObject<DOMElement | null>).current = node\n },\n [containerRef],\n )\n // 行内几何取**本组件渲染期真值**(同一份 softWrap 结果 + 窗口化偏移),不由\n // 上层重算——消除双源漂移。必须在任何 early return 之前调用(hooks 规则)。\n const windowForCursor = computeInputWindow(displayLines.length, wrappedCursorLine, maxVisibleLines)\n const aboveIndicatorRows = windowForCursor.startLine > 0 ? 1 : 0\n const cursorInline = {\n // 行内列 = 前缀宽 + 光标前文本显示宽(宽字符精确,与渲染端同源)。\n column:\n prefixW +\n stringWidth((displayLines[wrappedCursorLine] ?? '').slice(0, cursorCharInLine)),\n // 光标行在根内的行偏移(顶部「…N above」指示行占第 0 行)。\n lineOffset: aboveIndicatorRows + windowForCursor.cursorLineInWindow,\n }\n usePhysicalCursor({\n active: physicalCursor && focused,\n containerRef: rootRef,\n text: value,\n inline: cursorInline,\n deleteKeyAt,\n })\n\n if (value.length === 0 && placeholder) {\n return (\n <Box ref={mergedRootRef} flexDirection='row'>\n <Text color={focused ? modeColor(mode, colors) : colors.inactive}>{prefix}</Text>\n {focused && <Text inverse={cursorVisible}> </Text>}\n <Text dimColor>{placeholder}</Text>\n </Box>\n )\n }\n\n const totalLines = displayLines.length\n const { startLine, visibleRows } = computeInputWindow(\n totalLines,\n wrappedCursorLine,\n maxVisibleLines,\n )\n const endLine = startLine + visibleRows\n\n const visibleLines = displayLines.slice(startLine, endLine)\n\n return (\n <Box ref={mergedRootRef} flexDirection='column'>\n {startLine > 0 && <Text dimColor>{` … ${startLine} ${i18next.t('scroll.viewportLinesAbove')}`}</Text>}\n {visibleLines.map((rawLine, i) => {\n const absLine = startLine + i\n const isCurrentLine = absLine === wrappedCursorLine\n // mask 模式:跳过 chip 解析(遮罩场景无 @ 引用语义),整行替换为等长 '•'——\n // 光标定位仍用原始 line 的 grapheme 索引('•' 与原字符一一对应,宽字符也按 1 格算)。\n const line = mask ? maskLine(rawLine) : rawLine\n\n if (!isCurrentLine || !focused) {\n const segs = mask ? [{ chunk: line, isChip: false }] : parseChips(line)\n return (\n <Box key={`${i}-${rawLine.slice(0, 10)}`} flexDirection='row'>\n {absLine === 0 && prefix ? (\n <Text color={focused ? modeColor(mode, colors) : colors.inactive}>{prefix}</Text>\n ) : (\n // 前缀隐藏(失焦)或续行:等宽空格占位,保持文本左边界不动。\n prefixW > 0 && <Text dimColor>{' '.repeat(prefixW)}</Text>\n )}\n {segs.length === 0 ? (\n <Text> </Text>\n ) : (\n segs.map((seg, si) => {\n const invalid = seg.isChip && isInvalidPasteSegment(seg, invalidPasteIds)\n return (\n <Text\n key={si}\n color={invalid ? colors.error : seg.isChip ? chipColor(seg.kind, colors) : undefined}\n strikethrough={invalid}\n >\n {seg.chunk}\n </Text>\n )\n })\n )}\n </Box>\n )\n }\n\n const idx = Math.max(0, Math.min(cursorCharInLine, line.length))\n\n // RFC-212 D2:\"胖字符\"选中态——光标恰好停在某 chip 的起始位置时,整个 chip\n // 作为一个整体 inverse 渲染,取代单 grapheme 光标块。渲染组合优先级(重要事项\n // 规则 8):inverse 不传 color(选中态是主视觉信号,kind 着色降级隐藏);若同时\n // 为失效 paste chip,叠加 error 色 + strikethrough(失效信号优先于选中态)。\n const selectedChip = !mask && idx < line.length ? chipStartingAt(line, idx) : undefined\n if (selectedChip) {\n const before = line.slice(0, idx)\n const after = line.slice(idx + selectedChip.text.length)\n const selectedInvalid =\n selectedChip.kind === 'paste' &&\n selectedChip.pasteId !== undefined &&\n (invalidPasteIds?.has(selectedChip.pasteId) ?? false)\n return (\n <Box key={`${i}-${rawLine.slice(0, 10)}`} flexDirection='row'>\n {absLine === 0 && prefix ? (\n <Text color={focused ? modeColor(mode, colors) : colors.inactive}>{prefix}</Text>\n ) : (\n // 前缀隐藏(失焦)或续行:等宽空格占位,保持文本左边界不动。\n prefixW > 0 && <Text dimColor>{' '.repeat(prefixW)}</Text>\n )}\n {parseChips(before).map((seg, si) => {\n const invalid = seg.isChip && isInvalidPasteSegment(seg, invalidPasteIds)\n return (\n <Text\n key={`b-${si}`}\n color={invalid ? colors.error : seg.isChip ? chipColor(seg.kind, colors) : undefined}\n strikethrough={invalid}\n >\n {seg.chunk}\n </Text>\n )\n })}\n <Text\n inverse\n color={selectedInvalid ? colors.error : undefined}\n strikethrough={selectedInvalid}\n >\n {selectedChip.text}\n </Text>\n {parseChips(after).map((seg, si) => {\n const invalid = seg.isChip && isInvalidPasteSegment(seg, invalidPasteIds)\n return (\n <Text\n key={`a-${si}`}\n color={invalid ? colors.error : seg.isChip ? chipColor(seg.kind, colors) : undefined}\n strikethrough={invalid}\n >\n {seg.chunk}\n </Text>\n )\n })}\n {ghostSuffix ? <Text dimColor>{ghostSuffix}</Text> : null}\n </Box>\n )\n }\n\n const before = line.slice(0, idx)\n const rest = line.slice(idx)\n const cursorGrapheme = rest.length > 0 ? ([...rest][0] ?? ' ') : ' '\n const after = rest.length > 0 ? rest.slice(cursorGrapheme.length) : ''\n const cursorChipInfo = !mask && rest.length > 0 ? chipAtOffset(line, idx) : undefined\n const cursorChipInvalid =\n cursorChipInfo?.kind === 'paste' &&\n cursorChipInfo.pasteId !== undefined &&\n (invalidPasteIds?.has(cursorChipInfo.pasteId) ?? false)\n\n return (\n <Box key={`${i}-${rawLine.slice(0, 10)}`} flexDirection='row'>\n {absLine === 0 && prefix ? (\n <Text color={focused ? modeColor(mode, colors) : colors.inactive}>{prefix}</Text>\n ) : (\n // 前缀隐藏(失焦)或续行:等宽空格占位,保持文本左边界不动。\n prefixW > 0 && <Text dimColor>{' '.repeat(prefixW)}</Text>\n )}\n {(mask ? [{ chunk: before, isChip: false }] : parseChips(before)).map((seg, si) => {\n const invalid = seg.isChip && isInvalidPasteSegment(seg, invalidPasteIds)\n return (\n <Text\n key={`b-${si}`}\n color={invalid ? colors.error : seg.isChip ? chipColor(seg.kind, colors) : undefined}\n strikethrough={invalid}\n >\n {seg.chunk}\n </Text>\n )\n })}\n <Text\n inverse={cursorVisible}\n color={cursorChipInvalid ? colors.error : cursorChipInfo != null ? chipColor(cursorChipInfo.kind, colors) : undefined}\n strikethrough={cursorChipInvalid}\n >\n {cursorGrapheme}\n </Text>\n {(mask ? [{ chunk: after, isChip: false }] : parseChips(after)).map((seg, si) => {\n const invalid = seg.isChip && isInvalidPasteSegment(seg, invalidPasteIds)\n return (\n <Text\n key={`a-${si}`}\n color={invalid ? colors.error : seg.isChip ? chipColor(seg.kind, colors) : undefined}\n strikethrough={invalid}\n >\n {seg.chunk}\n </Text>\n )\n })}\n {ghostSuffix ? <Text dimColor>{ghostSuffix}</Text> : null}\n </Box>\n )\n })}\n {endLine < totalLines && (\n <Text dimColor>{` … ${totalLines - endLine} ${i18next.t('scroll.viewportLinesBelow')}`}</Text>\n )}\n </Box>\n )\n})\n","/**\n * RFC-114 D1:全局命令快捷键声明式 registry,编译期常量数组。\n * 全部条目由 App.tsx 的 `app-prompt-global` FocusBus fallback 经 `matchKeyBinding` +\n * `commandHandlers` 表分发(RFC-251 Phase 4g);`smart-close` 因涉及额外状态判断而保留\n * 独立硬编码分支(useAppKeyboard 内),其余条目走纯表驱动。\n *\n * ⚠️ **本表新增条目必须同时接 handler**——`matchKeyBinding` 命中后若 `commandHandlers`\n * 查无此项会静默返回 false(按了没反应),而 `/keybindings` 面板仍会把它列为可用键位\n * (panel 只按搜索词过滤,不校验 handler 存在性)。回归门见\n * `tests/keybindings/registry.test.ts` 的「每条 binding 都有 handler」用例。\n *\n * scopes 绝大多数为 '*'(对齐调用点均容许透传的真实行为,不做静默收窄——见 RFC-114 D2\n * 修正说明);`save-draft` 是唯一例外,收窄到 ['prompt']——它与面板内的裸 's' 键会撞车,\n * 详见该条目注释。\n *\n * 不可变规则(来自 RFC-114 §3):\n * - R1:编译期常量,不提供运行时 register/unregister API。\n * - R7:数组顺序即 matchKeyBinding 的匹配优先级。\n *\n * ## 选键位前必读:三层拦截,逐层收窄\n * 一个 Ctrl+组合要真正到达 handler,须依次穿过:\n *\n * 1. **终端 App**(最外层,本程序无法干预)。Windows Terminal 会把若干 Ctrl+组合作为\n * 自身快捷键吃掉(Ctrl+V=粘贴最典型,见 toggle-edited-files 注释);Linux 下终端/\n * tmux/screen 的用户配置同样可能截获。**这一层没有编程解法**,只有\"默认用最常见的\n * 键 + 允许重绑\"——即 `keybinding_overrides`(`/keybindings` 面板或 settings.json),\n * 该覆盖表经 `matchKeyBinding` 的 overrides 形参真正作用于分发。\n *\n * 2. **行规程 / 控制台模式**。Ctrl+S 历史上是 XON/XOFF 流控的 XOFF(暂停输出、屏幕假\n * 死),但那只发生在**行规程模式**:Ink 的 `useInput` 会 `setRawMode(true)`,libuv\n * 的 `uv_tty_set_mode(RAW)` 在 Unix 下显式清掉 `IXON`\n * (`tmp.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON)`,src/unix/tty.c),Windows 下\n * 走 `ENABLE_WINDOW_INPUT`(src/win/tty.c)。故 raw 模式期间 Ctrl+S 是普通字节,\n * **不会**触发 XOFF。⚠️ 但外部编辑器等**临时退出 raw 模式**的路径下该保护消失。\n *\n * 3. **Ink 的键解析**。`parse-keypress.js` 只把 **0x01–0x1A** 认作 ctrl+letter\n * (`s.length === 1 && s <= '\\x1a'`)。Ctrl+符号(`\\`=0x1C、`]`=0x1D、`^`=0x1E、\n * `_`=0x1F)落在区间外 → `ctrl` 恒 false,`{ctrl:true, input:']'}` 形态的 predicate\n * **永不匹配**。这类键位是死键:按下静默无反应、不报错也不插字符,极难发现(草稿保存\n * 曾绑在 Ctrl+]/Ctrl+\\ 上失效两周无人察觉)。**故本表禁止使用 Ctrl+符号键位。**\n */\nimport type { KeyBinding } from './types'\n\nexport const KEY_BINDINGS: readonly KeyBinding[] = [\n // `focus-inspector`(Ctrl+G)已随 RFC-335 M6「Inspector 调试轴下线」一并移除。\n // 该条目此前是**死绑定**:Inspector 组件与其硬编码分派分支都已删除,但 binding 仍留在\n // 本表,于是 `/keybindings` 面板把它当可用键位列给用户(panel 只按搜索词过滤,不校验\n // handler 存在性),按下去却毫无反应。删除而非补 handler——被聚焦的对象本身已不存在。\n {\n commandId: 'cycle-permission-mode',\n description: '循环切换权限模式',\n predicate: { shift: true, key: 'tab' },\n scopes: '*',\n },\n {\n commandId: 'toggle-notifications',\n description: '打开/关闭通知中心',\n predicate: { ctrl: true, input: 'n' },\n scopes: '*',\n },\n {\n commandId: 'toggle-edited-files',\n description: '打开/关闭编辑文件面板',\n // 原 Ctrl+E:与终端/readline 标准 emacs 绑定(光标跳行尾)撞车——本仓输入框自身\n // 也把 Ctrl+E 实现为\"移动到行尾\"(useTextInput.ts),全局面板命令与行内编辑命令\n // 在同一个按键上互相打架(真实用户反馈)。改用 Ctrl+V:不与任何 emacs 编辑键\n // (a/b/c/d/e/f/h/k/l/r/s/u/w/x/y/_)、不与其余全局面板键(g/n/o/p/q/t/y/z)冲突,\n // 也不与终端粘贴(bracketed-paste 走转义序列,不占用 Ctrl+V 这个控制字节)冲突。\n //\n // 已知局限(Windows Terminal,2026-07 终局排查):Windows Terminal 默认把 Ctrl+V 绑定\n // 为系统级粘贴命令(微软官方文档 Terminal.PasteFromClipboard),会在到达本程序前被\n // 拦截。排查过全部候选路线均有硬伤,非本仓能单方面解决——纯 Ctrl+字母 26 个已被\n // 三层逻辑(emacs 编辑键/全局面板键/PromptInput 硬编码)占满;Ctrl+符号(如 Ctrl+\\/\n // Ctrl+],含本仓已有的 Ctrl+/)在 Ink 的 legacy 键盘解析层(0x01-0x1A 区间外)不会被\n // 识别为 ctrl:true;Ctrl+Shift+字母 在非 kitty 的 legacy 终端投递路径下与 Ctrl+字母\n // 发送完全相同的字节(Shift 信息丢失),且 Ctrl+Shift+E 恰是 VS Code 默认全局键;\n // Alt+字母 在 macOS 上是系统级重音符死键(如 Option+E)。对照同类产品 Claude Code\n // 官方文档同样承认 Ctrl+V 在 WSL/Windows 终端会被拦截,标准做法不是找一个不存在的\n // \"全平台零冲突键\",而是默认用最常见的键 + 提供可重新绑定的逃生口——Windows 用户\n // 遇到此冲突时,用 `keybinding_overrides`(/keybindings 面板或 settings.json)\n // 自行重新绑定即可,见 packages/setting/src/schema.ts:202。\n predicate: { ctrl: true, input: 'v' },\n scopes: '*',\n },\n {\n commandId: 'toggle-subagents',\n description: '打开/关闭子代理面板',\n predicate: { ctrl: true, input: 't' },\n scopes: '*',\n },\n {\n commandId: 'toggle-delegates',\n description: '打开/关闭委派面板',\n predicate: { ctrl: true, input: 'p' },\n scopes: '*',\n },\n {\n commandId: 'toggle-draft-pane',\n description: '打开/关闭草稿面板',\n predicate: { ctrl: true, input: 'o' },\n scopes: '*',\n },\n {\n commandId: 'toggle-queue-pane',\n description: '打开/关闭队列面板',\n predicate: { ctrl: true, input: 'q' },\n scopes: '*',\n },\n {\n commandId: 'smart-close',\n description: '智能关闭当前最上层面板',\n // 终局审计:Ctrl+K 同时也是 emacs kill_to_end(删除至行尾)。prompt 态下\n // App.tsx 的 app-prompt-global FocusBus fallback 对 prompt 态 Ctrl+K 跳过全局快捷键\n // 分发——emacs kill-line 优先,smart-close 仅在非 prompt 态生效。\n // 与 Claude Code 一致:Ctrl+K 仅用于 emacs 编辑,不用作全局命令。\n predicate: { ctrl: true, input: 'k' },\n scopes: '*',\n },\n {\n commandId: 'save-draft',\n description: '保存当前输入为草稿',\n // Ctrl+S 曾是 `toggle-scrollback-freeze`(冻结对话渲染以便安全选中/复制),已退役:\n // ① 冻结与「滚动回看」重复——REPLRegion 的转录快照对 `scrollLock`(offset>0)同样\n // 生效,按一下 Alt+↑ 与按 Ctrl+S 效果等价;\n // ② 选中/复制早已不依赖它——RFC-315/321 的格子选区走 `writeSelectionRepaint` 专用\n // 重绘通道,不经 React 全树重渲,冻不冻结对选区无影响。\n // 腾出的 Ctrl+S 还给「保存草稿」——跨软件通用肌肉记忆。此前草稿保存因 Ctrl+S 被占而\n // 被挤到 Ctrl+] / Ctrl+\\,那组键位在 Ink 的 legacy 解析层(只认 0x01-0x1A 为\n // ctrl+letter)根本不会被识别为 ctrl:true —— 是彻底的死键,按下静默无反应。\n //\n // 为何进本表而非留在 PromptInput 内联硬编码:**跨平台逃生口**。终端 App 可能在按键\n // 到达本程序前就截获 Ctrl+组合(Windows Terminal 对 Ctrl+V 即如此,见 toggle-edited-files\n // 的注释),本仓既定策略是「默认用最常见的键 + 允许用户重绑」。只有进了 KEY_BINDINGS\n // 才会出现在 `/keybindings` 面板与 `keybinding_overrides` 覆盖体系里。\n //\n // ⚠️ scopes 必须收窄到 'prompt'(本表首条非 '*' 记录):SubagentsPane / DelegatesPane\n // 等面板有裸 `input === 's'` 绑定,若用 '*',面板聚焦时 Ctrl+S 会命中本命令并对着空\n // 输入框弹「输入为空」toast。'prompt' 即 routeDerivedOwner 在对话路由下的取值。\n predicate: { ctrl: true, input: 's' },\n scopes: ['prompt'],\n },\n /**\n * RFC-174 M5 D5:快捷键面板入口。\n *\n * 默认选择单键方案 { ctrl: true, input: '/' }(Ctrl+/),\n * 避免与其余 KEY_BINDINGS 的 ctrl+ 字母键位冲突。\n * RFC 原设计 Ctrl+K Ctrl+S 双键序列的终端捕获可行性留待用户\n * 在真实终端环境验证——sandbox 无 pty,无法做终端兼容性测试。\n */\n {\n commandId: 'open-keybinding-panel',\n description: '打开快捷键面板:浏览、搜索、重映射、重置快捷键',\n predicate: { ctrl: true, input: '/' },\n scopes: '*',\n },\n] as const\n","import type { KeyBindingPredicate } from './types'\n\n/**\n * 判断一个按键谓词是否为\"未分配\"态——全部字段均为 undefined(等价于空对象 `{}`)。\n * 供 M3 冲突检测的\"替换\"选项和 M4 面板的\"清除快捷键\"流程使用。\n *\n * RFC-174 M3 补充:`upArrow`/`downArrow`(M1 控件导航层新增字段,见 `types.ts`)\n * 一并纳入检查——本函数编写时(M2)晚于 M1 但未同步补全,M3 实现时发现此遗漏\n * (conflict.ts 一度用本地 wrapper `isFullyUnassigned` 绕过),此处直接补全字段\n * 检查是根源修复,不引入行为变化(现有调用方在未使用这两个字段的谓词上判断结果不变)。\n */\nexport function isUnassigned(predicate: KeyBindingPredicate): boolean {\n return (\n predicate.ctrl === undefined &&\n predicate.shift === undefined &&\n predicate.meta === undefined &&\n predicate.input === undefined &&\n predicate.key === undefined &&\n predicate.upArrow === undefined &&\n predicate.downArrow === undefined\n )\n}\n\n/**\n * 解析某个 commandId 的当前生效谓词。\n * - 若 `overrides[commandId]` 存在则返回覆盖值(整体替换默认值,非深合并)。\n * - 若不存在则返回 `defaultPredicate`(编译期默认值)。\n * - 覆盖值为空对象 `{}` 时视为\"未分配\"态(`isUnassigned` 返回 true),\n * 调用方(`matchKeyBinding`/`matchControlBinding`)应在此态下恒不匹配,\n * 可供 M3/M4 的\"替换\"或\"清除快捷键\"流程使用。\n */\nexport function resolveKeyBinding(\n commandId: string,\n defaultPredicate: KeyBindingPredicate,\n overrides: Record<string, KeyBindingPredicate> | undefined,\n): KeyBindingPredicate {\n const override = overrides?.[commandId]\n if (override !== undefined) {\n return override\n }\n return defaultPredicate\n}\n","/**\n * RFC-114 D2:遍历 `bindings` 找到第一条 predicate 匹配当前 input/key\n * 且 scopes 包含 `currentScope`(或为 '*')的记录,返回其 commandId。\n *\n * 匹配规则:\n * - predicate 字段逐字段相等比较(ink Key 类型的同名字段)。\n * - `undefined` 字段视为\"不要求该字段值,任何值都匹配\"(即 `{ ctrl: true, input: 'g' }`\n * 匹配 `key.ctrl===true && key.input==='g'`,不关心 `key.shift` 是否也为 true)。\n * - 数组顺序即优先级(R7):更靠前的记录优先命中。\n */\nimport { isUnassigned, resolveKeyBinding } from './overrides'\n\nimport type { KeyBinding, KeyBindingPredicate } from './types'\n\n/**\n * Ink Key 的字段子集——matchKeyBinding / predicateMatches 实际使用的字段。\n * RFC-174 M1:新增 upArrow / downArrow(控件导航层)。\n */\nexport type MatchKey = {\n ctrl?: boolean\n shift?: boolean\n meta?: boolean\n tab?: boolean\n escape?: boolean\n return?: boolean\n upArrow?: boolean\n downArrow?: boolean\n}\n\n/**\n * 判断一条 predicate 是否匹配当前按键输入。\n * 仅检查 predicate 上存在的字段(非 undefined),不存在/undefined 的字段视为通配。\n */\nexport function predicateMatches(\n predicate: KeyBindingPredicate,\n input: string,\n key: MatchKey,\n): boolean {\n // 修饰键比较对 key 侧做真值归一(`undefined` = 未按下)。predicate 侧仍严格:\n // 显式写 `ctrl: false` 表示\"必须没按 Ctrl\",缺省才是通配。若这里用严格 `!==`,\n // 调用方传部分 Key 对象(`{ ctrl: true }`,无 meta 字段)时 `undefined !== false`\n // 会让 `meta: false` 的记录恒不匹配——一个只在部分调用点显形的假阴性。\n if (predicate.ctrl !== undefined && !!key.ctrl !== predicate.ctrl) return false\n if (predicate.shift !== undefined && !!key.shift !== predicate.shift) return false\n if (predicate.meta !== undefined && !!key.meta !== predicate.meta) return false\n if (predicate.input !== undefined && input !== predicate.input) return false\n if (predicate.key !== undefined) {\n // Map KeyBindingPredicate key names to ink Key boolean fields\n switch (predicate.key) {\n case 'tab':\n if (!key.tab) return false\n break\n case 'escape':\n if (!key.escape) return false\n break\n case 'return':\n if (!key.return) return false\n break\n }\n }\n if (predicate.upArrow !== undefined && key.upArrow !== predicate.upArrow) return false\n if (predicate.downArrow !== undefined && key.downArrow !== predicate.downArrow) return false\n return true\n}\n\n/**\n * 判断 bindings 记录的 scopes 是否包含 currentScope。\n * `'*'` 表示所有 scope 都生效。\n */\nexport function scopeMatches(scopes: KeyBinding['scopes'], currentScope: string): boolean {\n if (scopes === '*') return true\n return scopes.includes(currentScope as never)\n}\n\n/**\n * 遍历 KEY_BINDINGS 找到第一条匹配的记录。\n *\n * ## overrides 必须在这里生效(RFC-174 收尾修复)\n * RFC-174 M4/M5 交付了 `/keybindings` 面板与 `keybinding_overrides` 持久化,但**漏了\n * 运行时消费这一环**:此前本函数直接拿编译期常量 `binding.predicate` 比较,\n * `resolveKeyBinding` 的消费方只有面板展示(keybinding-panel-logic)与冲突检测\n * (conflict.ts)。表现是用户在面板里改完键位、settings.json 也确实写进去了,但按键\n * 行为分毫不变,且无任何\"需重启生效\"提示——逃生口是个空头支票。\n *\n * 这条链尤其重要:`registry.ts` 里 Ctrl+V 的注释向 Windows 用户承诺\"被终端拦截时用\n * keybinding_overrides 自行重绑即可\",不接这一环该承诺无法兑现。\n *\n * 覆盖语义与面板层严格一致(都走 `resolveKeyBinding`):整体替换而非深合并;\n * 覆盖值为空对象 `{}` 时 `isUnassigned` 为真,此时该命令**恒不匹配**(用户主动清除了\n * 快捷键),故需显式跳过——否则空谓词会退化成\"全字段通配\"匹配一切按键。\n *\n * @param bindings - KEY_BINDINGS 数组\n * @param currentScope - 当前的 FocusOwner(如 'prompt'、'retry'、'toast')\n * @param input - useInput 的 input 字符串\n * @param key - useInput 的 key 对象\n * @param overrides - 用户覆盖表(`settings.keybinding_overrides`);省略时用编译期默认值\n * @returns 匹配到的 commandId,无匹配时返回 undefined\n */\nexport function matchKeyBinding(\n bindings: readonly KeyBinding[],\n currentScope: string,\n input: string,\n key: MatchKey,\n overrides?: Record<string, KeyBindingPredicate>,\n): string | undefined {\n for (const binding of bindings) {\n if (!scopeMatches(binding.scopes, currentScope)) continue\n const predicate = resolveKeyBinding(binding.commandId, binding.predicate, overrides)\n // 用户清除了该快捷键(覆盖为 `{}`)→ 恒不匹配。不可省:空谓词全字段 undefined,\n // predicateMatches 会视作全通配从而匹配任意按键。\n if (isUnassigned(predicate)) continue\n if (predicateMatches(predicate, input, key)) {\n return binding.commandId\n }\n }\n return undefined\n}\n","/**\n * RFC-174 M1 D1:控件导航默认值表。\n * RFC-220 M1:追加 Tab/Shift+Tab 作为 list-nav-down/list-nav-up 的等价触发键。\n *\n * 收编\"列表选中项移动\"这一语义高度相似的控件级键位映射。\n * 物理拆分为独立表(非并入 KEY_BINDINGS)——全局命令层 scope 均为 '*' 的假设长期成立,\n * 控件层引入 FocusOwner 精确 scope 是新语义,物理拆分保持既有代码路径零改动。\n *\n * 匹配规则:\n * - 同一 commandId 可有多条记录(表示 OR 语义:任一 predicate 命中即匹配)。\n * 例如 list-nav-up 有三条记录:{upArrow: true} OR {input: 'k'} OR {shift+key:'tab'}。\n * 这与 selectDialogKey/questionsDialogKey 既有的 `key.upArrow || input === 'k'`\n * 同构——收编后语义不变。\n * - 数组顺序即优先级(同 matchKeyBinding 的 R7 规则)。\n * - 每条记录对应一个 KeyBindingPredicate(复用 types.ts,RFC-174 M1 扩展了\n * upArrow/downArrow 字段)。\n */\nimport { predicateMatches } from './match'\nimport { isUnassigned, resolveKeyBinding } from './overrides'\n\nimport type { KeyBinding, KeyBindingPredicate } from './types'\nimport type { MatchKey } from './match'\n\n/**\n * 控件导航命令默认值表(编译期常量)。\n *\n * list-nav-up / list-nav-down 覆盖全部列表选中项移动面板。\n * 箭头键(upArrow/downArrow)全域生效——这些物理按键在所有面板中语义一致。\n * vim 备用键(k/j)仅在明确不会冲突的面板生效——background 面板中 k=kill\n * 是预存的上下文动作键(第四类),不收编到控件导航层。\n *\n * select-dialog-logic / questions-dialog-logic 的 matchControlBinding 调用\n * 不传 currentScope(undefined),因此 scope 检查被跳过,绑定在对话框\n * 上下文中均生效(与收编前 `key.upArrow || input==='k'` 完全等价)。\n *\n * RFC-220 M1:Tab/Shift+Tab 记录的 scopes 精确限定为类别 A 的 7 个 App-keyboard owner\n * (editedFiles/subagents/taskFocus/background/draft/queue/schedule)——不含 select/\n * questions(它们的 matchControlBinding 调用不传 scope,天然跳过检查,写不写都不影响),\n * 不用 '*'(防御性排除 prompt/retry/errorRetry/toast 四个 owner——这四者的 handler 从\n * 不调用 matchControlBinding,理论上 '*' 也不会误触,但显式列出 scope 是本仓既有纪律,\n * 对齐 k/j 两条记录已采用的精确 scopes 风格,见 RFC-220 D1a)。\n */\nexport const CONTROL_BINDINGS: readonly KeyBinding[] = [\n {\n commandId: 'list-nav-up',\n description: '列表选中项上移',\n predicate: { upArrow: true },\n scopes: '*',\n },\n {\n commandId: 'list-nav-up',\n description: '列表选中项上移(vim k)',\n // `ctrl: false` 不可省:predicate 里缺省的字段是**通配**(见 predicateMatches),\n // 故 `{ input: 'k' }` 会把 **Ctrl+K** 也算作 vim-k。面板聚焦时 Ctrl+K(smart-close\n // 关闭面板)便被面板的 list-nav-up 抢先消费并 `return true`,全局命令层再也收不到——\n // 实测表现为「任务面板展开时 Ctrl+K 只把选中项上移,关不掉面板」。meta 同理。\n predicate: { input: 'k', ctrl: false, meta: false },\n // 排除 background:k 在该面板是 kill 命令(第四类上下文动作键,不收编)\n scopes: ['editedFiles', 'subagents', 'taskFocus', 'draft', 'queue', 'schedule', 'select', 'questions'],\n },\n {\n commandId: 'list-nav-down',\n description: '列表选中项下移',\n predicate: { downArrow: true },\n scopes: '*',\n },\n {\n commandId: 'list-nav-down',\n description: '列表选中项下移(vim j)',\n // ctrl/meta 显式排除,理由同 vim-k 那条(缺省字段=通配,会吞 Ctrl+J)。\n predicate: { input: 'j', ctrl: false, meta: false },\n // 排除 background:与 k 对称处理,避免未来出现 j 语义冲突\n scopes: ['editedFiles', 'subagents', 'taskFocus', 'draft', 'queue', 'schedule', 'select', 'questions'],\n },\n] as const\n\n/**\n * RFC-174 M1 D1:检查 commandId 对应的控件级命令是否匹配当前按键。\n *\n * 对齐 matchKeyBinding 的调用惯例——遍历 CONTROL_BINDINGS,找到第一条\n * predicate 匹配 + scope 匹配的记录,返回其 commandId 与目标 commandId 比对。\n * 同一 commandId 可注册多条记录(OR 语义),匹配逻辑通过 for-loop 天然支持:\n * 遍历 CONTROL_BINDINGS,按数组顺序优先命中(同 R7 规则)。\n *\n * @param commandId - 目标命令(如 'list-nav-up')\n * @param input - useInput 的 input 字符串\n * @param key - useInput 的 key 对象\n * @param currentScope - 可选:当前 FocusOwner(未提供时跳过 scope 检查)\n * @returns 该次按键是否匹配目标命令\n */\nexport function matchControlBinding(\n commandId: string,\n input: string,\n key: MatchKey,\n currentScope?: string,\n overrides?: Record<string, KeyBindingPredicate>,\n): boolean {\n for (const binding of CONTROL_BINDINGS) {\n if (currentScope !== undefined && !scopeMatches(binding.scopes, currentScope)) continue\n const predicate = resolveKeyBinding(binding.commandId, binding.predicate, overrides)\n // 覆盖为 `{}`(用户清除快捷键)→ 跳过,理由同 matchKeyBinding:空谓词会全通配。\n if (isUnassigned(predicate)) continue\n if (predicateMatches(predicate, input, key)) {\n return binding.commandId === commandId\n }\n }\n return false\n}\n\n/**\n * 判断 bindings 记录的 scopes 是否包含 currentScope。\n * `'*'` 表示所有 scope 都生效。\n */\nfunction scopeMatches(scopes: KeyBinding['scopes'], currentScope: string): boolean {\n if (scopes === '*') return true\n return scopes.includes(currentScope as never)\n}\n","/**\n * RFC-174 M3 D3:运行时冲突检测。\n *\n * checkConflict 查询两张表(全局命令层 KEY_BINDINGS + 控件导航层 CONTROL_BINDINGS)\n * 在给定 scope 下的全部当前生效谓词(含 keybinding_overrides 覆盖态),命中即返回\n * 冲突的 commandId。\n *\n * 关键场景(RFC 明确要求):\n * - 单向冲突:B 从未被覆盖,A 改成 B 的默认键位 → 必须检测到 B 的默认值。\n * - 未分配态跳过:B 被覆盖为空对象 {}(unassigned),A 用任何键位均不冲突。\n * - 跨 scope 放行:不同 scope 下相同键位不算冲突。\n */\nimport type { KeyBindingPredicate } from './types'\nimport { KEY_BINDINGS } from './registry'\nimport { CONTROL_BINDINGS } from './control-registry'\nimport { resolveKeyBinding, isUnassigned } from './overrides'\nimport { scopeMatches } from './match'\n\n/**\n * 检测新谓词 `newPredicate` 在给定 `scope` 下是否与已有命令冲突。\n *\n * 遍历两张表(KEY_BINDINGS + CONTROL_BINDINGS)的全部记录,排除 `commandId` 自身,\n * 对每条记录通过 `resolveKeyBinding` 算出当前生效谓词(含覆盖态),比较其是否等于\n * `newPredicate`。\n *\n * - 跳过 `isUnassigned` 为 true 的记录(未分配的命令不可能冲突)。\n * - 跳过 `scopeMatches` 不匹配的记录(不同 scope 下相同键位不冲突)。\n * - 谓词比较使用语义相等:每个字段逐字段 `===`,`undefined` 与 `undefined` 视为相等\n * (例如 `{ctrl:true, input:'g'}` 与 `{ctrl:true, input:'g', shift:undefined}` 视为相同)。\n *\n * @param commandId - 当前正在编辑的命令 ID(排除自身)\n * @param newPredicate - 用户想要设置的新谓词\n * @param scope - 当前操作的 scope(如 'prompt'、'*' 等)\n * @param overrides - 用户覆盖表(同 `keybinding_overrides`,可选)\n * @returns 冲突时返回 `{ conflictsWith: string }`,无冲突返回 `null`\n */\nexport function checkConflict(\n commandId: string,\n newPredicate: KeyBindingPredicate,\n scope: string,\n overrides: Record<string, KeyBindingPredicate> | undefined,\n): { conflictsWith: string } | null {\n // 合并两张表:全局命令层 + 控件导航层\n const allBindings = [...KEY_BINDINGS, ...CONTROL_BINDINGS]\n\n for (const binding of allBindings) {\n // 排除自身\n if (binding.commandId === commandId) continue\n\n // 算出该记录的\"当前生效谓词\"(含覆盖态——即使从未被覆盖,也是编译期默认值)\n const effectivePredicate = resolveKeyBinding(\n binding.commandId,\n binding.predicate,\n overrides,\n )\n\n // 未分配态的命令不可能与任何键位冲突\n if (isUnassigned(effectivePredicate)) continue\n\n // scope 不匹配的记录不参与冲突判断\n if (!scopeMatches(binding.scopes, scope)) continue\n\n // 比较当前生效谓词是否与新谓词语义等价\n if (predicatesEqual(effectivePredicate, newPredicate)) {\n return { conflictsWith: binding.commandId }\n }\n }\n\n return null\n}\n\n/**\n * 比较两个谓词语义是否等价——逐字段 `===` 比较。\n * `undefined` 与 `undefined` 视为相等,因此部分字段缺省的谓词与显式 `undefined` 的谓词等价。\n *\n * 例:`{ctrl:true, input:'g'}` 与 `{ctrl:true, input:'g', shift:undefined}` → true\n */\nfunction predicatesEqual(a: KeyBindingPredicate, b: KeyBindingPredicate): boolean {\n return (\n a.ctrl === b.ctrl &&\n a.shift === b.shift &&\n a.meta === b.meta &&\n a.input === b.input &&\n a.key === b.key &&\n a.upArrow === b.upArrow &&\n a.downArrow === b.downArrow\n )\n}\n\n// ─── 编译期/模块加载期断言 ───────────────────────────────────────────\n\n/**\n * 检查 KEY_BINDINGS 与 CONTROL_BINDINGS 之间是否有重复的 commandId。\n * 模块加载期即执行——重复则 throw,阻止程序继续运行。\n *\n * 注意:CONTROL_BINDINGS 内部允许同一 commandId 有多条记录(OR 语义,如 list-nav-up\n * 有 upArrow 和 input:'k' 两条)。本断言仅检查跨表冲突——即 KEY_BINDINGS 中已有的\n * commandId 不应在 CONTROL_BINDINGS 中以不同语义出现,反之亦然。\n */\nfunction assertUniqueCommandIds(): void {\n const keyIds = new Set(KEY_BINDINGS.map((b) => b.commandId))\n const ctrlIds = new Set(CONTROL_BINDINGS.map((b) => b.commandId))\n\n for (const id of keyIds) {\n if (ctrlIds.has(id)) {\n throw new Error(\n `[keybindings] duplicate commandId '${id}' found across KEY_BINDINGS and CONTROL_BINDINGS`,\n )\n }\n }\n}\n\nassertUniqueCommandIds()\n","/**\n * key-dispatch.ts — Router 键盘分派的共享纯函数(RFC-304 D4/F1 修订)。\n *\n * 从 Router.tsx 抽取 handleKeyIntercept/handleKeyDefault 的 Tab 分支为纯函数,\n * 供 Router 与 TabScope 共用同一份实现——杜绝两处漂移(TabScope 复刻不全导致\n * onTab/onEscape/onAction 静默死回调的教训,见 RFC-304 §8.2 F1)。\n *\n * 抽取边界(§1.2 carve-out:零行为变化):\n * - dispatchIntercept = Router.handleKeyIntercept 逐字等价\n * - dispatchTabDefault = Router.handleKeyDefault 的 Tab 分支逐字等价(不含 Esc/goBack\n * 导航段——那是 Router 的导航栈语义,TabScope 无导航栈,由调用方自行处理)\n *\n * 两段语义(对齐 RFC-281 D1/D3):\n * - 拦截段:代理聚焦元素的 onTab/onEscape/onAction(对标浏览器 event target listener)\n * - Tab 默认段:无人拦截时 Tab/Shift+Tab 在路由内循环焦点(对标浏览器 default action)\n */\nimport type { TabableRegistry } from './Tabable'\n\n/** RouterKey 的纯数据子集(无 Ink Key 依赖,便于 TabScope 复用与测试)。 */\nexport interface RouterKeyLike {\n tab?: boolean\n shift?: boolean\n escape?: boolean\n return?: boolean\n upArrow?: boolean\n downArrow?: boolean\n leftArrow?: boolean\n rightArrow?: boolean\n ctrl?: boolean\n meta?: boolean\n}\n\n/**\n * 拦截段:只查聚焦元素声明的 onTab/onEscape/onAction,命中且返回 true 才消费。\n * 顺序不可颠倒(onTab → onEscape → onAction,与 Router.tsx 原实现逐字一致)。\n */\nexport function dispatchIntercept(\n registry: TabableRegistry,\n activeId: string | null,\n key: RouterKeyLike,\n): boolean {\n if (!activeId) return false\n const entry = registry.get(activeId)\n if (!entry) return false\n\n if (key.tab) {\n if (entry.onTab && entry.onTab(key.shift ?? false)) return true\n return false\n }\n if (key.escape) {\n if (entry.onEscape && entry.onEscape()) return true\n return false\n }\n if (key.return) {\n if (entry.onAction && entry.onAction()) return true\n return false\n }\n return false\n}\n\n/**\n * Tab 默认段(last-resort):Tab/Shift+Tab 在 routePath 内的 Tabable 间循环焦点。\n * Enter 无默认行为。Esc 无默认行为(Router 的 Esc→goBack 是导航段,不在此)。\n *\n * 与原 Router.handleKeyDefault 的 Tab 分支逐字等价:\n * - 无当前焦点时只有正向 Tab 落到首个 Tabable(Shift+Tab 分支无 getInitialFocus 兜底)\n */\nexport function dispatchTabDefault(\n registry: TabableRegistry,\n activeId: string | null,\n routePath: string,\n setActiveId: (id: string | null) => void,\n key: RouterKeyLike,\n): boolean {\n if (key.tab) {\n const current = activeId\n const tabbable = registry.getTabable(routePath)\n if (tabbable.length === 0) return false\n if (current) {\n const nextId = key.shift\n ? registry.prev(current, routePath)\n : registry.next(current, routePath)\n if (nextId) setActiveId(nextId)\n } else if (!key.shift) {\n // 无当前焦点时只有正向 Tab 会落到首个 Tabable(保持拆分前语义)\n const first = registry.getInitialFocus(routePath)\n if (first) setActiveId(first)\n }\n return true\n }\n\n return false\n}\n","/**\n * router-ref.ts — 全局 router 引用(供 React 树外的 bootstrap 层使用)。\n * 对标 Vue Router 的全局 router 实例。\n * \n * Router 组件挂载时调用 setRouterRef 注册;卸载时清空。\n * bootstrap/handle-modal.ts 通过 getRouterRef() 获取 navigate/goBack。\n */\n\nexport interface RouterNavigateOptions {\n replace?: boolean\n state?: unknown\n reset?: boolean\n}\n\nexport interface RouterNavigateFunction {\n (to: string, opts?: RouterNavigateOptions): void\n (delta: number): void\n}\n\nexport interface RouterRef {\n navigate: RouterNavigateFunction\n goBack: () => void\n /** 按路径确保栈中存在一个条目(不重复 push)——瞬态 overlay 显示用。 */\n ensure: (path: string) => void\n /**\n * 按路径移除栈中条目(RFC-280 D1)——瞬态 overlay 自动消失用。\n * 与 goBack 的区别:不要求目标在栈顶,故 toast 定时器到点时不会误关用户已打开的面板。\n */\n dismiss: (path: string) => void\n currentPath: string\n /**\n * 只读路由栈快照(RFC-280 M4)——供 `syncOverlayRoute` 做「已在栈中则不重复 navigate」\n * 判重。只暴露 path 字段,避免树外代码依赖 HistoryEntry 的内部结构(state/lastFocusId)。\n */\n stack: readonly { path: string }[]\n}\n\nlet routerRef: RouterRef | null = null\n\nexport function setRouterRef(ref: RouterRef | null): void {\n routerRef = ref\n}\n\nexport function getRouterRef(): RouterRef | null {\n return routerRef\n}\n","/**\n * Router.tsx — 路由栈 + 统一键盘调度。\n *\n * 职责:\n * 1. 路由历史栈管理(navigate / goBack / replace)\n * 2. 统一键盘拦截(Tab / Shift+Tab / Enter / Esc / ↑↓)\n * 3. 焦点恢复(goBack 时还原前一路由的聚焦位置)\n * 4. 向下注入 RouteContext(Tabable 用于确定注册的 routePath)\n *\n * 焦点锁规则:\n * Tab 只在当前栈顶路由(currentPath)注册的 Tabable 间循环——modal 路由天然形成焦点锁,\n * 不需要显式 FocusGroup。\n */\nimport React, { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react'\nimport { TabableRegistry, RouteContext } from './Tabable'\nimport { useFocusBus, PRIORITY_ROUTER_INTERCEPT, PRIORITY_ROUTER_DEFAULT } from './FocusBus'\nimport { dispatchIntercept, dispatchTabDefault } from './key-dispatch'\nimport { setRouterRef } from './router-ref'\nimport type { RouteContextValue } from './Tabable'\n\n// ═══════════════════ 类型 ═══════════════════\n\ninterface HistoryEntry {\n path: string\n params: Record<string, string>\n state?: unknown\n /** goBack 恢复用 */\n lastFocusId?: string\n}\n\ninterface HistoryState {\n entries: HistoryEntry[]\n index: number\n}\n\nexport interface NavigateOptions {\n /** Replace the current history entry, matching Web Router/History replace semantics. */\n replace?: boolean\n /** Opaque per-location state, matching Web Router location state semantics. */\n state?: unknown\n /**\n * TUI MemoryRouter extension: reset the in-memory stack to exactly the target location.\n * Use only for workflow completion / returning focus to PromptInput, not for local nested-route close.\n */\n reset?: boolean\n}\n\nexport interface NavigateFunction {\n (to: string, opts?: NavigateOptions): void\n (delta: number): void\n}\n\nexport interface RouterContextValue {\n /** 路由栈 */\n stack: readonly HistoryEntry[]\n /** 栈顶路由路径 */\n currentPath: string\n /** 当前聚焦 Tabable ID */\n activeId: string | null\n /** Web Router 风格导航:navigate(to, options) / navigate(delta)。 */\n navigate: NavigateFunction\n /** pop 栈顶;兼容别名,等价 navigate(-1)。 */\n goBack: () => void\n /**\n * 按路径确保栈中存在一个条目(不重复 push)——瞬态 overlay 显示用(RFC-280 D1)。\n */\n ensure: (path: string) => void\n /**\n * 按路径移除栈中条目(不要求它在栈顶)——瞬态 overlay 自动消失/程序化关闭用(RFC-280 D1)。\n *\n * 为何不能用 goBack:瞬态浮层(如 toast 的 2s 自动消失定时器)到点时,用户可能已经打开了\n * 别的面板 → 该浮层已不在栈顶,`goBack()` 会错误地弹掉**用户正在看的那个**。\n *\n * 语义:\n * - 移除**最靠栈顶的** path 匹配条目\n * - path 不在栈中 → no-op(幂等,容忍重复/竞态调用)\n * - 移除的是栈顶 → 等价 goBack(含焦点恢复)\n * - 移除的是中间条目 → 只抽走该条目,currentPath 与栈顶焦点不变\n * - 栈长度 ≤1 时不移除(根路由 '/' 永不被弹出)\n */\n dismiss: (path: string) => void\n /** 编程式聚焦 */\n focus: (id: string) => void\n /** 键盘调度(外层 useInput 调用,返回 true=已消费) */\n handleKey: (input: string, key: Record<string, unknown>) => boolean\n}\n\nconst RouterContext = createContext<RouterContextValue | null>(null)\n\nexport function useRouter(): RouterContextValue {\n const ctx = useContext(RouterContext)\n if (!ctx) throw new Error('useRouter() must be used inside <Router>')\n return ctx\n}\n\n/**\n * useOptionalRouter — 不在 <Router> 内时返回 null,不抛错。\n * 供既能在生产(有 Router)又能 standalone 渲染(测试/独立弹层)的组件降级使用:\n * 无 router 时不做路由派生/导航,行为退化为纯本地状态。\n */\nexport function useOptionalRouter(): RouterContextValue | null {\n return useContext(RouterContext)\n}\n\nexport function useRoute<S = unknown>(): { path: string; params: Record<string, string>; state: S } {\n const { stack } = useRouter()\n const top = stack[stack.length - 1]\n return top ? { path: top.path, params: top.params, state: top.state as S } : { path: '/', params: {}, state: undefined as S }\n}\n\n/**\n * usePrefixRouteState — 读取前缀路由「基路径」条目携带的 state(RFC-262 嵌套路由)。\n *\n * 前缀路由(如 `/viewer/file`)在导航到子路由(`/viewer/file/find`)时会 push 一个不带\n * state 的新栈顶条目——此时 `useRoute()`(读栈顶)拿到的 state 是 undefined。prefix route\n * 的 wrapper 组件必须改用本 hook,从栈中就近向下找到基路径条目的 state,否则子路由激活时\n * wrapper 会因 `req` 为空而整体卸载(find toolbar 连同查看器一起消失)。\n */\nexport function usePrefixRouteState<S = unknown>(basePath: string): S {\n const { stack } = useRouter()\n for (let i = stack.length - 1; i >= 0; i--) {\n const entry = stack[i]!\n if (entry.path === basePath && entry.state !== undefined) return entry.state as S\n }\n return undefined as S\n}\n\n// ═══════════════════ 路径匹配 ═══════════════════\n\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const pp = pattern.split('/')\n const pp2 = path.split('/')\n if (pp.length !== pp2.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < pp.length; i++) {\n const seg = pp[i]!\n const val = pp2[i]!\n if (seg.startsWith(':')) {\n params[seg.slice(1)] = val\n } else if (seg !== val) {\n return null\n }\n }\n return params\n}\n\n// ═══════════════════ Router ═══════════════════\n\ninterface RouterProps {\n children: React.ReactNode\n initialPath?: string\n}\n\nexport function Router({ children, initialPath = '/' }: RouterProps) {\n const registry = useMemo(() => new TabableRegistry(), [])\n const [history, setHistory] = useState<HistoryState>(() => ({\n entries: [{ path: initialPath, params: {} }],\n index: 0,\n }))\n const [activeId, setActiveId] = useState<string | null>(null)\n const activeIdRef = useRef<string | null>(null)\n activeIdRef.current = activeId\n\n const currentEntry = history.entries[history.index]\n const currentPath = currentEntry?.path ?? initialPath\n const stack = history.entries.slice(0, history.index + 1)\n\n // 树外 RouterRef 的实时读取源。\n // 树外调用方(bootstrap/handle-modal.ts 等)惯用 `const router = getRouterRef()!` 捕获一次,\n // 随后跨 await 复用。若 RouterRef 的 currentPath/stack 绑定到捕获时刻的 `stack` 闭包,\n // 捕获后的任何 navigate 都不会反映到该快照上——守卫 `router.currentPath !== X` 恒真,\n // 导致 async 弹窗(/install 的 dry-run 探测)resolve 后提前 return、永远停在 loading 态。\n // 故这两个字段必须经 ref 读当前值,而非捕获值。\n const stackRef = useRef(stack)\n stackRef.current = stack\n\n // ─── onFocusFallback(Tabable 卸载时调用) ───\n const onFocusFallback = useCallback((nextId: string | null) => {\n setActiveId(nextId)\n }, [])\n\n const restoreFocusFor = useCallback((entry: HistoryEntry | undefined) => {\n setTimeout(() => {\n if (entry?.lastFocusId) {\n setActiveId(entry.lastFocusId)\n } else {\n setActiveId(registry.getInitialFocus(entry?.path ?? '/'))\n }\n }, 0)\n }, [registry])\n\n const focusInitialFor = useCallback((path: string) => {\n setTimeout(() => {\n setActiveId(registry.getInitialFocus(path))\n }, 0)\n }, [registry])\n\n // ─── navigate ───\n const navigate = useCallback<NavigateFunction>((to: string | number, opts?: NavigateOptions) => {\n if (typeof to === 'number') {\n setHistory((prev) => {\n if (to === 0 || prev.entries.length === 0) return prev\n const nextIndex = prev.index + to\n if (nextIndex < 0 || nextIndex >= prev.entries.length) return prev\n restoreFocusFor(prev.entries[nextIndex])\n return { ...prev, index: nextIndex }\n })\n return\n }\n\n const loc: HistoryEntry = { path: to, params: {}, state: opts?.state }\n\n setHistory((prev) => {\n if (opts?.reset) {\n return { entries: [loc], index: 0 }\n }\n\n const currentActiveId = activeIdRef.current\n const entries = [...prev.entries]\n if (entries.length > 0 && currentActiveId) {\n const current = entries[prev.index]\n if (current) {\n registry.rememberFocus(current.path, currentActiveId)\n entries[prev.index] = { ...current, lastFocusId: currentActiveId }\n }\n }\n\n if (opts?.replace && entries.length > 0) {\n entries[prev.index] = loc\n return { entries, index: prev.index }\n }\n\n const nextEntries = [...entries.slice(0, prev.index + 1), loc]\n return { entries: nextEntries, index: nextEntries.length - 1 }\n })\n\n // 新路由激活后聚焦(延迟一帧等 Tabable 注册完成)\n focusInitialFor(to)\n }, [focusInitialFor, registry, restoreFocusFor])\n\n // ─── goBack ───\n const goBack = useCallback(() => {\n navigate(-1)\n }, [navigate])\n\n // ─── ensure(RFC-280 D1:按 path 原子确保存在,防同 tick 重复 push)───\n const ensure = useCallback((path: string) => {\n setHistory((prev) => {\n const activeEntries = prev.entries.slice(0, prev.index + 1)\n if (activeEntries.some((entry) => entry.path === path)) return prev\n const currentActiveId = activeIdRef.current\n const entries = [...prev.entries]\n const current = entries[prev.index]\n if (current && currentActiveId) {\n registry.rememberFocus(current.path, currentActiveId)\n entries[prev.index] = { ...current, lastFocusId: currentActiveId }\n }\n const nextEntries = [...entries.slice(0, prev.index + 1), { path, params: {} }]\n focusInitialFor(path)\n return { entries: nextEntries, index: nextEntries.length - 1 }\n })\n }, [focusInitialFor, registry])\n\n // ─── dismiss(RFC-280 D1:按 path 移除,不要求在栈顶)───\n const dismiss = useCallback((path: string) => {\n setHistory((prev) => {\n // I4:根路由永不被弹出——栈长 ≤1 时任何 dismiss 都是 no-op。\n if (prev.entries.length <= 1) return prev\n // 从当前 active stack 顶向下找最靠栈顶的匹配条目;forward entries 不参与可见性关闭。\n let idx = -1\n for (let i = prev.index; i >= 0; i--) {\n if (prev.entries[i]!.path === path) { idx = i; break }\n }\n // I3:不在栈中 / 根条目 → 幂等 no-op,绝不误弹其他条目。\n if (idx <= 0) return prev\n\n const entries = [...prev.entries.slice(0, idx), ...prev.entries.slice(idx + 1)]\n const nextIndex = idx <= prev.index ? Math.max(0, prev.index - 1) : prev.index\n\n // 只有移除当前栈顶才需要迁移焦点(等价 goBack);抽中间条目时 currentPath 不变。\n if (idx === prev.index) {\n restoreFocusFor(entries[nextIndex])\n }\n return { entries, index: Math.min(nextIndex, entries.length - 1) }\n })\n }, [restoreFocusFor])\n\n // ─── focus ───\n const focus = useCallback((id: string) => { setActiveId(id) }, [])\n\n // ─── 键盘调度(纯函数,不由 Router 自己 useInput) ───\n // 不再用 useInput——改为暴露 handleKey 给外层唯一 useInput 调用。\n // 这样聚焦元素(Input/List)的 useInput 先处理按键,\n // 不消费时才到 router.handleKey——等价浏览器事件冒泡。\n // ─── RFC-281 D1/D3:键盘调度拆为「拦截器分派段」与「默认导航段」 ───\n //\n // 拆分动机(RFC-281 §1):原实现把两种语义完全不同的职责焊在同一优先级 50 上——\n // ① 拦截器分派 = 代理聚焦元素的意图(对标浏览器 event target 的 listener)→ 应高优先级\n // ② 默认导航 = 无人处理时的兜底(对标浏览器 default action)→ 应**最低**优先级\n // 结果是\"默认行为跑在具体处理器之前\",组件在 FALLBACK(10) 层的 Esc/Tab 处理永远\n // 收不到键(Router@50 先 goBack 并 return true 截断传播),\"内部阻止冒泡\"不可能成立。\n //\n // 切分判据(D3):读 activeIdRef + registry.get() 的分支 → 拦截段;\n // 操作 stack / setActiveId 做导航的分支 → 默认段。\n\n type RouterKey = {\n tab?: boolean; shift?: boolean; escape?: boolean; return?: boolean\n upArrow?: boolean; downArrow?: boolean; leftArrow?: boolean; rightArrow?: boolean\n ctrl?: boolean; meta?: boolean\n }\n\n // RFC-304 D4/F1:键盘分派抽到 router/key-dispatch.ts 共享纯函数(Router 与 TabScope\n // 共用同一份实现防漂移)——拦截段/默认段语义与拆分前逐字等价,行为零变化。\n /** 拦截段:只查聚焦元素声明的 onTab/onEscape/onAction,命中且返回 true 才消费。 */\n const handleKeyIntercept = useCallback((_input: string, key: RouterKey): boolean => {\n return dispatchIntercept(registry, activeIdRef.current, key)\n }, [registry])\n\n /** 默认段(last-resort):Tab 路由内焦点循环 / Esc 逐层回退。Enter 无默认行为。 */\n const handleKeyDefault = useCallback((_input: string, key: RouterKey): boolean => {\n if (dispatchTabDefault(registry, activeIdRef.current, currentPath, setActiveId, key)) {\n return true\n }\n\n if (key.escape) {\n if (stack.length > 1) { goBack(); return true }\n return false // 根路由不消费,透传给外层\n }\n\n return false\n }, [registry, currentPath, stack.length, goBack])\n\n /**\n * 对外契约(D2/R3):语义与拆分前逐字等价(先拦截器、后默认)。\n * 供既有 `RouterContextValue.handleKey` 消费方与无 FocusBus 的降级路径使用。\n * FocusBus 派发不走这里——它注册上面两段到各自优先级(见下方 register)。\n */\n const handleKey = useCallback(\n (input: string, key: RouterKey): boolean =>\n handleKeyIntercept(input, key) || handleKeyDefault(input, key),\n [handleKeyIntercept, handleKeyDefault],\n )\n\n // ─── 全局 router ref(供 React 树外 bootstrap 层使用) ───\n useEffect(() => {\n setRouterRef({\n navigate,\n goBack,\n ensure,\n dismiss,\n // stack/currentPath 经 stackRef 实时读——树外调用方捕获 ref 后跨 await 复用时,\n // 必须看到最新栈状态(见 stackRef 定义处注释)。\n get stack() { return stackRef.current },\n get currentPath() { return stackRef.current[stackRef.current.length - 1]?.path ?? '/' },\n })\n return () => setRouterRef(null)\n }, [navigate, goBack, ensure, dismiss])\n\n // ─── Router 双注册到 FocusBus(RFC-281 D1)───\n // 拦截段@50(高于组件 FALLBACK=10);默认段@5(**低于** FALLBACK)。\n // 于是组件在 FALLBACK 层 `return true` 即可阻止 Router 默认导航——等价 preventDefault。\n const focusBus = useFocusBus()\n const interceptRef = useRef(handleKeyIntercept)\n interceptRef.current = handleKeyIntercept\n const defaultRef = useRef(handleKeyDefault)\n defaultRef.current = handleKeyDefault\n useEffect(() => {\n const offIntercept = focusBus.register(\n 'router-intercept', (i, k) => interceptRef.current(i, k), PRIORITY_ROUTER_INTERCEPT,\n )\n const offDefault = focusBus.register(\n 'router-default', (i, k) => defaultRef.current(i, k), PRIORITY_ROUTER_DEFAULT,\n )\n return () => { offIntercept(); offDefault() }\n }, [focusBus])\n\n // ─── 路由激活时自动聚焦 ───\n useEffect(() => {\n const initial = registry.getInitialFocus(currentPath)\n if (initial && !activeIdRef.current) {\n setActiveId(initial)\n }\n }, [currentPath])\n\n // ─── Context 注入 ───\n const routeCtxValue = useMemo<RouteContextValue>(\n () => ({ routePath: currentPath, currentPath, activeId, registry, onFocusFallback, hasRouter: true }),\n [currentPath, activeId, registry, onFocusFallback],\n )\n\n const routerValue = useMemo<RouterContextValue>(\n () => ({ stack, currentPath, activeId, navigate, goBack, ensure, dismiss, focus, handleKey }),\n [stack, currentPath, activeId, navigate, goBack, ensure, dismiss, focus, handleKey],\n )\n\n return (\n <RouterContext.Provider value={routerValue}>\n <RouteContext.Provider value={routeCtxValue}>\n {children}\n </RouteContext.Provider>\n </RouterContext.Provider>\n )\n}\n\n// ═══════════════════ <Route> 声明式路由渲染 ═══════════════════\n\n/** 前缀匹配:path 是 currentPath 的前缀(含精确相等)。 */\nexport function matchPrefix(pattern: string, path: string): boolean {\n return path === pattern || path.startsWith(pattern + '/')\n}\n\nexport interface RouteProps {\n /** 路径模式(支持 :param 和 ?query 匹配)。'*' = 始终渲染(layout)。 */\n path: string\n /** 前缀匹配模式:path 是 currentPath 前缀时就渲染(嵌套 layout 用, RFC-262)。 */\n prefix?: boolean\n /**\n * 在栈中即渲染模式(RFC-280 D4)——path 出现在路由栈任意位置就渲染,不要求是栈顶。\n *\n * 用于**可并存的瞬态 overlay**:Band B overlay 彼此非互斥(agent 处理中 pause 浮层在场,\n * 此时后台任务完成又弹 toast),若用精确匹配则被压在下面的 overlay 会被卸载,破坏其\n * 可见性语义(RFC-280 G6)。\n *\n * 与 `prefix` 的区别:`prefix` 只看 `currentPath`(栈顶),无法表达「在栈中但非栈顶」。\n *\n * 语义分层:可见性 = 在栈中(本模式);焦点归属 = 在栈顶(Router 既有语义)。两者正交。\n */\n inStack?: boolean\n /** 匹配时渲染的元素。 */\n element: React.ReactNode\n /** 子路由(嵌套时 layout 保持渲染)。 */\n children?: React.ReactNode\n}\n\n/**\n * 声明式路由组件。\n * - path='*': 始终渲染(layout route)\n * - inStack=true: path 在路由栈任意位置即渲染(可并存瞬态 overlay, RFC-280 D4)\n * - prefix=true: 前缀匹配(currentPath 以 path 开头时渲染)\n * - 默认: 精确匹配\n */\nexport function Route({ path, prefix, inStack, element, children }: RouteProps): React.ReactElement | null {\n const { currentPath, stack, activeId } = useRouter()\n const routeCtx = useContext(RouteContext)\n\n const renderMatched = (routePath: string): React.ReactElement => {\n if (!routeCtx) return <>{element}{children}</>\n return (\n <RouteContext.Provider value={{ ...routeCtx, routePath, activeId }}>\n {element}{children}\n </RouteContext.Provider>\n )\n }\n\n if (path === '*') {\n return renderMatched(currentPath)\n }\n\n if (inStack) {\n if (!stack.some((e) => e.path === path)) return null\n return renderMatched(path)\n }\n\n if (prefix) {\n if (!matchPrefix(path, currentPath)) return null\n return renderMatched(path)\n }\n\n const params = matchPath(path, currentPath)\n if (!params) return null\n\n return renderMatched(path)\n}\n","/**\n * ScrollIndicators.tsx — 滚动\"▲/▼ N 更多\"提示行统一组件(真实用户反馈修复:mobile-use\n * 面板设备列表要求\"使用 scroll view\")。\n *\n * 终局审计发现全仓 8 处消费方各自手写同一视觉模式(`▲ {STR.common.more}`/`↑ {STR.scroll.above(n)}`\n * 等):`FileContentViewer.tsx`/`ShellFullOutputDialog.tsx`/`SigilPane.tsx`/\n * `SelectPrimitive.tsx`/`SelectDialog.tsx` 五个宿主文件 + 独立编译的\n * mobile-use 插件面板(手写裸中文字面量,未走 STR 单一真源,也是本次修复导火索)。抽出\n * 本组件统一渲染,`STR.scroll.above`/`STR.scroll.below`(数量文案)与 `STR.common.more`(无数量场景,\n * 如 Select 的窗口跟随选中项,不需要精确计数)两种文案模式都支持。\n *\n * **职责边界(纯展示,零 state,零键盘)**:本组件不管理 `scrollOffset`、不监听\n * `useInput`——各消费方的滚动驱动机制本质不同(`FileContentViewer`/mobile-use 是自由滚动\n * offset;`SelectPrimitive` 是\"窗口跟随选中项\"的状态机 `createSelectState`;`DelegatesPane`\n * 是贴底显示最新的反向锚定),强行统一驱动逻辑会破坏 Select 的键盘模型(数字直达/disabled\n * 跳过/hotkey)。本组件只接收\"是否有更多/更多多少条\"的既算好的布尔值/计数,渲染层完全解耦\n * 于滚动语义,可以安全跨越 `@x-otto/tui` panel shim 边界(同 `Button`/`GroupDivider`/`DataList`\n * 纯展示组件先例)。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\nimport { i18next } from '../i18n'\n\nexport interface ScrollIndicatorAboveProps {\n /** 上方是否还有更多内容。 */\n hasMore: boolean\n /** 上方隐藏的条目/行数(提供时渲染精确计数 `↑ 上方 N 行`;不提供则渲染无计数的 `▲ 更多`)。 */\n count?: number\n /** 前导空白(默认一个空格,对齐多数消费方;部分消费方如 SigilPane 用两格)。 */\n indent?: string\n}\n\n/** 上方\"还有更多\"指示行。`hasMore=false` 时不渲染任何内容(含外层容器)。 */\nexport function ScrollIndicatorAbove({ hasMore, count, indent = ' ' }: ScrollIndicatorAboveProps): React.ReactElement | null {\n const colors = useColors()\n if (!hasMore) return null\n return (\n <Text color={colors.inactive} dimColor>\n {indent}\n {count !== undefined ? i18next.t('scroll.above', { n: count }) : `▲ ${i18next.t('common.more')}`}\n </Text>\n )\n}\n\nexport interface ScrollIndicatorBelowProps {\n /** 下方是否还有更多内容。 */\n hasMore: boolean\n /** 下方隐藏的条目/行数(提供时渲染精确计数 `↓ 下方 N 行`;不提供则渲染无计数的 `▼ 更多`)。 */\n count?: number\n /** 前导空白(默认一个空格,对齐多数消费方;部分消费方如 SigilPane 用两格)。 */\n indent?: string\n}\n\n/** 下方\"还有更多\"指示行。`hasMore=false` 时不渲染任何内容(含外层容器)。 */\nexport function ScrollIndicatorBelow({ hasMore, count, indent = ' ' }: ScrollIndicatorBelowProps): React.ReactElement | null {\n const colors = useColors()\n if (!hasMore) return null\n return (\n <Text color={colors.inactive} dimColor>\n {indent}\n {count !== undefined ? i18next.t('scroll.below', { n: count }) : `▼ ${i18next.t('common.more')}`}\n </Text>\n )\n}\n","/**\n * Typography.tsx — 文字层级语义组件(RFC-269 M2,对标 AntD Typography.Text)。\n *\n * **主题控制的核心锁点**:把散落在 39 个业务文件(239 处)的手写\n * `<Text color={THEME.inactive}>` 收敛为语义 API `<Typography type=\"secondary\">`——\n * THEME 消费点从业务层收进组件内部,换肤/调语义时影响半径 = 本组件而非几十个文件\n * (RFC-269 背景:THEME 消费面 66:16 倒挂是主题控制的真实瓶颈)。\n *\n * 命名避让:不叫 `Text`(与 ink 原生 `Text` 同名,31+ 文件已 import 它会歧义——\n * RFC-269 评审 F5),采用 AntD 同名概念 `Typography`。\n *\n * type → THEME 角色映射(设计语言 §文字强调层级 2.5 级):\n * - `primary` → THEME.text(终端默认前景,自适应明暗)=正文\n * - `secondary` → THEME.inactive(注释蓝灰)=次级/说明/脚注文字(最大宗)\n * - `danger` / `success` / `warning` → 状态色文字\n * - `accent` → 品牌强调(守恒 ≤5%,慎用)\n *\n * 不收编 `dimColor`:它是 ink 原生**强度通道** prop(`\\x1b[2m`),不经 THEME——\n * 包一层无主题控制收益,且有 HintBar `\\x1b[22m` intensity-reset 教训(评审 F10)。\n * 需要\"次级色 + 变暗\"叠加时照常 `<Typography type=\"secondary\" dim>`(dim 透传 ink)。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\nimport type { ThemeColors } from '../theme'\n\nexport type TypographyType = 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'accent'\n\nexport interface TypographyProps {\n children?: React.ReactNode\n /** 文字层级语义(缺省 primary=正文)。 */\n type?: TypographyType\n /** 加粗(透传 ink)。 */\n bold?: boolean\n /** 斜体(透传 ink)——设计语言中是特殊语义标记,非通用强调层级。 */\n italic?: boolean\n /** ink 原生强度通道(`\\x1b[2m`);与 type 正交,需要\"次级色+变暗\"时叠加。 */\n dim?: boolean\n /** 换行策略(透传 ink,viewport 预算场景用 'truncate-end')。 */\n wrap?: React.ComponentProps<typeof Text>['wrap']\n}\n\n/** type → THEME 角色(组件内部唯一 token 消费点)。 */\nexport function resolveTypographyColor(type: TypographyType, colors: ThemeColors): string | undefined {\n switch (type) {\n case 'primary':\n return colors.text // undefined = 终端默认前景(自适应明暗)\n case 'secondary':\n return colors.inactive\n case 'danger':\n return colors.error\n case 'success':\n return colors.success\n case 'warning':\n return colors.warning\n case 'accent':\n return colors.accent\n }\n}\n\nexport function Typography({\n children,\n type = 'primary',\n bold,\n italic,\n dim,\n wrap,\n}: TypographyProps): React.ReactElement {\n const colors = useColors()\n return (\n <Text color={resolveTypographyColor(type, colors)} bold={bold} italic={italic} dimColor={dim} wrap={wrap}>\n {children}\n </Text>\n )\n}\n","import type { Key } from 'ink'\n\n/**\n * 横排按钮组的方向解算(单一真源)。\n *\n * 语义(对齐 Segmented.tsx / GalleryPane.tsx 既有正确实现):\n * ← / Shift+Tab → -1(反向)\n * → / Tab → +1(正向)\n * 其余 → 0(不消费)\n *\n * 历史缺陷(RFC-289 §2.3):方向量曾写成 `key.shift && key.tab ? -1 : 1`,\n * 只区分 Shift+Tab,导致 leftArrow 与 rightArrow 同走 +1——左箭头行为等同右箭头。\n * 该写法被复制到 7 个 dialog,因测试只覆盖右箭头而长期未被发现。\n */\nexport function resolveHorizontalStep(key: Key): -1 | 0 | 1 {\n if (key.leftArrow) return -1\n if (key.rightArrow) return 1\n if (key.tab) return key.shift ? -1 : 1\n return 0\n}\n\n/** 环形步进:在 [0, count) 内按 step 回绕。 */\nexport function cycleIndex(current: number, step: number, count: number): number {\n if (count <= 0) return 0\n return (current + step + count) % count\n}\n","/**\n * Tag.tsx — 内联方括号标签(RFC-269 M3,对标 AntD Tag / Radix Badge)。\n *\n * 收编 5 处统一手写形态 `<Typography type='secondary'>[{label}]</Typography>`——\n * 多为\"当前候选视图名\"标识(FileViewer/ExtensionDetail/Sigil/EditedFileFull 的\n * `[Rendered]`/`[Raw]`)与计数标识(ProgressDialog 的 `[2/5]`)。\n *\n * 主题控制:方括号包裹 + 语义色 + **浅色底**统一收进本组件,业务层只写\n * `<Tag>{label}</Tag>`——改标签视觉(色/底/形)只动这一处(RFC-269 组件体系原则)。\n * 浅色底 `colors.tagBg`(dark 微亮灰蓝 #262A3D / light 微暗灰 #E5E7EB)衬托 `[label]`,\n * 双态 WCAG 达标(secondary 前景 dark 5.03 / light 3.90,primary 前景 12+)。\n *\n * 与 StatusIcon 的分工(M3 决策):本仓状态字符(check/cross/todo 三态/statusDot)\n * 的用法**全是数据层**(三元表达式产字符、查表 `{ch,color}`、模板串拼接),\n * `<Text color={x}>{ch}</Text>` 形态仅 1 个业务消费方——不满 ≥3 门槛,故 StatusIcon\n * 不建(YAGNI,同 lesson_120);本组件只承担\"方括号标签\"这一确有 5 消费方的模式。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\nimport { resolveTypographyColor } from './Typography'\nimport type { TypographyType } from './Typography'\n\nexport interface TagProps {\n children?: React.ReactNode\n /** 语义色(透传 Typography 的文字层级;缺省 secondary=次级标识)。 */\n type?: TypographyType\n /** 标签前是否留一个前导空格(贴在前文后面时用,收编现状多数带空格)。 */\n leadingSpace?: boolean\n}\n\nexport function Tag({ children, type = 'secondary', leadingSpace = false }: TagProps): React.ReactElement {\n const colors = useColors()\n return (\n <Text>\n {leadingSpace ? ' ' : ''}\n {/* 浅色底块:括号内外各留一个空格,让背景色形成\"标签胶囊\"而非贴字紧边。 */}\n <Text color={resolveTypographyColor(type, colors)} backgroundColor={colors.tagBg}>\n {' '}[{children}]{' '}\n </Text>\n </Text>\n )\n}\n","/**\n * viewport-budget.ts — 全屏面板「独占视口」的共享高度账本(方案1 单一真源)。\n *\n * 残影根因(RFC-019/RFC-049 坐实):stock Ink 的 inline 模式下 yoga 的 `height` **不裁剪溢出**\n * (同类工具因此各自 fork 渲染器)。所以在 stock Ink 下防溢出的唯一手段是「**按行数预算**,\n * 保证所有 live 元素总高 ≤ 终端行数」。本模块把这个预算收敛到一处——取代四个全屏面板(subagent\n * 切入 / diff / background / task detail)此前各自手调、互不一致、且 DelegateOutputPane 还算错了的\n * 魔数(曾经 9 / 8 / 6 / 0.5×)。\n *\n * 不变式:`paneBodyRows(...) + paneInternalChrome + APP_CHROME_BELOW_ROWS + VIEWPORT_SAFETY_ROWS ≤ rows`。\n * 全屏面板在场时 App 已抑制周边 live chrome(REPLRegion 流式块 / StreamingSpinner / 三 summary 面板),\n * 故面板下方只剩**常驻** chrome(状态行 + 输入框)。把它定为常量在此处,是四个面板的唯一减数来源。\n */\n\n/** 全屏面板**下方常驻** App chrome 行数:status 行(1) + 输入框 marginTop(1) + 输入框本体(1)。 */\nexport const APP_CHROME_BELOW_ROWS = 3\n\n/** 安全余量:吸收窄终端 status 行换行 / header 折行 / 少量 QueuedCommands 入队行等弹性增高。 */\nexport const VIEWPORT_SAFETY_ROWS = 2\n\n/** 面板正文最少可见行数(再矮也保留,避免 0/负高度)。 */\nexport const MIN_PANE_BODY_ROWS = 3\n\n/**\n * 全屏面板正文(可滚动区)能用的行数。\n *\n * = 终端行数 − 面板自身内部 chrome − 下方常驻 App chrome − 安全余量 − 额外下方行。\n *\n * @param rows 终端总行数。\n * @param internalChromeRows 面板自身渲染的**非正文**行数(DialogFrame 框线 / header / tabs / footer /\n * 草稿行 / ↑N earlier 等)。按**最坏可同时出现**的组合计,宁可少显一行也不溢出。\n * @param extraBelowRows 额外的下方行(如 QueuedCommands 入队 N 行),默认 0;调用方有该状态时显式传入。\n */\nexport function paneBodyRows(rows: number, internalChromeRows: number, extraBelowRows = 0): number {\n return Math.max(\n MIN_PANE_BODY_ROWS,\n rows - internalChromeRows - APP_CHROME_BELOW_ROWS - VIEWPORT_SAFETY_ROWS - Math.max(0, extraBelowRows),\n )\n}\n\n// ═══════════════════ RFC-271:弹层/面板高度硬上限(≤ 终端 1/3) ═══════════════════\n\n/**\n * 弹层/面板可占用的最大**总帧高**占终端行数的比例(RFC-271 D2 → RFC-301 D1 → 2/5 修订)。\n *\n * 用户 grill 定稿沿革(全部弹层/面板统一适用):\n * - RFC-271 D1(方案 A):2/3;\n * - RFC-301 D1(2026-07-30):2/3 → 3/5(面板最大占终端 60%);\n * - 2026-08 用户定稿:3/5 → 2/5 → **1/3**(面板最大占终端 33%)。\n */\nexport const OVERLAY_MAX_HEIGHT_RATIO = 1 / 3\n\n/**\n * 弹层总帧高的绝对下限(行)——**实测标定,非手数**(RFC-271 T2 → 2/5 修订重标定)。\n *\n * 语义:下限 = **结构固定弹窗的物理最小帧高**(框线放不下就不可用)。\n *\n * 标定沿革:\n * - RFC-271 T2(2026-07-28):最简弹窗实测 AlertDialog 10 / ConfirmDialog 11 /\n * PermissionDialog 11 → 取 11。彼时比例 3/5,rows=24 → 14 ≥ 11,下限只在\n * rows≤19 兜底,最简弹窗够用。\n * - 2/5 修订(2026-08):比例收紧到 2/5 后 rows=24 → 9,下限接管范围扩大到\n * rows≤37。此时兜底的不再只是最简弹窗,而是**全部**结构固定弹层——棘轮门\n * 实测最高者 InspectorPane / BashPermissionDialog = **14 行**(框线2+header2+\n * footer1+正文/内边距)。下限必须覆盖最高结构下限,否则它们在 rows≤37 溢出。\n * 故 11 → **14**。\n */\nexport const MIN_OVERLAY_ROWS = 14\n\n/**\n * 弹层/面板允许的最大总帧高(RFC-271 D2/D4)。\n *\n * = `max(MIN_OVERLAY_ROWS, floor(rows × 1/3))`\n *\n * **下限优先于比例**(RFC-271 D4 + 评审 P1-1):极小终端(如 rows=24 → 1/3 仅 8 行)下\n * 返回 `MIN_OVERLAY_ROWS`,此时帧高**允许**超过 1/3 比例——这是 G1 硬约束的唯一例外,\n * 因为放不下框线的弹窗不可用。验收一律以本函数返回值为准,而非字面 1/3。\n *\n * 正文可用行数 = `overlayMaxRows(rows) − 组件自身 chrome`。\n *\n * @param rows 终端总行数。\n */\nexport function overlayMaxRows(rows: number): number {\n const safeRows = Number.isFinite(rows) && rows > 0 ? rows : 24\n return Math.max(MIN_OVERLAY_ROWS, Math.floor(safeRows * OVERLAY_MAX_HEIGHT_RATIO))\n}\n\n// ═══════════════════ RFC-301:面板高度统一模型(自适应 + min + max) ═══════════════════\n\n/**\n * 面板最小目标高度(行)——用户定稿(RFC-301 D1)。\n *\n * 语义:列表/选择/查看类面板内容再少也至少 15 行(视觉上\"是个面板\"而非残影),\n * 不足部分由窗口 filler 补齐。注意小终端(overlayMaxRows < 15,如 rows=24 → 14)\n * 时上限优先,min 被 max 吸收——不溢出是硬约束(RFC-271 G1)。\n */\nexport const MIN_PANEL_ROWS = 15\n\n/**\n * 面板目标窗口行数 = clamp(内容行数, MIN_PANEL_ROWS, overlayMaxRows(rows))。\n *\n * **总帧高语义(RFC-301 D1 修订)**:min 15 / max 1/3 约束的是**面板总帧高**\n * (含 DialogFrame 框线/header/footer chrome)。调用方必须把自身 chrome 行数\n * 计入 contentRows 再传入,即:\n *\n * `windowRows = panelVisibleRows(内容行数 + chrome, rows) − chrome`\n *\n * 这样总帧高 = chrome + windowRows ∈ [15, overlayMaxRows(rows)]。\n * 若只传内容行数,min 15 窗口 + chrome 会突破 1/3 上限(rows=24 时 15+8=23 > 14,\n * 门禁转红——Select* 家族实测发现的真实缺陷)。\n *\n * 三个分支(对总帧高):\n * - 内容+chrome < MIN_PANEL_ROWS → 总帧高 15(filler 补齐)\n * - 总帧高 ∈ [15, maxRows] → 自适应(零 filler)\n * - 总帧高 > maxRows → maxRows(超限滚动)\n * - 小终端(maxRows < 15)→ 恒 maxRows(上限优先,min 被吸收)\n *\n * 全部面板消费此函数(RFC-301 R3:表达式唯一真源,禁止组件内联魔数)。\n *\n * @param contentRows 含 chrome 的内容自然行数(列表条数/物理行数 + 面板 chrome)。\n * @param rows 终端总行数。\n */\nexport function panelVisibleRows(contentRows: number, rows: number): number {\n const safeContent = Number.isFinite(contentRows) && contentRows > 0 ? contentRows : 0\n return Math.min(overlayMaxRows(rows), Math.max(MIN_PANEL_ROWS, safeContent))\n}\n\n\n// ═══════════ extension 家族面板高度上限(用户定稿 2026-08-09:总帧高 ≤ rows/3) ═══════════\n\n/**\n * extension 列表/详情面板的总帧高硬上限(含全部 chrome)。\n *\n * 用户定稿 2026-08-09:extension 面板(`/dialog/extensions` 列表 + `/dialog/extension`\n * 详情)最大占终端 1/3。实现与全局 `overlayMaxRows` 同源(1/3 比例 + MIN_OVERLAY_ROWS\n * 下限),单独成函数仅作语义命名——extension 家族的消费方显式引用本函数,让\"为何是\n * 1/3\"的归属在调用点可读,而不是一个无主的魔数。\n */\nexport function extensionPanelMaxRows(rows: number): number {\n const safeRows = Number.isFinite(rows) && rows > 0 ? rows : 24\n return Math.max(MIN_OVERLAY_ROWS, Math.floor(safeRows / 3))\n}\n\n// ═══════════ RFC-320 D3:信号面板的列表高度模型(动态 + 上限 5 条) ═══════════\n\n/**\n * 信号面板列表的最大可见条数(用户定稿,RFC-320 D3)。\n *\n * 适用范围**仅限**与 SignalBar 关联的四个信号面板\n * (Tasks / Delegates / Subagents / EditedFiles)——它们是\"瞄一眼就走\"的后台信号\n * 展开视图,占高必须可预期,否则连续吃掉对话流。\n *\n * **与 MIN_PANEL_ROWS(15) 的关系(范围隔离,非反转)**:\n * RFC-301 D1 的 min 15 服务的是\"工作面板\"(SigilPane 选择列表、FileContentViewer\n * 文件内容、CompactionLog/Perf/TaskDetail 等)——用户要**进去操作/阅读**,撑到 15 行\n * 才像个面板。信号面板语义不同:看一眼计数与前几条即可,撑高纯属浪费。\n * 故本模型**不改** `panelVisibleRows`,其 6 个既有消费方与门禁基线原样不动。\n */\nexport const SIGNAL_PANEL_MAX_ROWS = 5\n\n/**\n * 信号面板的可见条数 = clamp(实际条数, 0, SIGNAL_PANEL_MAX_ROWS),并受终端高度约束。\n *\n * 语义:**按内容动态设高**(1 条就 1 行,不留空——与 otto\"无数据零占高\"原则一致),\n * 超出上限则列表内滚动 + \"+N more\" 指示(由调用方渲染)。\n *\n * @param itemCount 列表实际条数。\n * @param rows 终端总行数(小终端下进一步收紧,避免面板挤掉对话流)。\n * @param chromeRows 面板自身 chrome 行数(标题/边框/footer),用于终端高度约束。\n */\nexport function signalPanelVisibleRows(itemCount: number, rows: number, chromeRows = 0): number {\n const safeCount = Number.isFinite(itemCount) && itemCount > 0 ? Math.floor(itemCount) : 0\n const capped = Math.min(safeCount, SIGNAL_PANEL_MAX_ROWS)\n // 小终端兜底:面板总高(含 chrome)不超过 overlayMaxRows。\n const budget = Math.max(1, overlayMaxRows(rows) - chromeRows)\n return Math.min(capped, budget)\n}\n","/**\n * progress-bar-model.ts — 进度条填充计算核心(RFC-279)。\n *\n * 纯函数、零渲染依赖,供 Ink 组件出口(ProgressBar.tsx)与字符串出口\n * (progress-bar-string.ts)共用。业务分段算法(token 占比分配、触发线阈值换算)保留\n * 在各消费方(InspectorPane.computeSegments/occupancy-bar 现状不变),本模块只负责把\n * \"填充比例/分段/标记位\"转成统一的格子序列(cells),不重新发明业务计算。\n */\n\nexport interface ProgressCell {\n filled: boolean\n /** 该格所属分段的语义 key(多段场景,如 'overhead'/'messages'/'free');简单场景为 undefined。 */\n segmentKey?: string\n /** 该格是否命中某个阈值标记位(如 occupancy-bar 的 prune/compact 触发线)。 */\n markerKey?: string\n}\n\nexport interface ProgressBarModelInput {\n /** 0-100;越界自动钳制(负数→0,>100→100);NaN 按 0 处理。 */\n percent: number\n width: number\n /**\n * 多段模式(对齐 InspectorPane.computeSegments 输出形状)——每段占格数由调用方算好\n * 传入,本函数只负责按段生成 cells,不重新计算段宽比例。空数组等价于不传(回退简单\n * 二色模式)。段宽总和可以不等于 width——模型层忠实转录调用方给定的段宽,不做静默\n * 缩放(调用方的既有算法如 computeSegments 已经自行处理\"最后一段补齐宽度\")。\n */\n segments?: Array<{ key: string; width: number }>\n /**\n * 阈值标记位(0-100 百分比 → 换算成格索引),对齐 occupancy-bar 触发线换算公式\n * `Math.round((pct / 100) * total)`,越界 clamp 到 `[0, width-1]`(与\n * occupancy-bar.ts 现状 `Math.min(total - 1, ...)` 行为一致)。\n */\n markers?: Record<string, number>\n}\n\nexport interface ProgressBarModel {\n /** 取整并钳到 [0,100] 的百分比。 */\n pct: number\n /**\n * 实心格数——简单模式下按 percent 计算;分段模式下等于 segments 宽度总和(不重新\n * 按 percent 计算,二选一语义:有 segments 时以 segments 为准)。\n */\n filledCount: number\n cells: ProgressCell[]\n}\n\nfunction clampPercent(percent: number): number {\n if (!Number.isFinite(percent)) return 0\n return Math.max(0, Math.min(100, percent))\n}\n\nexport function computeProgressBarModel(input: ProgressBarModelInput): ProgressBarModel {\n const pct = Math.round(clampPercent(input.percent))\n const width = Math.max(0, Math.floor(input.width))\n\n if (width === 0) {\n return { pct, filledCount: 0, cells: [] }\n }\n\n const segments = input.segments?.filter((s) => s.width > 0) ?? []\n const hasSegments = segments.length > 0\n\n let cells: ProgressCell[]\n let filledCount: number\n\n if (hasSegments) {\n // 分段模式:忠实转录调用方给定的段宽,不重新计算/不缩放。\n cells = []\n for (const seg of segments) {\n for (let i = 0; i < seg.width; i++) {\n cells.push({ filled: true, segmentKey: seg.key })\n }\n }\n filledCount = cells.length\n // 段宽总和 < width 时补齐空位(视觉上占满声明的 width);> width 时不截断\n // (忠实转录,调用方负责摆对——见 RFC-279 §5 测试策略)。\n while (cells.length < width) {\n cells.push({ filled: false })\n }\n } else {\n filledCount = Math.max(0, Math.min(width, Math.round((pct / 100) * width)))\n cells = Array.from({ length: width }, (_, i) => ({ filled: i < filledCount }))\n }\n\n if (input.markers) {\n for (const [key, markerPct] of Object.entries(input.markers)) {\n if (!Number.isFinite(markerPct)) continue\n const pos = Math.max(0, Math.min(width - 1, Math.round((markerPct / 100) * width)))\n const cell = cells[pos]\n if (cell) cell.markerKey = key\n }\n }\n\n return { pct, filledCount, cells }\n}\n","/**\n * progress-bar-chars.ts — 进度条字符集预设(RFC-279)。\n *\n * 不统一成单一字符集——7 处收编点已经用了 3 套体系且视觉场景不同(对话流内嵌 vs 独立\n * 弹窗 vs 状态栏),本 RFC 收编的是渲染骨架(计算逻辑/颜色应用方式),不是视觉本身。\n * 迁移后应保持像素级视觉不变(除 ProgressDialog footer bug 修复本身是有意变化)。\n */\n\n/** 三套现状字符预设——对齐各消费方迁移前的实际字符。 */\nexport const PROGRESS_CHARS = {\n /** `ProgressDialog.tsx` 现状(`/install` 总进度条)。 */\n ascii: { filled: '=', empty: '.' },\n /** `CachePane`/`InspectorPane` 回退态占用条现状。 */\n block: { filled: '█', empty: '░' },\n /** `occupancy-bar`/`ContextDetailPane`/`message-lines.compactRatioBar` 现状。 */\n dot: { filled: '▰', empty: '▱' },\n} as const\n\nexport type ProgressCharPreset = keyof typeof PROGRESS_CHARS\n\nexport interface ProgressChars {\n filled: string\n empty: string\n}\n\n/** 解析 chars 入参(预设名或自定义字符对)为具体的 {filled, empty}。 */\nexport function resolveProgressChars(\n chars: ProgressCharPreset | ProgressChars | undefined,\n): ProgressChars {\n if (!chars) return PROGRESS_CHARS.block\n if (typeof chars === 'string') return PROGRESS_CHARS[chars]\n return chars\n}\n","/**\n * truncate-display.ts — 显示宽度感知截断(RFC-182 后续全仓审计,AP2 反模式单一真源)。\n *\n * 背景:全仓 12+ 处独立的 `text.slice(0, n)` 截断(retry/history/draft/queue/subagents/\n * cache/extensions/questions/permission…)全部按**字符数**截断——CJK 字符 slice 计 1 但\n * 终端显示占 2 列,截断产物仍可超宽,Ink 默认 wrap 将其折行 → 帧高失控(RFC-182 §1 根因\n * 的变体)。本模块提供统一的显示宽度截断,替代裸 slice。\n *\n * 与 `<Text wrap=\"truncate-end\">`(Ink 渲染层截断)的分工:能交给渲染层就交给渲染层\n * (零计算、宽度自动取实际可用值);只有**拼接场景**(截断后还要接后缀/其他字段,如\n * `${slice(cmd, 43)}… · ${model}`)必须在字符串层截断——那正是本函数的场景。\n */\nimport { displayWidth } from './insert-history'\nimport { FIGURES } from \"../theme\"\n\n/**\n * 按显示宽度截断字符串(CJK 感知),超宽时截到 `maxWidth − 1` 并追加 `…`。\n * 不切半个宽字符(逐字符累加宽度,放不下即停)。maxWidth ≤ 1 时返回 `…`。\n */\nexport function truncateByDisplayWidth(text: string, maxWidth: number): string {\n if (maxWidth <= 1) return FIGURES.ellipsis\n if (displayWidth(text) <= maxWidth) return text\n let budget = maxWidth - 1\n let out = ''\n for (const ch of text) {\n const w = displayWidth(ch)\n if (w > budget) break\n out += ch\n budget -= w\n }\n return `${out}…`\n}\n\n/**\n * 按显示宽度右填充(RFC-183 M2,Table 列对齐)——`padEnd` 的 CJK 感知版:\n * `padEnd(n)` 按字符数填充,CJK 字符占 2 列时列对齐错位。超宽不截断(截断语义归\n * `truncateByDisplayWidth`,两者可组合)。\n */\nexport function padEndDisplayWidth(text: string, width: number): string {\n const w = displayWidth(text)\n return w >= width ? text : text + ' '.repeat(width - w)\n}\n","/**\n * GroupDivider.tsx — 分组分隔行(RFC-149 D3;视觉改版参考 web-ui 侧栏分组标题)。\n *\n * 消费方:SigilPane 分类头 / SelectDialog 列表分组分隔。\n *\n * 视觉设计对齐 `packages/web-ui/x-figma-site/.../Sidebar/sidebar.scss` 的\n * `&__section-label`(11px / font-weight:600 / color:faint / uppercase)——不加\n * `── ── ` 这类对称画线包裹,纯靠「大写 + 粗体 + 弱化色」的排版层次做分组标题,\n * 与终端等宽字体天然契合、比双线包裹更接近现代 UI kit(Linear/Notion/Radix)观感。\n *\n * 默认输出含 1 个前导空格(对齐多数消费方现状);`indent` prop 供未来场景覆盖。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../theme'\n\nexport interface GroupDividerProps {\n label: string\n /** 前导空白字符串(默认 1 个空格,对齐多数消费方)。 */\n indent?: string\n}\n\nexport function GroupDivider({ label, indent = ' ' }: GroupDividerProps): React.ReactElement {\n const colors = useColors()\n // 防御:label 理论上恒为 string,但数据源含运行时 kind(插件动态 @ provider)+ i18next.t\n //(未初始化时返回 undefined,RFC-264 迁移后新增的边界)——共享渲染原语不得因坏输入崩溃。\n const safeLabel = typeof label === 'string' ? label : String(label ?? '')\n return (\n <Text color={colors.inactive} bold>\n {indent}\n {safeLabel.toUpperCase()}\n </Text>\n )\n}\n","/**\n * select-row-window.ts — 分组列表的固定物理行滚动窗口(纯函数,可测)。\n *\n * 根因(真实用户反馈:/extension 等分组列表滚动时高度仍会跳动):此前 SelectDialog/\n * SelectPrimitive 先对 **条目(option)** 取固定数量窗口,再在渲染时按「当前窗口内的\n * 局部位置」临时插入分组分隔线/分隔线前空行——滚动跨越分组边界时,窗口内多出的分隔线\n * +空行数量不固定(0~2 行 × 窗口内分组切换次数),加上 `detail` 行是否落入窗口也会变化,\n * 导致总物理行数随滚动位置浮动。SigilPane 的分组头同样把「前导空行」按局部渲染位置\n * (`i > 0`)条件插入,犯的是同一类错误。\n *\n * 修复对齐 FileContentViewer M3 范式:先把**全部**条目展开成固定的物理行数组(分隔线、\n * 分隔线前空行、条目行、detail 行都是独立的数组元素,基于全局位置一次性算好,不依赖\n * 窗口内的局部渲染顺序),再对这份完整物理行数组做**固定行数**窗口切片——窗口内元素数\n * 恒等于 windowSize(末尾不足时由调用方补空行),滚动到哪里都不会变化。\n *\n * `computeFixedRowWindow` 是与具体行类型无关的通用窗口滚动纯函数(泛型 T)——\n * `buildSelectRows`/`SelectRow` 是 `SelectOption[]` 场景(SelectDialog/SelectPrimitive)\n * 的物理行展开实现;SigilPane 的行类型不同(`SigilEntry[]` 衍生),在 SigilPane.tsx\n * 内自行展开后同样调用 `computeFixedRowWindow` 复用窗口滚动算法,不重复实现。\n */\nimport type { SelectOption } from './select'\n\nexport interface FixedRowWindowResult<T> {\n /** 本次应渲染的物理行切片(长度 ≤ windowSize)。 */\n rows: T[]\n /** 末尾需要补齐的空白行数(rows.length + fillerCount === windowSize,除非 windowSize<=0)。 */\n fillerCount: number\n hasMoreAbove: boolean\n hasMoreBelow: boolean\n /** 本次窗口顶部在 allRows 中的下标——调用方需持久化(如 useRef)供下次调用的 prevScrollTop。 */\n scrollTop: number\n}\n\n/**\n * 固定行数窗口滚动(keep-visible 最小滚动,同 `Select.ts` createSelectState 的 `so` clamp\n * 语义,不做居中滚动——保持既有翻页手感)。以**物理行**为单位切片,而非条目数——保证\n * 任意滚动位置渲染的物理行数恒为 windowSize(不足时 fillerCount 由调用方补空行)。\n *\n * @param allRows 全部物理行(分隔线/空行/条目/detail 等已展开,任意元素类型)。\n * @param selectedRowIndex 当前选中行在 allRows 中的下标(找不到传 -1,锚定为 0)。\n * @param windowSize 窗口物理行数上限;传 `Infinity` 表示不限制(展示全部,fillerCount 恒 0)。\n * @param prevScrollTop 上次窗口顶部下标(首次调用传 0)。\n */\nexport function computeFixedRowWindow<T>(\n allRows: readonly T[],\n selectedRowIndex: number,\n windowSize: number,\n prevScrollTop: number,\n): FixedRowWindowResult<T> {\n const total = allRows.length\n // windowSize=Infinity 是「不限制窗口、展示全部、不补空行」的显式信号(如 Welcome 静态\n // 嵌入展示场景)——fillerCount 恒为 0(Infinity - rows.length 会产出 Infinity,\n // 传入 Array.from({length}) 会抛错,必须在此短路)。\n if (!Number.isFinite(windowSize)) {\n return { rows: [...allRows], fillerCount: 0, hasMoreAbove: false, hasMoreBelow: false, scrollTop: 0 }\n }\n if (windowSize <= 0 || total === 0) {\n return { rows: [], fillerCount: Math.max(0, windowSize), hasMoreAbove: false, hasMoreBelow: false, scrollTop: 0 }\n }\n const anchor = selectedRowIndex >= 0 ? selectedRowIndex : 0\n\n let top = prevScrollTop\n if (anchor < top) top = anchor\n else if (anchor >= top + windowSize) top = anchor - windowSize + 1\n const maxTop = Math.max(0, total - windowSize)\n top = Math.min(Math.max(0, top), maxTop)\n\n const end = Math.min(total, top + windowSize)\n const rows = allRows.slice(top, end)\n return {\n rows,\n fillerCount: Math.max(0, windowSize - rows.length),\n hasMoreAbove: top > 0,\n hasMoreBelow: end < total,\n scrollTop: top,\n }\n}\n\nexport type SelectRow =\n | { kind: 'spacer' }\n | { kind: 'divider'; label: string }\n | { kind: 'item'; option: SelectOption; optionIndex: number }\n | { kind: 'detail'; option: SelectOption; optionIndex: number }\n\n/**\n * 展开 options 为固定物理行数组。分隔线前空行只在**全局第一条分隔线之后**的每条分隔线\n * 前插入(第一条分隔线前无需空行——上方已有过滤框/标题提供分隔);detail 行仅在\n * `option.detail` 存在**且 `detailPlacement !== 'inline'`** 时追加,紧跟其条目行。\n *\n * `detailPlacement`(RFC-292 D1):\n * - `'below'`(缺省):现状不变,detail 独占一行。\n * - `'inline'`:detail 并入 item 行本身渲染(label 同一行右侧,按组内最大宽度对齐),\n * 此处不再产出独立 `kind:'detail'` 行——渲染层(SelectDialog)负责把 detail 文本\n * 画在 item 行内,不经过本函数的行数组。\n */\nexport function buildSelectRows(\n options: readonly SelectOption[],\n detailPlacement: 'below' | 'inline' = 'below',\n): SelectRow[] {\n const rows: SelectRow[] = []\n let prevGroup: string | undefined\n let dividerCount = 0\n options.forEach((option, optionIndex) => {\n if (option.group && option.group !== prevGroup) {\n if (dividerCount > 0) rows.push({ kind: 'spacer' })\n rows.push({ kind: 'divider', label: option.group })\n dividerCount++\n }\n prevGroup = option.group\n rows.push({ kind: 'item', option, optionIndex })\n if (option.detail && detailPlacement === 'below') rows.push({ kind: 'detail', option, optionIndex })\n })\n return rows\n}\n\n/** 对 `SelectOption[]` 场景(SelectDialog/SelectPrimitive)的固定窗口滚动便捷封装。 */\nexport function computeSelectRowWindow(\n allRows: readonly SelectRow[],\n selectedOptionIndex: number,\n windowSize: number,\n prevScrollTop: number,\n): FixedRowWindowResult<SelectRow> {\n const selectedRowIndex = allRows.findIndex((r) => r.kind === 'item' && r.optionIndex === selectedOptionIndex)\n return computeFixedRowWindow(allRows, selectedRowIndex, windowSize, prevScrollTop)\n}\n","/**\n * motion.ts — 集中的「减少动效」开关(业界通行的 reduced-motion / prefersReducedMotion 通道)。\n *\n * 背景:otto 此前**无** reduced-motion 通道,spinner 旋转 / glimmer 扫光 / 工具呼吸圆圈无条件跑——\n * 对前庭敏感用户、录屏、慢终端不友好。本模块是单一真源(MU-D / RFC-039 A2)。\n *\n * 语义:开启时动画组件**冻结到稳定首帧**(静态字形 + 无扫光 + 无呼吸/旋转),但保留计时/token 等\n * 静态信息文字(那是信息不是动效)。开关源 = 环境变量 `OTTO_TUI_REDUCED_MOTION ∈ {1,true}`,\n * **每次调用即时读**(不缓存)——便于测试在运行期切换,且与 THEME live-binding 风格一致。\n */\nexport function reducedMotion(): boolean {\n const v = process.env['OTTO_TUI_REDUCED_MOTION']\n return v === '1' || v === 'true'\n}\n","/**\n * animation-scheduler.ts — RFC-319 M1:TUI 动画统一调度器。\n *\n * ## 为何统一\n * 迁移前:21 处 `setInterval` 各自持有定时器、8 个独立帧率常量、14 处分散判断\n * reducedMotion。直接代价是**渲染层不得不猜测上层动画节奏**——RFC-315 的突发\n * 抑制阈值必须显著大于全部动画周期,而 Wordmark 的 80ms 与当时阈值 80ms 只差\n * 1~2ms,导致每帧踩线、真机光标持续抖动(见 RFC-315 D0b 约束②)。\n *\n * ## 设计要点\n * - **帧网格量化 + 按到期调度**(两层,缺一不可):\n * ① **量化**:周期向上取整到 `FRAME_GRID_MS` 的整数倍。TUI 的真实瓶颈不是\n * 定时器唤醒,而是**每次 setState 引发的 React 全树渲染 + yoga 布局**。\n * 各动画若在互不相同的时刻触发(如 80ms 与 500ms 永远错拍),每帧都是一次\n * 独立渲染;量化到同一网格后,多个动画落在**同一时刻** → React 自动批处理\n * → N 次渲染合并为 1 次。量化误差 ≤ 一格(80ms),视觉无差异。\n * ② **按到期调度**:每次唤醒后算\"下一个该触发的时刻\",一次性 timer 精确唤醒,\n * 唤醒次数 = 实际帧数,零空转。\n * 曾用\"周期 GCD 作基准 tick\",两层都不满足:{80,500} 的 GCD=20ms → 50 次/秒\n * 唤醒(35.5 次纯空转),且各动画仍错拍、渲染次数照旧;GCD 对取值还极敏感\n * (加 77ms 动画直接退化到 1ms/1000 次每秒)。真机表现为明显卡顿。\n * - **帧号时间派生**:`floor(elapsed / periodMs)`,而非计数器递增——某次 tick 迟到\n * 不影响下一帧号(自动追帧对齐挂钟时间)。沿用 Spinner 既有范式(M69-01)。\n * - **reducedMotion 零唤醒**:开启时**不注册任何 interval**(优于旧做法\"仍 tick\n * 但不 setState\"),且消费方回调一次都不会收到。\n * - **Clock 可注入**:对齐 docs/CODE-STYLE.md 与 RFC D13 的确定性重放要求,\n * 动画测试可推进虚拟时间,去除 `await sleep`。\n */\n\nimport { useEffect, useRef } from 'react'\n\n/**\n * 帧网格(ms)——所有动画周期向上量化到它的整数倍,使触发时刻可合并。\n * 取 80ms:等于现存最快动画(Wordmark 扫光)的周期,量化对它零损失;\n * 12.5fps 对终端动画足够(终端本身刷新粒度远粗于此)。\n */\nexport const FRAME_GRID_MS = 80\n\n/** 把周期量化到帧网格(向上取整,至少一格)。 */\nexport function quantizePeriod(periodMs: number): number {\n return Math.max(1, Math.round(periodMs / FRAME_GRID_MS)) * FRAME_GRID_MS\n}\n\n/** 时钟端口(确定性测试用;生产注入 Date.now + setTimeout)。 */\nexport interface SchedulerClock {\n now(): number\n /** 一次性定时器(按到期时刻调度,不用 interval 轮询)。 */\n setTimer(fn: () => void, ms: number): unknown\n clearTimer(handle: unknown): void\n}\n\nexport const systemClock: SchedulerClock = {\n now: () => Date.now(),\n setTimer: (fn, ms) => {\n const t = setTimeout(fn, ms)\n t.unref?.()\n return t\n },\n clearTimer: (handle) => clearTimeout(handle as NodeJS.Timeout),\n}\n\ninterface Subscriber {\n periodMs: number\n onFrame: (frame: number) => void\n /** 已派发的最新帧号——用于计算下一个到期时刻,并防重复派发。 */\n lastFrame: number\n /**\n * **本订阅者的起始时刻**(相对调度器 startedAt 的 elapsed)。\n *\n * 帧号必须相对订阅时刻派生,不能相对进程启动时刻:否则组件挂载时会落在任意帧\n * (spinner 从随机一帧开始转、呼吸点半明半暗),而迁移前每个组件持有自己的\n * start ref、挂载即 frame 0。真机表现为\"每次打开对话框动画起点不一致\"。\n */\n startedAtElapsed: number\n}\n\nexport interface AnimationScheduler {\n /**\n * 订阅指定周期的帧回调;返回取消函数。\n * 回调仅在**帧号变化时**触发(周期 > 基准 tick 时自动分频,不做无谓 setState)。\n */\n subscribe(periodMs: number, onFrame: (frame: number) => void): () => void\n /** 当前已注册的最小周期(ms);无订阅者时为 undefined。供渲染层派生阈值,不再猜测。 */\n minPeriodMs(): number | undefined\n /** 活跃订阅者数量(诊断/测试用)。 */\n size(): number\n /** 停止调度并清空订阅(进程退出/测试清理)。 */\n dispose(): void\n}\n\nexport interface CreateSchedulerOptions {\n clock?: SchedulerClock\n /** reducedMotion 判定(默认读环境变量,每次注册时求值)。 */\n reducedMotion?: () => boolean\n}\n\n/**\n * 创建动画调度器。\n *\n * reducedMotion 开启时:`subscribe` 仍返回可用的取消函数(调用方无需分支),\n * 但**不注册任何 interval、不触发任何回调**——动画组件自然冻结在首帧。\n */\nexport function createAnimationScheduler(options: CreateSchedulerOptions = {}): AnimationScheduler {\n const clock = options.clock ?? systemClock\n const isReducedMotion =\n options.reducedMotion ??\n (() => {\n const v = process.env['OTTO_TUI_REDUCED_MOTION']\n return v === '1' || v === 'true'\n })\n\n const subscribers = new Set<Subscriber>()\n let handle: unknown = null\n const startedAt = clock.now()\n\n /** 某订阅者下一帧的到期时刻(相对调度器 startedAt 的 elapsed)。 */\n const nextDueOf = (s: Subscriber): number =>\n s.startedAtElapsed + (s.lastFrame + 1) * s.periodMs\n\n const schedule = (): void => {\n if (handle !== null) {\n clock.clearTimer(handle)\n handle = null\n }\n if (subscribers.size === 0) {\n return\n }\n const elapsed = clock.now() - startedAt\n let soonest = Number.POSITIVE_INFINITY\n for (const s of subscribers) {\n const due = nextDueOf(s)\n if (due < soonest) {\n soonest = due\n }\n }\n // 到期时刻已过(tick 迟到/长任务阻塞)→ 立即唤醒,由 onTick 追帧对齐。\n const delay = Math.max(0, soonest - elapsed)\n handle = clock.setTimer(onTick, delay)\n }\n\n const onTick = (): void => {\n handle = null\n const elapsed = clock.now() - startedAt\n for (const s of subscribers) {\n // 帧号时间派生(相对**订阅时刻**):tick 迟到不影响下一帧号(自动追帧),\n // 且组件挂载即从 frame 0 起算。\n const frame = Math.floor((elapsed - s.startedAtElapsed) / s.periodMs)\n if (frame === s.lastFrame) {\n continue\n }\n s.lastFrame = frame\n try {\n s.onFrame(frame)\n } catch {\n // 单个消费方抛错不得影响其余动画(RFC-319 风险表:单点故障隔离)。\n }\n }\n schedule()\n }\n\n return {\n subscribe(periodMs, onFrame) {\n if (!Number.isFinite(periodMs) || periodMs <= 0) {\n return () => {}\n }\n // reducedMotion:零注册、零唤醒、零回调——动画冻结在首帧。\n if (isReducedMotion()) {\n return () => {}\n }\n const quantized = quantizePeriod(periodMs)\n const nowElapsed = clock.now() - startedAt\n const sub: Subscriber = {\n // 量化到帧网格:让不同周期的动画落在同一触发时刻,合并为一次 React 渲染。\n periodMs: quantized,\n onFrame,\n lastFrame: 0,\n // 对齐到网格:起点取**不晚于当前**的最近网格点,既保证挂载即 frame 0,\n // 又不破坏\"多动画同刻触发\"的批处理前提。\n startedAtElapsed: Math.floor(nowElapsed / FRAME_GRID_MS) * FRAME_GRID_MS,\n }\n subscribers.add(sub)\n schedule()\n return () => {\n subscribers.delete(sub)\n schedule()\n }\n },\n minPeriodMs() {\n let min: number | undefined\n for (const s of subscribers) {\n min = min === undefined ? s.periodMs : Math.min(min, s.periodMs)\n }\n return min\n },\n size: () => subscribers.size,\n dispose() {\n subscribers.clear()\n if (handle !== null) {\n clock.clearTimer(handle)\n handle = null\n }\n },\n }\n}\n\n/** 进程级单例(生产用)。测试请用 createAnimationScheduler 注入假时钟。 */\nexport const animationScheduler = createAnimationScheduler()\n\n/**\n * React 绑定:订阅动画帧,卸载即退订。\n *\n * **必须用它而非直接 `animationScheduler.subscribe`**——直接用时,若 effect 依赖\n * 写错(如把每次渲染新建的回调放进依赖),会反复退订/重订;更危险的是回调闭包\n * 捕获旧 state。本 hook 用 ref 转发最新回调,effect 依赖只有 `periodMs`:\n * 订阅在组件生命周期内**建立一次**,回调始终是最新的。\n *\n * @param periodMs 期望周期(会被量化到帧网格);传 null/undefined 表示暂不订阅。\n * @param onFrame 帧回调(无需 useCallback 包裹)。\n */\nexport function useAnimationFrame(\n periodMs: number | null | undefined,\n onFrame: (frame: number) => void,\n): void {\n const cbRef = useRef(onFrame)\n cbRef.current = onFrame\n useEffect(() => {\n if (periodMs === null || periodMs === undefined) {\n return undefined\n }\n return animationScheduler.subscribe(periodMs, (f) => cbRef.current(f))\n }, [periodMs])\n}\n","/**\n * Select — Windowed selector state machine (M50-03).\n * CC CustomSelect: viewport scrolling, disabled skipping, numeric hotkeys, boundary callbacks.\n */\nimport type { ButtonColor } from '../components/Button'\n\n/** color 仅 layout='buttons' 模式生效:调用方为每个按钮指定语义色(缺省按位置回退)。 */\nexport interface SelectOption {\n label: string\n value: string\n disabled?: boolean\n hotkey?: number\n group?: string\n detail?: string\n color?: ButtonColor\n /** 条目行前导空白(`Select.Item` 的 `indent` prop 透传,默认一个空格)。 */\n indent?: string\n /**\n * 序号标记(`Select.Item` 的 `marker` prop 透传):\n * - `'number'`:渲染该项在列表中的序数(1-based,跨分组连续计数)\n * - `'-'` / `'·'`:渲染固定项目符\n * 不传 = 不渲染任何标记(默认现状)。\n */\n marker?: 'number' | '-' | '·'\n /** `marker='number'` 时使用的序数(compileOptions 自动计算,1-based)。 */\n ordinal?: number\n /**\n * 行内尾随文案(`Select.Item` 的 `trailingLabel` prop 透传):渲染在条目同一行右侧\n * (非另起一行的 `detail`),用于 Welcome 扩展列表这类\"名称 + 状态\"同行双栏布局。\n */\n trailingLabel?: string\n /** `trailingLabel` 的颜色(默认 THEME.inactive)。 */\n trailingColor?: string\n /**\n * label 颜色覆盖(独立于 `disabled`——`disabled` 会额外追加 \" (disabled)\" 后缀文案,\n * 不适合\"仅需弱化配色、不需要追加文案\"的场景,如 Welcome 未启用扩展行)。\n *\n * 缺省 = `colors.inactive`(未选中项统一灰,与选中态的品牌紫拉开对比)。\n */\n labelColor?: string\n /**\n * 模糊搜索用纯语义文本(RFC-125 review 修复):`label` 常含渲染态装饰(当前项标记 `●`/\n * 空格 padding/上下文窗口后缀等),直接对其模糊评分会因短 query 命中装饰字符产生假阳性\n * (如 \"dsk\" 因 \"200k\" 的 k 误中不相关模型)。提供 `searchText` 时 searchable 模式改为\n * 对它评分(缺省回退 `label`,向后兼容不提供该字段的调用方)。\n */\n searchText?: string\n}\nexport interface SelectOptions {\n maxVisible?: number\n onBoundary?: (dir: 'top' | 'bottom') => void\n}\n\nexport interface SelectState {\n readonly selectedIndex: number\n readonly scrollOffset: number\n getVisibleOptions(): SelectOption[]\n getOptionAt(index: number): SelectOption | undefined\n hasMoreAbove(): boolean\n hasMoreBelow(): boolean\n getSelected(): SelectOption | null\n moveUp(): void\n moveDown(): void\n accept(): SelectOption | null\n selectByHotkey(key: number): SelectOption | null\n selectByIndex(index: number): void\n}\n\nexport function createSelectState(options: SelectOption[], opts: SelectOptions = {}): SelectState {\n const maxV = opts.maxVisible ?? 10\n let si = options.findIndex((o) => !o.disabled)\n if (si < 0) si = 0\n let so = 0\n\n function clamp() {\n if (si < so) so = si\n else if (si >= so + maxV) so = si - maxV + 1\n const max = Math.max(0, options.length - maxV)\n if (so < 0) so = 0\n if (so > max) so = max\n }\n\n function moveTo(n: number) {\n if (n < 0) {\n opts.onBoundary?.('top')\n return\n }\n if (n >= options.length) {\n opts.onBoundary?.('bottom')\n return\n }\n const dir = n > si ? 1 : -1\n let target = n\n while (target >= 0 && target < options.length && options[target]?.disabled) target += dir\n if (target < 0) {\n opts.onBoundary?.('top')\n return\n }\n if (target >= options.length) {\n opts.onBoundary?.('bottom')\n return\n }\n si = target\n clamp()\n if (target === 0) opts.onBoundary?.('top')\n if (target === options.length - 1) opts.onBoundary?.('bottom')\n }\n\n clamp()\n\n return {\n get selectedIndex() {\n return si\n },\n get scrollOffset() {\n return so\n },\n getVisibleOptions() {\n return options.slice(so, so + maxV)\n },\n getOptionAt(i) {\n return options[i]\n },\n hasMoreAbove() {\n return so > 0\n },\n hasMoreBelow() {\n return so + maxV < options.length\n },\n getSelected() {\n return options[si] ?? null\n },\n moveUp() {\n moveTo(si - 1)\n },\n moveDown() {\n moveTo(si + 1)\n },\n accept() {\n return options[si] ?? null\n },\n selectByHotkey(k) {\n const i = options.findIndex((o) => o.hotkey === k)\n if (i >= 0 && !options[i]?.disabled) {\n si = i\n clamp()\n return options[i]!\n }\n return null\n },\n selectByIndex(i) {\n if (i >= 0 && i < options.length && !options[i]?.disabled) {\n si = i\n clamp()\n }\n },\n }\n}\n","/**\n * select-dialog-logic.ts — SelectDialog 的纯逻辑(键→动作映射),与 .tsx 分离便于单测\n * (测试导入 .tsx 会触发 JSX runtime 解析,故按仓库惯例把纯逻辑放 .ts)。\n *\n * RFC-174 M1:↑/k、↓/j 的判断改为查 control-registry(matchControlBinding),\n * 动作语义不变(仍返回 { type: 'up' | 'down' }),仅键位判断来源从函数体内硬编码\n * 改为中心化 registry。\n */\nimport { matchControlBinding } from '../keybindings'\n\nexport type SelectKeyAction =\n | { type: 'up' | 'down' | 'accept' | 'cancel' }\n | { type: 'index'; index: number }\n | null\n\n/** 把一次按键映射为选择动作(无映射→null)。↑/k 上、↓/j 下、1-9 直达、Enter 选定、Esc 取消。 */\nexport function selectDialogKey(\n input: string,\n key: { upArrow?: boolean; downArrow?: boolean; return?: boolean; escape?: boolean },\n): SelectKeyAction {\n if (key.escape) return { type: 'cancel' }\n if (key.return) return { type: 'accept' }\n if (matchControlBinding('list-nav-up', input, key)) return { type: 'up' }\n if (matchControlBinding('list-nav-down', input, key)) return { type: 'down' }\n if (/^[1-9]$/.test(input)) return { type: 'index', index: Number(input) - 1 }\n return null\n}\n","/**\n * contrast.ts — 按背景色亮度算高对比前景(黑/白),用于填色按钮的选中文字。\n *\n * 不能写死白字:白字配黄底(warning)几乎看不清。按背景相对亮度选黑或白,\n * 故 light/dark 主题下风险底色不同(THEME.error/warning/success 各主题 hex 不同 / ansi 为色名)\n * 都能拿到正确反色。\n */\n\nconst NAMED_LUM: Record<string, number> = {\n black: 0,\n red: 0.3,\n green: 0.55,\n yellow: 0.93,\n blue: 0.2,\n magenta: 0.4,\n cyan: 0.7,\n white: 1,\n gray: 0.5,\n grey: 0.5,\n}\n\n/** 估算颜色相对亮度(hex 用 Rec.601 加权;色名查表;未知 0.5)。 */\nfunction luminance(color: string): number {\n if (color.startsWith('#')) {\n const h = color.slice(1)\n const n =\n h.length === 3\n ? [h[0]! + h[0]!, h[1]! + h[1]!, h[2]! + h[2]!]\n : [h.slice(0, 2), h.slice(2, 4), h.slice(4, 6)]\n const [r, g, b] = n.map((x) => parseInt(x, 16) / 255)\n return 0.299 * r! + 0.587 * g! + 0.114 * b!\n }\n return NAMED_LUM[color.toLowerCase()] ?? 0.5\n}\n\n/** 给定背景色,返回高对比前景:亮底用黑字、暗底用白字。color 缺省按暗底处理(白字)。 */\nexport function contrastText(bg: string | undefined): 'black' | 'white' {\n if (!bg) return 'white'\n return luminance(bg) > 0.55 ? 'black' : 'white'\n}\n","/**\n * Button.tsx — TUI 通用按钮组件(variant + color 正交设计,对标 Radix UI Button).\n *\n * variant 控制视觉形式:\n * solid — 实心背景 + 高对比前景文字(focused 时);brackets=true 时方括号恒定\n * outline — [ label ] 方括号 + 对应色彩文字(focused 时)\n * ghost — 纯文字;focused 时同 solid 铺 color 背景 + 高对比前景文字(回归修复:此前\n * focused 只加粗变色、无背景高亮,键盘焦点在弱色终端下不够醒目,如 /model\n * \"去授权\"这类副标题内联逃生口按钮)\n *\n * color 控制语义色彩(仅 solid / outline / ghost-focused 有效):\n * accent — 蓝(THEME.selection)\n * amber — 黄(THEME.warning)\n * red — 红(THEME.error)\n * green — 绿(THEME.success)\n * gray — 灰(THEME.inactive)\n *\n * brackets 控制方括号格式:\n * outline 默认 true(focused 时显示 [ label ])\n * solid 默认 false(显示 ` label ` 空格包裹);置 true 后 unfocused 用 inactive 灰\n * ghost 默认 false\n *\n * 状态交互:\n * focused=false → 无背景,文字弱化(solid+brackets 用 inactive、solid 用 color 色、outline/ghost 用 inactive)\n * focused=true → solid/ghost 铺 color 背景+反色字加粗、outline 方括号+color 色加粗\n * disabled → 所有变体 [ label ] dimColor inactive\n *\n * 使用示例:\n * <Button variant=\"solid\" color=\"amber\" label=\"Upgrade\" focused={sel === 0} />\n * <Button variant=\"solid\" brackets color=\"accent\" label=\"Approve\" focused={sel === 0} />\n * <Button variant=\"ghost\" label=\"Cancel\" focused={sel === 1} />\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { THEME, useColors } from '../theme'\nimport type { ThemeColors } from '../theme'\nimport { contrastText } from '../dialogs/contrast'\nimport { useTabable } from '../router/Tabable'\n\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost'\nexport type ButtonColor = 'accent' | 'amber' | 'red' | 'green' | 'gray'\n\nexport interface ButtonProps {\n label: string\n variant?: ButtonVariant\n /** 语义色彩(ghost 变体下忽略,始终用 accent 蓝)。默认 accent。 */\n color?: ButtonColor\n /** @deprecated 使用 tabIndex 替代。向后兼容:传入时覆盖 useTabable 结果。 */\n focused?: boolean\n disabled?: boolean\n /** 强制方括号格式(outline 默认 true;solid/ghost 默认 false)。\n * solid+brackets=true 时 unfocused 用 inactive 灰色(而非 color 色)。 */\n brackets?: boolean\n /** tabIndex for focus traversal(默认 -1 = 不参与;0+ = 参与)。 */\n tabIndex?: number\n /** Enter 触发的 action(tabIndex ≥ 0 时通过 useTabable onAction 注册)。 */\n onAction?: () => void\n}\n\nfunction resolveBg(color: ButtonColor, colors: ThemeColors): string {\n switch (color) {\n case 'accent':\n return colors.selection\n case 'amber':\n return colors.warning\n case 'red':\n return colors.error\n case 'green':\n return colors.success\n case 'gray':\n return colors.panelBorder\n }\n}\n\nfunction resolveFg(color: ButtonColor, colors: ThemeColors): string {\n return resolveBg(color, colors)\n}\n\n/**\n * resolveButtonStyle — Button 的纯样式决策(无 hook,可直接单测)。\n *\n * 抽出的动机:Button 组件体内调用 `useTabable()`(hook),故不能作为纯函数直接调用\n * 取返回元素;样式回归测试改为调用本纯函数断言 `backgroundColor`/`color`/`bold`/`label`。\n * Button 渲染分支与本函数一一对应(disabled / ghost / solid / outline × focused)。\n */\nexport interface ButtonStyle {\n label: string\n color: string\n backgroundColor?: string\n bold: boolean\n dim: boolean\n}\n\nexport function resolveButtonStyle(\n opts: {\n label: string\n variant?: ButtonVariant\n color?: ButtonColor\n focused?: boolean\n disabled?: boolean\n brackets?: boolean\n },\n colors: ThemeColors = THEME,\n): ButtonStyle {\n const { label, variant = 'ghost', color = 'accent', focused = false, disabled = false } = opts\n const useBrackets = opts.brackets ?? variant === 'outline'\n const bracketLabel = `[ ${label} ]`\n\n if (disabled) {\n return { label: bracketLabel, color: colors.inactive, bold: false, dim: true }\n }\n\n if (variant === 'ghost') {\n const plainLabel = ` ${label} `\n if (focused) {\n const bg = resolveBg(color, colors)\n // useBrackets 时用 [ label ],否则铺背景用 plainLabel(对齐 solid focused 与本变体\n // 文档\"focused 同 solid 铺 color 背景+反色字\"——此前无条件用 bracketLabel 是 bug,\n // 会给未传 brackets 的 ghost(如 Segmented/SelectDialog)强加 [ ] 括号)。\n return { label: useBrackets ? bracketLabel : plainLabel, color: contrastText(bg), backgroundColor: bg, bold: true, dim: false }\n }\n return { label: useBrackets ? bracketLabel : plainLabel, color: colors.inactive, bold: false, dim: false }\n }\n\n const bg = resolveBg(color, colors)\n const fg = resolveFg(color, colors)\n\n if (variant === 'solid') {\n const plainLabel = ` ${label} `\n if (focused) {\n return { label: useBrackets ? bracketLabel : plainLabel, color: contrastText(bg), backgroundColor: bg, bold: true, dim: false }\n }\n return { label: useBrackets ? bracketLabel : plainLabel, color: useBrackets ? colors.inactive : fg, bold: false, dim: false }\n }\n\n // outline\n if (focused) {\n return { label: useBrackets ? bracketLabel : ` ${label} `, color: fg, bold: true, dim: false }\n }\n return { label: useBrackets ? bracketLabel : ` ${label} `, color: colors.inactive, bold: false, dim: false }\n}\n\nexport function Button({\n label,\n variant = 'ghost',\n color = 'accent',\n focused: focusedProp,\n disabled = false,\n brackets,\n tabIndex = -1,\n onAction,\n}: ButtonProps): React.ReactElement {\n // RFC-263 M6:Button 随 `/theme` 切换——colors 经 useColors() 注入纯样式函数。\n const colors = useColors()\n const { focused: tabFocused } = useTabable({ tabIndex, disabled: disabled || tabIndex < 0, onAction })\n const focused = focusedProp ?? tabFocused\n\n const style = resolveButtonStyle({ label, variant, color, focused, disabled, brackets }, colors)\n return (\n <Text\n color={style.color}\n backgroundColor={style.backgroundColor}\n bold={style.bold}\n dimColor={style.dim || undefined}\n >\n {style.label}\n </Text>\n )\n}\n","/**\n * SelectPrimitive.tsx — Radix 风格组合式 Select 组件(`<Select.Root>` 家族)。\n *\n * 用途:需要\"分组 + 可选中列表\"的场景,用声明式 JSX 树描述选项结构,而不是手写扁平\n * `SelectOption[]` 数组 + push 循环(对齐 Radix UI / shadcn 的 `Select.Root/Group/Item`\n * 组合语法,让终端 UI 的分组列表构造方式与主流 Web 组件库的心智模型一致)。\n *\n * 架构:`Select.Item`/`Select.Group`/`Select.Label`/`Select.Separator` 本身不渲染任何\n * Ink 节点(return null)——它们只是\"数据描述符\",由 `Select.Root` 通过 `React.Children`\n * 递归遍历 children 树,编译成扁平的 `SelectOption[]`,复用已有的 `createSelectState`\n * 键盘状态机(↑↓/hotkey/disabled 跳过/滚动窗口)与 `GroupDivider` 分组标题渲染——\n * 不重新发明选择逻辑,只是给已验证好用的数据驱动管线包一层组合式构造入口。\n *\n * `Select.Trigger`/`Select.Content` 是结构占位符:本仓所有 select 场景都是 openModal\n * 打开即完整列表的全屏模态,没有\"内联可展开下拉 + popover 定位\"的真实使用场景——\n * 这两个组件只做 API 对齐(保持与 Radix 语法一致的可读性),不承担实际渲染或定位职责,\n * `Content` 的 children 才是真正被遍历编译的对象;`Root` 若跳过 `Trigger`/`Content`\n * 直接以 `Group`/`Item` 作 children 同样可用(`Content` 非强制层级)。\n *\n * 键位:↑/k 上、↓/j 下、1-9 直达、Enter 选中、Esc 取消(与 `SelectDialog` 一致)。\n */\nimport React, { useMemo, useRef, useState } from 'react'\nimport { Typography } from '../components/Typography'\nimport { Box, Text, type Key } from 'ink'\nimport { createSelectState, type SelectOption } from './select'\nimport { selectDialogKey } from './select-dialog-logic'\nimport { buildSelectRows, computeSelectRowWindow } from './select-row-window'\nimport { padEndDisplayWidth } from '../term/truncate-display'\nimport { displayWidth } from '../term/insert-history'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { GroupDivider } from '../layouts/GroupDivider'\nimport { HintBar } from '../layouts/HintBar'\nimport { ScrollIndicatorAbove, ScrollIndicatorBelow } from '../components/ScrollIndicators'\nimport { Button } from '../components/Button'\nimport { SPACING, FIGURES, DIALOG_BORDER, useColors } from '../theme'\nimport { i18next } from '../i18n'\nimport { KEY } from '../input/keyhints'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { panelVisibleRows, extensionPanelMaxRows } from '../term/viewport-budget'\n\nimport { useAnimationFrame } from '../utils/animation-scheduler'\n\n/** 状态前缀呼吸周期(ms)——明暗各占一拍。 */\nconst PULSE_MS = 2000\n\n// ────────────────────────────────────────────────────────────────────────────\n// Item / Group / Label / Separator — 纯数据描述符,不渲染任何 Ink 节点\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface SelectItemProps {\n value: string\n disabled?: boolean\n /** 附加说明(渲染为选中项下方一行弱化色文本,对齐 `SelectOption.detail`)。 */\n detail?: string\n /** 条目行前导空白(默认一个空格)。 */\n indent?: string\n /**\n * 序号标记:`'number'` 渲染该项在整个列表中的序数(1-based,跨分组连续计数);\n * `'-'` / `'·'` 渲染固定项目符。不传 = 不渲染标记(默认现状)。\n */\n marker?: 'number' | '-' | '·'\n /** 行内尾随文案(同一行右侧,非另起一行的 `detail`)。 */\n trailingLabel?: string\n /** `trailingLabel` 的颜色(默认 colors.inactive)。 */\n trailingColor?: string\n /** label 颜色覆盖(独立于 `disabled`,不追加任何文案后缀)。 */\n labelColor?: string\n children: React.ReactNode\n}\n\n/** 单个可选条目。本身不渲染——`Select.Root` 遍历 children 树时提取其 props 编译为 `SelectOption`。 */\nexport function SelectItem(_props: SelectItemProps): null {\n return null\n}\nSelectItem.displayName = 'Select.Item'\n\nexport interface SelectLabelProps {\n children: React.ReactNode\n}\n\n/** 分组标题。本身不渲染——`Select.Group` 读取其 children 文本作为分组标签。 */\nexport function SelectLabel(_props: SelectLabelProps): null {\n return null\n}\nSelectLabel.displayName = 'Select.Label'\n\nexport interface SelectGroupProps {\n children: React.ReactNode\n}\n\n/** 分组容器:内部第一个 `Select.Label` 作为组标签,其余 `Select.Item` 归入该组。 */\nexport function SelectGroup(_props: SelectGroupProps): null {\n return null\n}\nSelectGroup.displayName = 'Select.Group'\n\n/** 视觉分隔(渲染为一行空白,供顶层 `Select.Group` 之间留白)。本身不渲染——仅作 children 树标记。 */\nexport function SelectSeparator(): null {\n return null\n}\nSelectSeparator.displayName = 'Select.Separator'\n\n/** 结构占位符:无内联展开/popover 定位场景,仅透传 children 供 `Select.Root` 直接消费。 */\nexport function SelectContent({ children }: { children: React.ReactNode }): React.ReactElement {\n return <>{children}</>\n}\nSelectContent.displayName = 'Select.Content'\n\n/** 结构占位符:本仓无\"点击展开\"交互,Trigger 不渲染任何内容(openModal 已在外层触发展开)。 */\nexport function SelectTrigger(_props: { children?: React.ReactNode }): null {\n return null\n}\nSelectTrigger.displayName = 'Select.Trigger'\n\n// ────────────────────────────────────────────────────────────────────────────\n// children 树 → 扁平 SelectOption[] 编译\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction textOf(node: React.ReactNode): string {\n if (typeof node === 'string' || typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map(textOf).join('')\n if (React.isValidElement(node)) {\n const props = node.props as { children?: React.ReactNode }\n return textOf(props.children)\n }\n return ''\n}\n\n/**\n * 递归遍历 `Select.Content`/`Select.Group`/`Select.Item` 组成的 children 树,\n * 编译为扁平 `SelectOption[]`(`group` 字段供 `GroupDivider` 分区渲染)。\n * 跳过 `Select.Trigger`(占位符不产出选项);`Select.Separator` 不产出选项\n * (分组间距已由 `GroupDivider` 渲染层自动处理,无需额外插入哨兵项)。\n */\nfunction compileOptions(children: React.ReactNode): SelectOption[] {\n const options: SelectOption[] = []\n let ordinal = 0\n\n function walk(node: React.ReactNode, currentGroup: string | undefined): void {\n React.Children.forEach(node, (child) => {\n if (!React.isValidElement(child)) return\n const type = child.type as { displayName?: string }\n\n if (type.displayName === 'Select.Item') {\n const p = child.props as SelectItemProps\n ordinal += 1\n options.push({\n value: p.value,\n label: textOf(p.children),\n disabled: p.disabled,\n detail: p.detail,\n indent: p.indent,\n marker: p.marker,\n ordinal,\n trailingLabel: p.trailingLabel,\n trailingColor: p.trailingColor,\n labelColor: p.labelColor,\n group: currentGroup })\n return\n }\n\n if (type.displayName === 'Select.Group') {\n const p = child.props as SelectGroupProps\n let label: string | undefined\n React.Children.forEach(p.children, (grandchild) => {\n if (React.isValidElement(grandchild) && (grandchild.type as { displayName?: string }).displayName === 'Select.Label') {\n label = textOf((grandchild.props as SelectLabelProps).children)\n }\n })\n walk(p.children, label)\n return\n }\n\n if (type.displayName === 'Select.Label' || type.displayName === 'Select.Separator' || type.displayName === 'Select.Trigger') {\n return\n }\n\n // Select.Content 或其它透传容器:继续下钻,分组归属不变。\n const p = child.props as { children?: React.ReactNode }\n if (p.children !== undefined) walk(p.children, currentGroup)\n })\n }\n\n walk(children, undefined)\n return options\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Root — 状态持有 + 键盘接管 + 渲染\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface SelectRootProps {\n /** 标题行(DialogFrame header,可选)。 */\n title?: string\n /** 副标题(弱化色,标题下一行,可选)。 */\n subtitle?: string\n /**\n * 标题下的可聚焦入口(对齐 `/model` 的「N 个 provider 插件待授权 [ 去授权 ]」提示范式,\n * 即 `SelectDialog` 的 `subtitle && hasActions` 分支)——渲染为 accent 边框盒:\n * `hint` 提示文案 + 行内 `[ label ]` ghost 按钮,位于 subtitle 之下、列表之上。\n * 焦点机:列表首项 ↑ → 入口;入口 ↓ → 列表首项;入口 Enter → 触发 `onPress`\n * (不经 onValueChange,语义是\"列表外的动作\")。缺省无入口,行为不变。\n */\n headerAction?: { hint: string; label: string; onPress: () => void }\n /** 受控选中值;不传则内部自持状态(对齐 Radix `defaultValue`/`value` 双模式,本组件仅支持 defaultValue 语义)。 */\n defaultValue?: string\n /** 选中值变化回调(Enter 确认时触发,而非每次移动都触发——对齐 SelectDialog 现有语义)。 */\n onValueChange?: (value: string) => void\n /** 取消回调(Esc)。 */\n onCancel?: () => void\n /** 单屏可见条目数(默认 10,对齐 SelectDialog)。 */\n maxVisible?: number\n /** 底部快捷键提示(不传则用默认 ↑↓/Enter/Esc 三件套)。 */\n footer?: React.ReactNode\n /** 是否持有键盘焦点(对齐仓库\"焦点门禁\"惯例,默认 true)。 */\n active?: boolean\n /**\n * 扩展键盘处理器(对齐 `FileContentViewer.onKey` 先例):在标准 ↑↓/Enter/Esc/数字直达\n * 之前调用,收到当前选中项(可能为 `null`,如空列表);返回 `true` 表示已消费,\n * 本次按键不再进入标准 select 键位处理(用于扩展如\"d 删除选中项\"这类消费方自定义动作)。\n */\n onKey?: (input: string, key: Key, selected: SelectOption | null) => boolean\n /**\n * 是否套 `DialogFrame` 独立边框(默认 `true`)。传 `false` 时不渲染边框/背景/内边距,\n * 仅输出 header + rows + footer 三段裸内容——供已有自己外层容器的消费方(如 Welcome\n * 启动横幅)嵌入复用同一套分组/条目视觉规范,避免双层边框。\n */\n frame?: boolean\n /**\n * 是否渲染选中态高亮(默认 `true`)。传 `false` 时所有条目按各自 disabled 态着色,\n * 不应用 `colors.selection`——供纯展示(无键盘导航意义)场景使用,如 `active=false`\n * 的静态列表,避免出现\"看起来被选中\"但实际不可交互的视觉误导。\n */\n highlightSelection?: boolean\n /** 终端总行数(RFC-301 窗口上限)。缺省 24。 */\n rows?: number\n /**\n * extension 家族专用高度模式(优化 2026-08-09 用户定稿):总帧高上限 rows/3\n * (更紧于全局 RFC-301 的 2/5),透传给 DialogFrame.maxHeight。true 时窗口预算\n * 同步按 1/3 预算重算(不再叠加 15 min),避免窗口本身已经顶到 min 15 超出上限\n * 再被 maxHeight 硬切。其他消费方保持全局 2/5 不受影响。\n */\n extensionHeightBudget?: boolean\n /**\n * detail 排布位置——对齐 SelectDialog 同名 prop:\n * `'below'`(缺省)detail 独占条目下方一行;\n * `'inline'` label → trailingLabel → detail 三列同行左对齐(各自按全表最大宽度 pad)。\n * 缺省 below 保持现有行为不变;inline 适用于 KeybindingsDialog 等需要键位+说明同行的场景。\n */\n detailPlacement?: 'below' | 'inline'\n children: React.ReactNode\n}\n\nexport function SelectRoot({\n title,\n subtitle,\n headerAction,\n defaultValue,\n onValueChange,\n onCancel,\n maxVisible = 10,\n footer,\n active = true,\n onKey,\n frame = true,\n highlightSelection = true,\n rows = 24,\n extensionHeightBudget = false,\n detailPlacement = 'below',\n children }: SelectRootProps): React.ReactElement {\n const colors = useColors()\n const options = useMemo(() => compileOptions(children), [children])\n // inline 模式三列对齐——与 SelectDialog 同款:label/trailingLabel 各按组内最大宽度 pad。\n const inlineLabelWidth = useMemo(() => {\n if (detailPlacement !== 'inline') return 0\n return Math.max(0, ...options.map((o) => displayWidth(o.label)))\n }, [detailPlacement, options])\n const inlineTrailingWidth = useMemo(() => {\n if (detailPlacement !== 'inline') return 0\n return Math.max(0, ...options.map((o) => displayWidth(o.trailingLabel ?? '')))\n }, [detailPlacement, options])\n\n const selRef = useRef<ReturnType<typeof createSelectState> | null>(null)\n if (!selRef.current) {\n const initialIndex = defaultValue ? options.findIndex((o) => o.value === defaultValue) : -1\n selRef.current = createSelectState(options, { maxVisible })\n if (initialIndex >= 0) selRef.current.selectByIndex(initialIndex)\n }\n const sel = selRef.current\n\n // 呼吸动画:running / needs-input 状态前缀(* / !)在明暗间缓慢切换。\n // RFC-319 M2:走统一调度器——顺带补齐 reducedMotion 语义(此前本组件无该判断,\n // 开启 reduced motion 仍在闪,属遗漏;现由调度器统一处理 = 零注册即冻结)。\n const [pulse, setPulse] = useState(false)\n useAnimationFrame(PULSE_MS, (f) => setPulse(f % 2 === 1))\n\n const [, force] = useState(0)\n const rerender = (): void => force((n) => n + 1)\n\n // 标题下入口的焦点态:true = 焦点在 headerAction 按钮,false = 焦点在列表(缺省)。\n // 无 headerAction 时恒 false(该分支永不进入)。对齐 SelectDialog.subtitleActions 的\n // actionFocus 语义,但本组件只有单个入口,用布尔即可(无按钮间 ← → 切换)。\n // 列表无任何可选项时初始聚焦入口(对齐 SelectDialog:全 disabled → 逃生口按钮 0)。\n const [actionFocused, setActionFocused] = useState<boolean>(\n () => !!headerAction && !options.some((o) => !o.disabled),\n )\n\n useFallbackInput('select-primitive', (input, key) => {\n if (onKey?.(input, key, sel.getSelected())) {\n rerender()\n return\n }\n // ── 标题下入口(headerAction)焦点态:Enter 触发、↓ 回列表首项、Esc 取消。 ──\n if (headerAction && actionFocused) {\n if (key.return) {\n headerAction.onPress()\n return\n }\n if (key.downArrow) {\n setActionFocused(false)\n rerender()\n return\n }\n // Esc 在入口态直接取消(与列表态一致);其余键忽略(入口无文本输入)。\n if (key.escape) {\n onCancel?.()\n return\n }\n return\n }\n const action = selectDialogKey(input, key)\n if (!action) return\n switch (action.type) {\n case 'cancel':\n onCancel?.()\n return\n case 'up': {\n // 列表已到顶(moveUp 不改变 selectedIndex)且有标题下入口 → 焦点上移到入口。\n const before = sel.selectedIndex\n sel.moveUp()\n if (headerAction && sel.selectedIndex === before) {\n setActionFocused(true)\n }\n rerender()\n return\n }\n case 'down':\n sel.moveDown()\n rerender()\n return\n case 'index': {\n sel.selectByIndex(action.index)\n const o = sel.getSelected()\n if (o && !o.disabled) onValueChange?.(o.value)\n return\n }\n case 'accept': {\n const o = sel.accept()\n if (o && !o.disabled) onValueChange?.(o.value)\n return\n }\n }\n },\n { isActive: active },\n )\n\n // 固定物理行滚动窗口(真实用户反馈:分组列表滚动时高度仍会跳动,根因见\n // select-row-window.ts 头注释——此前按条目数取窗口,分隔线/detail 行数量随滚动位置\n // 浮动)。allRows 基于全部 options 一次性展开,窗口切片以物理行为单位,任意滚动位置\n // 渲染行数恒为 windowSize(不足时 fillerCount 补空行)。仅 `frame=true`(独立弹层模态)\n // 限制窗口大小为 maxVisible——`frame=false` 用于 Welcome 启动横幅等静态嵌入展示,\n // 需展示全部条目(不裁剪、不补空行,与既有行为一致),故窗口大小取 Infinity。\n const allRows = useMemo(() => buildSelectRows(options, detailPlacement), [options, detailPlacement])\n const scrollTopRef = useRef(0)\n // RFC-301:frame 时窗口 = clamp(内容行 + chrome, 15, 2/5) − chrome(chrome≈9,同 SelectDialog);\n // frame=false(静态嵌入)保持 Infinity 展示全部。\n // extensionHeightBudget(扩展列表 1/3 上限):跳过 min 15 自适应分支与 filler 撑满——\n // 窗口 = min(内容自然行, 1/3 上限 − chrome)。短列表帧高 = 内容 + chrome(自然矮),\n // 长列表才截到 1/3(用户语义:\"最大高度 1/3\",非\"钉死 1/3\")。\n const windowSize = frame\n ? extensionHeightBudget\n ? Math.max(1, Math.min(allRows.length, extensionPanelMaxRows(rows) - 9))\n : panelVisibleRows(allRows.length + 9, rows) - 9\n : Number.POSITIVE_INFINITY\n const win = computeSelectRowWindow(allRows, sel.selectedIndex, windowSize, scrollTopRef.current)\n scrollTopRef.current = win.scrollTop\n\n // 注释见下——`sel.getSelected()` 返回的是内部 optionsRef 的旧引用,而 `allRows` /\n // `options` 可能在父组件重渲染后指向新数组(不同对象引用)——`===` 判断会静默失败。\n // `options[sel.selectedIndex]` 与 `allRows[i].option` 同源同一引用,恒相等。\n const selectedOption = options[sel.selectedIndex] ?? null\n\n const header = (\n <>\n {title && <DialogTitle>{title}</DialogTitle>}\n {title && <Box marginTop={SPACING.tight} />}\n {subtitle && <Typography type='secondary'>{subtitle}</Typography>}\n {headerAction && (\n // 对齐 SelectDialog 的 subtitleActions 视觉(/model「N 个 provider 插件待授权\n // [ 去授权 ]」):accent 边框盒内 = 提示文案 + 行内 ghost 方括号按钮。\n <Box\n flexDirection='row'\n alignSelf='flex-start'\n marginTop={subtitle ? SPACING.tight : SPACING.none}\n {...DIALOG_BORDER}\n borderColor={colors.accent}\n paddingX={SPACING.tight}\n >\n <Text color={colors.accent}>{headerAction.hint} </Text>\n <Button\n variant='ghost'\n brackets\n color='accent'\n label={headerAction.label}\n focused={actionFocused}\n tabIndex={-1}\n />\n </Box>\n )}\n </>\n )\n\n const body = (\n <>\n {/* RFC-182 审计 AP3:指示器行 height=1 恒占位——hasMore 变化(滚过列表边界)时该行\n 出现/消失曾使帧高 ±1(FileContentViewer\"下抖\"同款模式,D6 已定为规则 7)。 */}\n <Box height={1}>\n <ScrollIndicatorAbove hasMore={win.hasMoreAbove} />\n </Box>\n {win.rows.map((row, i) => {\n if (row.kind === 'spacer') {\n return <Text key={`spacer-${i}`}> </Text>\n }\n if (row.kind === 'divider') {\n return (\n <Box key={`div-${i}`} flexDirection='column'>\n <GroupDivider label={row.label} indent='' />\n </Box>\n )\n }\n const o = row.option\n if (row.kind === 'detail') {\n const indent = o.indent ?? ' '\n const detail = o.detail ?? ''\n // detail 行前导空白与选项行 label 起始位置对齐:\n // 选项行 = pointer区(2) + indent + markerText + label\n // detail行 = pointer区(2) + indent + markerPad + detail\n const markerPad = o.marker === 'number'\n ? ' '.repeat(`${o.ordinal}. `.length)\n : o.marker ? ' '.repeat(`${o.marker} `.length) : ''\n // RFC-234 M5b 呼吸动画:● 前缀匹配(语言无关),标签来自 i18n\n const prefixMatch = /^● (.+?) · /u.exec(detail)\n const prefixLabel = prefixMatch?.[1]\n const rest = prefixMatch ? detail.slice(prefixMatch[0].length) : detail\n return (\n // RFC-182 审计 AP1:detail 是用户可控文本(扩展/技能描述,可含长 CJK)——\n // truncate-end 防折行(detail 行属结构化行:缩进对齐语义,不预折行)。\n <Text key={`detail-${row.optionIndex}`} wrap='truncate-end'>\n <Typography type='secondary' dim>{' '}{indent}{markerPad}</Typography>\n {prefixLabel\n ? <Text color={colors.accent}>\n {pulse ? FIGURES.breatheBright : FIGURES.breatheDim}{' '}\n {prefixLabel}\n </Text>\n : null\n }\n <Typography type='secondary' dim>{prefixLabel ? ` · ${rest}` : rest}</Typography>\n </Text>\n )\n }\n const indent = o.indent ?? ' '\n const markerText = o.marker === 'number' ? `${o.ordinal}. ` : o.marker ? `${o.marker} ` : ''\n // headerAction 持焦点时列表 cursor 降级为弱色(焦点归属唯一:入口态列表不应显得被选中)。\n const isHighlighted = highlightSelection && o === selectedOption && !actionFocused\n // inline 三列:label → trailingLabel → detail 合成单 Text(对齐 SelectDialog 同款——\n // 分段 Ink Text 列起笔不稳定,合成字符串 + displayWidth pad 才是真左对齐)。\n const inlineMode = detailPlacement === 'inline' && o.detail\n return (\n // key 用 row.optionIndex(对齐 SelectDialog 同款修复)——o.value 在合法场景下\n // 可能重复出现(如同一 value 既在「最近使用」分组又在其归属分组各出现一次),\n // 用 value 作 key 在两个重复项同时落入滚动窗口时会产生 React key 冲突。\n <Box key={`item-${row.optionIndex}`} flexDirection='row'>\n <Text\n color={o.disabled ? colors.inactive : isHighlighted ? colors.selection : (o.labelColor ?? colors.inactive)}\n dimColor={o.disabled}\n bold={isHighlighted && !o.disabled}\n >\n {/* 选中态箭头(RFC-147 D-A1 统一标准,对齐 SelectDialog 同款修复)——\n disabled 项恒不显示箭头。marker(序号/项目符)与箭头共存时箭头在最前。\n 选中态额外叠加粗体(三重强化,RFC-098/147/183 纪律修订)。 */}\n {isHighlighted && !o.disabled ? `${FIGURES.pointer} ` : ' '}\n {indent}\n {markerText}\n {inlineMode\n ? `${padEndDisplayWidth(o.label, inlineLabelWidth)} ${padEndDisplayWidth(o.trailingLabel ?? '', inlineTrailingWidth)} ${o.detail}`\n : o.label}\n {o.disabled ? ` (${i18next.t('common.disabled')})` : ''}\n </Text>\n {o.trailingLabel && !inlineMode && (\n <>\n <Typography type='secondary'>{' '}</Typography>\n <Text color={o.trailingColor ?? colors.inactive} dimColor={!o.trailingColor}>\n {o.trailingLabel}\n </Text>\n </>\n )}\n </Box>\n )\n })}\n {Array.from({ length: win.fillerCount }, (_, i) => (\n <Text key={`filler-${i}`}> </Text>\n ))}\n <Box height={1}>\n <ScrollIndicatorBelow hasMore={win.hasMoreBelow} />\n </Box>\n </>\n )\n\n if (!frame) {\n return (\n <Box flexDirection='column'>\n {header}\n {body}\n </Box>\n )\n }\n\n return (\n <DialogFrame\n header={header}\n footer={footer ?? <HintBar items={[[`${KEY.up}${KEY.down}`, i18next.t('hint.select')], [KEY.enter, i18next.t('hint.confirm')], [KEY.esc, i18next.t('hint.cancel')]]} />}\n maxHeight={extensionHeightBudget && frame ? extensionPanelMaxRows(rows) : undefined}\n >\n {body}\n </DialogFrame>\n )\n}\n\n/** `Select.*` 命名空间导出(对齐 `<Select.Root><Select.Group>…` 组合语法)。 */\nexport const Select = {\n Root: SelectRoot,\n Trigger: SelectTrigger,\n Content: SelectContent,\n Group: SelectGroup,\n Label: SelectLabel,\n Item: SelectItem,\n Separator: SelectSeparator }\n","/**\n * ConfirmDialog.tsx — 通用确认对话框.\n *\n * 用途:打断确认、删除确认等「是/否」场景。\n * 从 PasteConfirmDialog 提取通用骨架:title + message + [confirm] / [cancel] 横排按钮。\n * 可选 [extra] 第三个按钮(如模型回退的「登录」中转动作)。\n *\n * 键位:← → / Tab 切换 · Enter 确认当前 · Esc 取消\n */\nimport React, { useState, useCallback } from 'react'\nimport { resolveHorizontalStep, cycleIndex } from '../input/horizontal-step'\nimport { Typography } from '../components/Typography'\nimport { Box, Text } from 'ink'\nimport { Button } from '../components/Button'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { HintBar } from '../layouts/HintBar'\nimport { SPACING, FIGURES } from '../theme'\nimport { i18next } from '../i18n'\nimport { KEY } from '../input/keyhints'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { useTabable } from '../router/Tabable'\n\nexport interface ConfirmDialogProps {\n title: string\n /** 正文(单行或多行)。 */\n message: string | string[]\n /** 确认按钮文案(默认 i18next.t('common.confirmButton'))。 */\n confirmLabel?: string\n /** 取消按钮文案(默认 i18next.t('common.genericCancelButton'))。 */\n cancelLabel?: string\n /** 确认按钮颜色(默认 'accent')。 */\n confirmColor?: 'accent' | 'amber' | 'red' | 'green'\n /** 破坏性确认(如中止/删除):默认焦点落在安全的「取消」上,防 Enter 误触。 */\n dangerous?: boolean\n /** 可选的第三个按钮(位置在确认和取消之间)。不传=双按钮。 */\n extraLabel?: string\n /** 第三个按钮颜色(默认 'amber')。 */\n extraColor?: 'accent' | 'amber' | 'red' | 'green'\n /** 可选的勾选框文案(如\"不再提醒\")。不传=不渲染勾选框。 */\n checkLabel?: string\n /** 勾选框初始状态(默认未勾选)。 */\n checked?: boolean\n onConfirm: () => void\n onCancel: () => void\n /** 第三个按钮回调(仅在 extraLabel 提供时生效)。 */\n onExtra?: () => void\n /** 勾选框变化回调(仅在 checkLabel 提供时生效)。 */\n onCheckChange?: (checked: boolean) => void\n}\n\nexport function ConfirmDialog({\n title,\n message,\n confirmLabel,\n cancelLabel,\n confirmColor = 'accent',\n dangerous = false,\n extraLabel,\n extraColor = 'amber',\n checkLabel,\n checked = false,\n onConfirm,\n onCancel,\n onExtra,\n onCheckChange,\n}: ConfirmDialogProps): React.ReactElement {\n const threeBtn = !!extraLabel\n const maxIdx = threeBtn ? 2 : 1\n const [selected, setSelected] = useState<number>(dangerous ? maxIdx : 0)\n const [isChecked, setIsChecked] = useState(checked)\n const lines = Array.isArray(message) ? message : [message]\n\n const toggleCheck = useCallback(() => {\n const next = !isChecked\n setIsChecked(next)\n onCheckChange?.(next)\n }, [isChecked, onCheckChange])\n\n // RFC-258:注册 onEscape 让 Router Esc handler 先走 cancel resolver(而非裸 goBack),\n // 避免 Esc 关闭弹窗后 resolver 永不调用导致 agent 阻塞。\n useTabable({\n onEscape: () => { onCancel(); return true },\n })\n\n // 横排按钮键盘导航:← → / Tab 切换 · Enter 确认 · Esc 取消 · Space 勾选框\n useFallbackInput('confirm-dialog-buttons', (_input, key) => {\n if (key.escape) {\n onCancel()\n return true\n }\n // RFC-289 D5:左右箭头方向解算收敛到单一真源(原实现 leftArrow 误走正向)\n const step = resolveHorizontalStep(key)\n if (step !== 0) {\n setSelected((s) => cycleIndex(s, step, maxIdx + 1))\n return true\n }\n if (key.return) {\n // 函数式读取最新 selected,避免闭包 stale\n setSelected((s) => {\n if (s === 0) onConfirm()\n else if (threeBtn && s === 1) onExtra?.()\n else onCancel()\n return s\n })\n return true\n }\n if (checkLabel && _input === ' ' && !key.ctrl && !key.meta) {\n toggleCheck()\n return true\n }\n return false\n })\n\n const confirmText = confirmLabel ?? i18next.t('common.confirmButton')\n const cancelText = cancelLabel ?? i18next.t('common.genericCancelButton')\n\n return (\n <DialogFrame\n header={<DialogTitle>{title}</DialogTitle>}\n footer={<HintBar items={[[`${KEY.left}${KEY.right}`, i18next.t('hint.switch')], [KEY.enter, i18next.t('hint.confirm')], [KEY.esc, i18next.t('hint.cancel')]]} />}\n >\n <Box marginTop={SPACING.tight} flexDirection='column'>\n {lines.map((l, i) => (\n <Text key={i} wrap='truncate-end'>{l === '' ? ' ' : l}</Text>\n ))}\n </Box>\n {checkLabel && (\n <Box marginTop={SPACING.tight}>\n <Typography type='secondary'>\n {isChecked ? FIGURES.bullet : FIGURES.todoPending}{' '}\n </Typography>\n <Typography type='secondary'>{checkLabel}</Typography>\n </Box>\n )}\n <Box marginTop={SPACING.tight} flexDirection='row' gap={SPACING.section}>\n <Button variant='solid' brackets color={confirmColor} label={confirmText} focused={selected === 0} />\n {threeBtn && (\n <Button variant='solid' brackets color={extraColor} label={extraLabel!} focused={selected === 1} />\n )}\n <Button variant='solid' brackets color='gray' label={cancelText} focused={selected === maxIdx} />\n </Box>\n </DialogFrame>\n )\n}\n","/**\n * List.tsx — 可聚焦列表组件(一个 Tabable tab stop,内部 ↑↓ 移动选中项)。\n *\n * 对照浏览器 <select> 或 <ul role=\"listbox\">:\n * - 整个列表是一个 tab stop(Tab 到达后 ↑↓ 在条目间移动,不是 Tab)\n * - Enter 触发当前选中条目的 onAction\n * - 聚焦时 ▶ 指示当前选中项\n * - 失焦时不响应任何键盘\n *\n * 用法:\n * <List tabIndex={0} autoFocus options={models} onSelect={(v) => onModel(v)} />\n *\n * 与逐条 <Tabable> 的区别:\n * <Tabable> 逐条 = 每个 <li> 是独立 tab stop(Tab 10 次才跳过 10 项列表)\n * <List> 整体 = 整个列表一个 tab stop(Tab 1 次跳过,↑↓ 在内部移动)\n */\nimport React, { useState } from 'react'\nimport { Box, Text } from 'ink'\nimport { useTabable } from '../router/Tabable'\nimport { useTabableInput } from '../router/FocusBus'\nimport { FIGURES, SPACING, useColors } from '../theme' \n\n// ═══════════════════ 类型 ═══════════════════\n\nexport interface ListOption {\n value: string\n label: string\n detail?: string\n disabled?: boolean\n}\n\nexport interface ListProps {\n /** -1=不可Tab 0+=Tab 顺序(传给 useTabable) */\n tabIndex?: number\n /** 路由激活时自动聚焦 */\n autoFocus?: boolean\n /** 选项列表 */\n options: ListOption[]\n /** 选中条目回调(Enter 触发) */\n onSelect: (value: string) => void\n /** 可选:初始选中 index */\n initialIndex?: number\n /** 可见行数上限(超出滚动),缺省 8 */\n maxVisible?: number\n /** 手动指定 ID */\n id?: string\n /** 紧凑模式:每项只占 1 行,不在条目之间插入空行。 */\n compact?: boolean\n}\n\n// ═══════════════════ List 组件 ═══════════════════\n\nexport function List({\n tabIndex = 0,\n autoFocus = false,\n options,\n onSelect,\n initialIndex = 0,\n maxVisible = 8,\n id,\n compact = false,\n}: ListProps): React.ReactElement {\n const colors = useColors()\n const { focused } = useTabable({ tabIndex, autoFocus, id })\n\n const [selectedIndex, setSelectedIndex] = useState(\n Math.min(initialIndex, options.length - 1),\n )\n\n // 聚焦时接管键盘\n useTabableInput(focused, (input, key) => {\n if (key.upArrow) {\n setSelectedIndex((i) => {\n // 跳过 disabled\n let next = i\n for (let step = 1; step <= options.length; step++) {\n const candidate = (i - step + options.length) % options.length\n if (!options[candidate]?.disabled) { next = candidate; break }\n }\n return next\n })\n return true\n }\n\n if (key.downArrow) {\n setSelectedIndex((i) => {\n let next = i\n for (let step = 1; step <= options.length; step++) {\n const candidate = (i + step) % options.length\n if (!options[candidate]?.disabled) { next = candidate; break }\n }\n return next\n })\n return true\n }\n\n if (key.return) {\n const opt = options[selectedIndex]\n if (opt && !opt.disabled) onSelect(opt.value)\n return true\n }\n\n // 数字键 1-9 快捷直达\n for (let i = 0; i < Math.min(options.length, 9); i++) {\n if (input === String(i + 1) && options[i] && !options[i]!.disabled) {\n onSelect(options[i]!.value)\n return true\n }\n }\n return false\n })\n\n // 滚动窗口\n const total = options.length\n const start = total <= maxVisible\n ? 0\n : Math.min(Math.max(0, selectedIndex - Math.floor(maxVisible / 2)), total - maxVisible)\n const windowed = options.slice(start, start + maxVisible)\n\n return (\n <Box flexDirection='column'>\n {start > 0 && (\n <Box><Text color={colors.inactive} dimColor> ▲ {start}</Text></Box>\n )}\n {windowed.map((opt, wi) => {\n const i = start + wi\n const isSelected = i === selectedIndex\n const showPointer = isSelected && focused\n const color = opt.disabled\n ? colors.inactive\n : showPointer\n ? colors.selection\n : isSelected\n ? colors.selection\n : colors.inactive\n const bold = isSelected && !opt.disabled\n\n return (\n <Box key={opt.value} marginBottom={compact ? SPACING.none : SPACING.tight} flexDirection='column'>\n <Text color={color} bold={bold}>\n {showPointer ? `${FIGURES.pointer} ` : ' '}{opt.label}\n </Text>\n {opt.detail && (\n <Box marginLeft={SPACING.section}><Text color={colors.inactive}>{opt.detail}</Text></Box>\n )}\n </Box>\n )\n })}\n {start + maxVisible < total && (\n <Box><Text color={colors.inactive} dimColor> ▼ {total - start - maxVisible}</Text></Box>\n )}\n </Box>\n )\n}\n\n// ═══════════════════ ListItem(可选:单独使用时的条目渲染) ═══════════════════\n\nexport interface ListItemProps {\n label: string\n detail?: string\n disabled?: boolean\n children?: React.ReactNode\n}\n\n/**\n * <ListItem> — 在 <List> 外单独使用时的条目渲染(纯展示,不含焦点逻辑)。\n */\nexport function ListItem({ label, detail, disabled, children }: ListItemProps): React.ReactElement {\n const colors = useColors()\n return (\n <Box flexDirection='column'>\n <Text color={disabled ? colors.inactive : undefined}>{label}</Text>\n {detail && <Text color={colors.inactive} dimColor>{detail}</Text>}\n {children}\n </Box>\n )\n}\n","/**\n * DataList.tsx — 分组键值信息列表(`/about` 等只读信息弹窗的行内双色升级版)。\n *\n * 命名对齐 Radix Themes `DataList.Root/Item/Label/Value`(本仓设计语言明确对标 Radix Themes,\n * 见 `docs/design/tui-design-language.md`)——同类\"展示元数据键值对\"的组件语义。\n *\n * 背景:`formatKeyValueLines`(`messages/key-value-lines.ts`)产出纯字符串数组,\n * `InfoDialog` 只能按整行套色(`lineTones`),无法做「label 弱化、value 正常显示」这类\n * 同一行两种颜色的排版——字符串方案的天花板。本组件是 JSX 版本,供有「分组标题 + 行内\n * label/value 双色」需求的消费方(目前仅 `/about`)使用;`formatKeyValueLines` 继续服务\n * 其余无分组、无双色诉求的简单场景(`/usage`/`/permissions`/`upgrade-check` 等),不迁移。\n *\n * 视觉规则(对齐 `docs/design/tui-design-language.md`):\n * - label 恒定 `THEME.inactive`(字段名弱化,不可配置——与 value 形成固定的视觉主次,\n * 对应 Radix `DataList.Label` 默认灰色角色)。\n * - value 默认终端前景色,可选 `tone` 覆盖(如需要强调的字段值)。\n * - 分组标题复用现成 `GroupDivider`(大写 + bold + inactive,零新造视觉语言)。\n * - 标签列按 `displayWidth` 动态对齐(CJK 宽字符占 2),逻辑与 `formatKeyValueLines` 一致,\n * 但对齐范围限定在同一分组内(不同分组的字段语义不同,跨组强行对齐无意义)。\n */\nimport React from 'react'\nimport { Box, Text } from 'ink'\nimport { GroupDivider } from '../layouts/GroupDivider'\nimport { SPACING, useColors } from '../theme'\nimport type { ThemeColors } from '../theme'\nimport { displayWidth } from '../term/insert-history'\n\nexport type DataListTone = 'success' | 'warning' | 'error' | 'muted' | 'secondary'\n\n/** 按 tone 现读 `THEME` 取色(函数形式而非模块级常量表,避免复制粘贴时遗漏更新)。 */\nfunction toneColorFor(tone: DataListTone, colors: ThemeColors): string | undefined {\n switch (tone) {\n case 'success':\n return colors.success\n case 'warning':\n return colors.warning\n case 'error':\n return colors.error\n case 'muted':\n return colors.inactive\n case 'secondary':\n return colors.secondary\n }\n}\n\nexport interface DataListRow {\n label: string\n value: string\n /** value 侧的可选强调色;不传走默认前景色。 */\n tone?: DataListTone\n}\n\nexport type DataListEntry =\n | { kind: 'group'; title: string }\n | ({ kind: 'row' } & DataListRow)\n | { kind: 'blank' }\n /** 无 label/value 配对的纯文本行(如底部提示语),不参与组内标签对齐计算。 */\n | { kind: 'text'; value: string; tone?: DataListTone }\n\nexport interface DataListProps {\n entries: readonly DataListEntry[]\n}\n\n/** 单个分组内的连续 row 序列,用于按组内最长 label 动态对齐。 */\nfunction groupRows(entries: readonly DataListEntry[]): DataListEntry[][] {\n const groups: DataListEntry[][] = []\n let current: DataListEntry[] = []\n for (const e of entries) {\n if (e.kind === 'group') {\n if (current.length > 0) groups.push(current)\n current = [e]\n } else {\n current.push(e)\n }\n }\n if (current.length > 0) groups.push(current)\n return groups\n}\n\nexport function DataList({ entries }: DataListProps): React.ReactElement {\n const colors = useColors()\n const chunks = groupRows(entries)\n return (\n <Box flexDirection=\"column\">\n {chunks.map((chunk, ci) => {\n const rows = chunk.filter((e): e is { kind: 'row' } & DataListRow => e.kind === 'row')\n const maxLabelWidth = Math.max(0, ...rows.map((r) => displayWidth(r.label)))\n return (\n <Box key={ci} flexDirection=\"column\" marginTop={ci > 0 ? SPACING.tight : SPACING.none}>\n {chunk.map((e, i) => {\n if (e.kind === 'group') return <GroupDivider key={i} label={e.title} />\n if (e.kind === 'blank') return <Text key={i}> </Text>\n if (e.kind === 'text') {\n return (\n <Text key={i} color={e.tone ? toneColorFor(e.tone, colors) : undefined} dimColor={e.tone === 'muted'}>\n {e.value}\n </Text>\n )\n }\n const pad = ' '.repeat(Math.max(0, maxLabelWidth - displayWidth(e.label)))\n return (\n <Text key={i}>\n {' '}\n <Text color={colors.inactive}>{e.label}</Text>\n {pad}\n {' '}\n <Text color={e.tone ? toneColorFor(e.tone, colors) : undefined}>{e.value}</Text>\n </Text>\n )\n })}\n </Box>\n )\n })}\n </Box>\n )\n}\n","/**\n * InfoDialog — 只读信息模态窗(/permissions、/status 等)。\n * 只读展示,Esc 关闭;接管输入焦点。\n *\n * action:错误对话框底部的单个可操作按钮,语义由调用方决定(不再只有\"重试\"一种)——\n * 例如认证失效场景传 kind:'login',渲染 [ 去登录 ] 唤起 /login 而非重复一次必然复现\n * 401 的失败请求(见 interactive.ts 的 authError 分支)。\n */\nimport React from 'react'\nimport { Box, Text } from 'ink'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { Button } from '../components/Button'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { HintBar } from '../layouts/HintBar'\nimport { DataList, type DataListEntry } from '../components/DataList'\nimport { SPACING, useColors } from '../theme'\nimport type { ThemeColors } from '../theme'\nimport { i18next } from '../i18n'\nimport { KEY } from '../input/keyhints'\n\nexport interface InfoDialogAction {\n /**\n * 'retry':重试原请求;'login':唤起 /login 面板(认证失效场景,重试必然复现同一失败);\n * 'extension':唤起 /extension 面板(插件高危能力待授权场景——用户需要的是去信任插件\n * 授予能力,而非重新登录/重试,见 /model refresh 结果 dialog 的\"待授权\"分支)。\n */\n kind: 'retry' | 'login' | 'extension'\n run: () => void\n}\n\n/**\n * 单行语义色调(RFC-126 附带修复:/model refresh 结果三态视觉分离):\n * 'success' = 正向反馈(绿,如 provider 已可用);'muted' = 中性/可忽略状态(灰弱化,\n * 如未认证——不是错误,无需处理);'warning' = 需要用户动作(琥珀,如能力待授权);\n * 未指定 = 默认前景色(既有行为,兼容全部现有调用点)。\n */\nexport type InfoLineTone = 'success' | 'muted' | 'warning'\n\nexport interface InfoDialogProps {\n /** 标题行(上边框下方第一行,blue bold)。 */\n title: string\n /** 正文行列表。`dataListEntries` 提供时忽略(见下)。 */\n lines: string[]\n /**\n * 按行索引指定色调(可选,稀疏映射——只需给需要着色的行索引赋值)。\n * 不提供时全部行走默认前景色,与既有 ~12 处 openInfo 调用点零行为变化。仅在 `lines`\n * 路径生效(`dataListEntries` 路径下由 `DataList` 自行控制着色)。\n */\n lineTones?: Record<number, InfoLineTone>\n onDismiss: () => void\n /** 底部单个可操作按钮(非空时渲染,Enter 触发)。取代原 onRetry——语义由 kind 决定文案/颜色。 */\n action?: InfoDialogAction\n /**\n * 分组键值正文(可选)——供需要「分组标题 + 行内 label/value 双色」的场景(如 `/about`)\n * 替代 `lines` 纯文本渲染,经 `DataList` 渲染。提供时优先于 `lines`(`lines` 仍需传\n * 空数组占位,类型上不做互斥以保持向后兼容——~12 处既有 `lines` 调用点零改动)。\n * 用纯数据(非 `ReactNode`)传递,避免 `@x-otto/cli` 引入 `react` 依赖。\n */\n dataListEntries?: DataListEntry[]\n}\n\n/** 按 kind 现取文案(函数形式而非模块级常量表——模块级 t() 在 import 时固化,切语言不更新)。 */\nfunction actionLabelFor(kind: InfoDialogAction['kind']): string {\n switch (kind) {\n case 'retry':\n return i18next.t('retry.nowButton')\n case 'login':\n return i18next.t('model.goToLoginButton')\n case 'extension':\n return i18next.t('model.goToExtensionButton')\n }\n}\n\n/** 按 tone 现读 `THEME` 取色(函数形式而非模块级常量表,避免复制粘贴时遗漏更新)。 */\nfunction toneColorFor(tone: InfoLineTone, colors: ThemeColors): string {\n switch (tone) {\n case 'success':\n return colors.success\n case 'muted':\n return colors.inactive\n case 'warning':\n return colors.warning\n }\n}\n\nexport function InfoDialog({ title, lines, lineTones, onDismiss, action, dataListEntries }: InfoDialogProps): React.ReactElement {\n const colors = useColors()\n useFallbackInput('info-dialog', (_inputChar, key) => {\n if (key.escape) {\n onDismiss()\n return true\n } else if (key.return && action) {\n onDismiss()\n queueMicrotask(() => action.run())\n return true\n }\n return false\n })\n\n const label = action ? actionLabelFor(action.kind) : undefined\n\n return (\n <DialogFrame\n header={<DialogTitle>{title}</DialogTitle>}\n footer={\n <HintBar\n items={action ? [[KEY.enter, label!], [KEY.esc, i18next.t('hint.close')]] : [[KEY.esc, i18next.t('hint.close')]]}\n />\n }\n >\n <Box marginTop={SPACING.tight} flexDirection=\"column\">\n {dataListEntries ? (\n <DataList entries={dataListEntries} />\n ) : (\n lines.map((l, i) => {\n const tone = lineTones?.[i]\n return (\n <Text key={i} color={tone ? toneColorFor(tone, colors) : undefined} dimColor={tone === 'muted'} wrap='truncate-end'>\n {l === '' ? ' ' : l}\n </Text>\n )\n })\n )}\n </Box>\n {action && (\n <Box marginTop={SPACING.tight}>\n <Button variant='solid' brackets color='accent' label={label!} focused={true} />\n </Box>\n )}\n </DialogFrame>\n )\n}\n","/**\n * ProgressBar.tsx — 统一进度条 Ink 组件(RFC-279)。\n *\n * 收编 4 处简单二色场景(ProgressDialog/CachePane/ContextDetailPane/InspectorPane 回退\n * 态)+ 2 处复杂场景(InspectorPane.ContextBar 多段染色、occupancy-bar 触发线标记)的\n * 渲染骨架。业务计算(token 占比分段、触发线阈值换算)保留在各消费方,本组件只消费\n * `computeProgressBarModel` 的输出并渲染。\n */\nimport React from 'react'\nimport { Text } from 'ink'\nimport { useColors } from '../../theme'\nimport { computeProgressBarModel } from './progress-bar-model'\nimport { resolveProgressChars } from './progress-bar-chars'\nimport type { ProgressCharPreset, ProgressChars } from './progress-bar-chars'\n\nexport interface ProgressBarProps {\n percent: number\n width: number\n chars?: ProgressCharPreset | ProgressChars\n /** 简单场景:单色(含可选阈值动态变色函数)。未传时回退 THEME.inactive。 */\n color?: string | ((pct: number) => string)\n /**\n * 复杂场景:多段染色(对齐 InspectorPane.ContextBar),与 color 互斥(segments 优先)。\n * `dimColor` 可选——对齐 ContextBar 现状 `free` 段的 `dimColor={seg.kind==='free'}`\n * 暗化处理(Ink 原生强度通道,非 THEME 颜色变体)。\n */\n segments?: Array<{ key: string; width: number; color: string; dimColor?: boolean }>\n /**\n * 触发线标记(对齐 occupancy-bar)——{标记名: {格索引, 颜色}},调用方已完成百分比→\n * 格索引换算(见 RFC-279 §2.3 两层 markers 关系说明)。\n */\n markers?: Record<string, { pos: number; color: string }>\n /** 百分比标签:'none' | 'suffix'(默认,` XX%` 尾随)| 'prefix'(`XX% ` 前置,对齐 InspectorPane 现状)。 */\n labelPosition?: 'none' | 'suffix' | 'prefix'\n /**\n * 百分比标签前缀(诚实标记)。如上下文占用条在 agent 工作中掺入本地实时估算时传 `~`,\n * 表示该读数尚未被权威值结算——沿用 Spinner 的 `~{pct}%` 既有语言,不新造视觉通道。\n */\n labelPrefix?: string\n}\n\nexport function ProgressBar({\n percent,\n width,\n chars,\n color,\n segments,\n markers,\n labelPosition = 'suffix',\n labelPrefix = '',\n}: ProgressBarProps): React.ReactElement {\n const colors = useColors()\n const { filled: filledChar, empty: emptyChar } = resolveProgressChars(chars)\n\n const model = computeProgressBarModel({\n percent,\n width,\n segments: segments?.map((s) => ({ key: s.key, width: s.width })),\n })\n\n const segmentColorByKey = new Map((segments ?? []).map((s) => [s.key, s.color]))\n const segmentDimByKey = new Map((segments ?? []).map((s) => [s.key, s.dimColor ?? false]))\n // markers 参数是 {pos, color} 渲染层格式(已换算),按 pos 建立\"格索引 → {name,color}\"映射,\n // 供渲染时判断某格是否命中标记线。\n const markerByPos = new Map(\n Object.entries(markers ?? {}).map(([name, m]) => [m.pos, { name, color: m.color }]),\n )\n\n const resolveColor = (pct: number): string => {\n if (typeof color === 'function') return color(pct)\n if (typeof color === 'string') return color\n return colors.inactive\n }\n\n const baseColor = resolveColor(model.pct)\n const pctLabel = `${labelPrefix}${model.pct}%`\n\n return (\n <Text>\n {labelPosition === 'prefix' ? <Text color={baseColor} bold>{pctLabel} </Text> : null}\n {model.cells.map((cell, i) => {\n const markerHit = markerByPos.get(i)\n let cellColor = colors.inactive\n if (cell.filled) {\n if (cell.segmentKey) cellColor = segmentColorByKey.get(cell.segmentKey) ?? baseColor\n else cellColor = baseColor\n }\n // 触发线标记优先于分段/单色(对齐 occupancy-bar.ts 现状:\"填充格:触发线位置 →\n // 预警色;其余格 → 整体色阶\",空位始终 inactive 即使处于触发线位置)。\n if (markerHit && cell.filled) cellColor = markerHit.color\n const dim = cell.filled && cell.segmentKey ? (segmentDimByKey.get(cell.segmentKey) ?? false) : false\n return (\n <Text key={i} color={cellColor} dimColor={dim}>\n {cell.filled ? filledChar : emptyChar}\n </Text>\n )\n })}\n {labelPosition === 'suffix' ? <Text>{' '}{pctLabel}</Text> : null}\n </Text>\n )\n}\n","/**\n * ProgressDialog.tsx — install 进度条对话框。\n *\n * Band A hard modal:阻断所有输入,Esc 无响应(不能取消安装)。\n * 四阶段进度条:resolving → detecting → installing → refreshing。\n * 成功 2s 后自动关闭,失败手动 [Close]。\n *\n * RFC-224 D1:active 阶段行内附带 braille spinner 动效(纯视觉,无需真实进度信号)——\n * 消除长耗时单阶段(如 git clone/npm pack)内数字/文案静止造成的\"看起来卡死\"体感。\n * 复用主对话流 Spinner.tsx 的 SPINNER_FRAMES 常量与 reducedMotion() 判断,但不复用整个\n * Spinner 组件本身(它绑定了 tokens/mode/toolBudget/stall 检测等与安装场景无关的语义)。\n */\nimport React, { useEffect, useRef, useState } from 'react'\nimport { Tag } from '../components/Tag'\nimport { Typography } from '../components/Typography'\nimport { ProgressBar } from '../components/progress-bar/ProgressBar'\nimport { Box, Text } from 'ink'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { HintBar } from '../layouts/HintBar'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { KEY } from '../input/keyhints'\nimport { i18next } from '../i18n'\nimport { FIGURES, SPINNER_FRAMES, SPACING, useColors } from '../theme'\nimport { useAnimationFrame } from '../utils/animation-scheduler'\n\n// RFC-306 L1:80ms → 160ms(弹层 spinner 同档降帧)\nconst SPINNER_FRAME_MS = 160\n\n/**\n * RFC-290 D3:阶段图标收编 `FIGURES`——此前自造 ASCII 字形(`*`/`>`/`+`/`!`),\n * 违反设计语言\"渲染层不写裸字形\"(`docs/design/tui-design-language.md` §符号),\n * 且恰好绕过 design-language-lint 的已收录符号黑名单(该盲区由 RFC-290 D6 断言 5 堵上)。\n * 四个语义均复用既有 token,不新增(与 `OnboardingDialog` 步骤指示器同源)。\n */\nconst PHASE_ICONS: Record<string, string> = {\n pending: FIGURES.todoPending,\n active: FIGURES.todoActive,\n done: FIGURES.check,\n error: FIGURES.cross,\n}\n\nconst BAR_WIDTH = 40\n\nexport interface ProgressDialogProps {\n title: string\n phases: string[]\n phaseIndex: number\n phaseStatuses: Array<'pending' | 'active' | 'done' | 'error'>\n percent: number\n detail: string\n subProgress?: { current: number; total: number }\n /** RFC-286 D2:四态。`partial` 语义见 `ProgressUpdate.status` 的 JSDoc。 */\n status: 'running' | 'success' | 'partial' | 'error'\n errorMessage?: string\n /** RFC-290 D2:终态「下一步」建议,由调用方结构化下发;组件零推断(见 ProgressUpdate JSDoc)。 */\n nextStep?: { kind: 'force' | 'reload'; label: string }\n onDismiss: () => void\n}\n\n/**\n * RFC-290 D1:终态折叠阶段摘要——只渲染**已走到**的阶段(done/error/active),\n * 跳过 pending。\n *\n * 终态下未开始的阶段无信息价值,且跳过它们能显著缩短折叠行。标签直接用既有\n * `phases[]` 原文(组件收到的是 CLI 传入的任意字符串,数据模型里不存在\"短名\",\n * 不为纯展示引入第二套标签源)。\n *\n * 导出供单测覆盖,不作为公开 API。\n */\nexport function buildPhaseSummary(\n phases: string[],\n phaseStatuses: Array<'pending' | 'active' | 'done' | 'error'>,\n): Array<{ label: string; status: 'active' | 'done' | 'error' }> {\n const out: Array<{ label: string; status: 'active' | 'done' | 'error' }> = []\n phases.forEach((label, i) => {\n const s = phaseStatuses[i] ?? 'pending'\n if (s === 'pending') return\n out.push({ label, status: s })\n })\n return out\n}\n\nexport function ProgressDialog({\n title,\n phases,\n phaseStatuses,\n percent,\n detail,\n subProgress,\n status,\n errorMessage,\n nextStep,\n onDismiss,\n}: ProgressDialogProps): React.ReactElement {\n const colors = useColors()\n const isRunning = status === 'running'\n const autoCloseRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n // 成功后 2s 自动关闭\n useEffect(() => {\n if (status === 'success') {\n autoCloseRef.current = setTimeout(onDismiss, 2000)\n return () => {\n if (autoCloseRef.current) clearTimeout(autoCloseRef.current)\n }\n }\n return undefined\n }, [status, onDismiss])\n\n // RFC-224 D1:active 阶段行 spinner 帧——只在 running 且未开启 reduced-motion 时跑定时器;\n // reduced-motion 时固定渲染首帧(R5),不循环、不额外起 interval。\n const [spinnerFrame, setSpinnerFrame] = useState(0)\n // RFC-319 M2:纯动效走统一调度器(rm 由调度器统一处理 = 零注册)。\n // status 仍是订阅开关:非 running 时不订阅(与原实现一致)。\n // status 决定是否订阅:非 running 传 null(hook 内跳过订阅)。\n useAnimationFrame(status === 'running' ? SPINNER_FRAME_MS : null, (f) => {\n setSpinnerFrame(f % SPINNER_FRAMES.length)\n })\n\n // 运行中:Esc 不响应(不能取消)\n // 成功/失败:Enter/Esc 关闭\n // RFC-286 R8:`partial` 必须与 success/error 一样可关闭——遗漏会让部分成功的面板\n // 既不自动关闭(下方 success-only 定时器不覆盖它)又无法手动关闭,形成死锁。\n useFallbackInput('progress-dialog', (_input, key) => {\n if ((key.return || key.escape) && status !== 'running') {\n onDismiss()\n return true\n }\n return false\n })\n\n return (\n <DialogFrame\n header={<DialogTitle>{title}</DialogTitle>}\n /* Footer 走 DialogFrame 的 `footer` 插槽(与 AlertDialog 等 21 个弹窗同构)——\n 由壳统一施加 dimColor + marginTop 并左对齐,不在 body 内手写居中 Box。\n - running:无(RFC-279 §4——阶段列表已用图标+色+spinner 表达\"进行中\",\n 再补一句文案冗余且与 active 阶段名矛盾)\n - success:无(自动关闭,给按键提示反而暗示需要操作)\n - partial/error:HintBar(RFC-290 D4;RFC-286 R10 要求 partial 必须有可见\n footer,缺失会让用户不知如何退出) */\n footer={\n status === 'partial' || status === 'error'\n ? <HintBar items={[[`${KEY.enter}/${KEY.esc}`, i18next.t('hint.close')]]} />\n : undefined\n }\n >\n <Box flexDirection='column' paddingX={SPACING.tight}>\n {/* 标题与内容之间的空白行 */}\n <Box height={1} />\n\n {/* 过程态:四阶段完整展开(RFC-290 D1——仅 running 保留,终态不再展示过程细节) */}\n {isRunning ? phases.map((label, i) => {\n const s = phaseStatuses[i] ?? 'pending'\n const isActive = s === 'active'\n const isError = s === 'error'\n const isDone = s === 'done'\n\n let iconColor = colors.inactive\n if (isActive) iconColor = colors.accent\n else if (isError) iconColor = colors.error\n else if (isDone) iconColor = colors.success\n\n return (\n <Box key={i}>\n <Text color={iconColor}>{PHASE_ICONS[s] ?? ' '} </Text>\n <Text color={isActive ? colors.accent : isError ? colors.error : isDone ? colors.success : colors.inactive}>\n {label}\n </Text>\n {isActive ? (\n <>\n <Text color={colors.accent}>{' '}{SPINNER_FRAMES[spinnerFrame]}{' '}</Text>\n <Typography type='secondary'>{detail}</Typography>\n </>\n ) : null}\n </Box>\n )\n }) : null}\n\n {/* 终态(partial/error):阶段折叠为一行摘要——保留\"卡在哪一步\"的非文本定位锚点\n (色弱用户可依赖符号而非颜色),但不再占用四行展示已无价值的过程细节。\n success 态完全不渲染本区(成功路径用户不关心过程)。 */}\n {!isRunning && status !== 'success' ? (\n <Box>\n {buildPhaseSummary(phases, phaseStatuses).map((p, i) => (\n <React.Fragment key={i}>\n {i > 0 ? <Typography type='secondary'>{' · '}</Typography> : null}\n <Text color={p.status === 'error' ? colors.error : p.status === 'done' ? colors.success : colors.accent}>\n {PHASE_ICONS[p.status]}{' '}\n </Text>\n <Typography type='secondary'>{p.label}</Typography>\n </React.Fragment>\n ))}\n </Box>\n ) : null}\n\n {/* 子进度(多 skill 时) */}\n {subProgress && status === 'running' ? (\n <Box marginTop={SPACING.tight}>\n <Tag>{subProgress.current}/{subProgress.total}</Tag>\n </Box>\n ) : null}\n\n {/* 进度条(RFC-279:收编为统一 ProgressBar 组件,chars='ascii')。\n RFC-290 G2:**仅 running 态渲染**——终态显示满格进度条是纯语义污染\n (失败却 100%,自相矛盾),且把用户视线从真正重要的结果上引开。 */}\n {/* running 态无 footer(RFC-279 §4),进度条是最后一个元素——补 marginBottom\n 让它与下边框留一行呼吸位,避免紧贴框线。终态各段自带 marginTop,不受影响。 */}\n {isRunning ? (\n <Box marginTop={SPACING.tight} marginBottom={SPACING.tight}>\n <ProgressBar percent={percent} width={BAR_WIDTH} chars='ascii' color={colors.accent} />\n </Box>\n ) : null}\n\n {/* 终态主体三段式(RFC-290 D2):结果摘要行 → 详情 → 下一步建议。\n 摘要行是最高优先级信息,排在最前;详情承载真实原因;建议只在调用方\n 结构化下发时渲染(组件零推断,见 nextStep 契约)。 */}\n {/* success 态上方无阶段摘要,标题下的空行已够——再加 marginTop 会形成双空行。 */}\n {!isRunning ? (\n <Box marginTop={status === 'success' ? SPACING.none : SPACING.tight} flexDirection='column'>\n <Box>\n <Text\n bold\n color={status === 'success' ? colors.success : status === 'partial' ? colors.warning : colors.error}\n >\n {status === 'success' ? FIGURES.check : status === 'partial' ? FIGURES.warn : FIGURES.cross}\n {' '}\n {status === 'success'\n ? i18next.t('install.done')\n : status === 'partial'\n ? i18next.t('install.summaryPartial')\n : i18next.t('install.summaryFailed')}\n </Text>\n </Box>\n\n {/* 详情:error 与 partial 都必须渲染(RFC-286 D2/D6/R5——partial 承载\n \"N installed, M failed\"与\"已安装但刷新失败\"两类关键信息,\n toast 单槽通道不可依赖)。 */}\n {status !== 'success' && errorMessage ? (\n <Box marginTop={SPACING.tight}>\n {/* 用 Typography 而非手写 <Text color={colors.inactive}>——RFC-269 M2 已把\n 次级文字收编进组件层,design-language-lint 的棘轮门钉死 inactive 归零。 */}\n <Typography type='secondary' wrap='wrap'>{errorMessage}</Typography>\n </Box>\n ) : null}\n\n {nextStep ? (\n <Box marginTop={SPACING.tight}>\n <Typography type='secondary'>{nextStep.label}</Typography>\n </Box>\n ) : null}\n </Box>\n ) : null}\n\n </Box>\n </DialogFrame>\n )\n}","/**\n * questions-dialog-logic.ts — QuestionsDialog 的纯状态机(AskUserQuestion 风格多问题复核).\n *\n * 模型对齐 CC AskUserQuestion:若干问题以「标签页」排列,末尾一个 Submit 标签页;\n * Tab/Shift+Tab 循环切标签页(末页 Tab 回第一页,首页 Shift+Tab 回 Submit 页;\n * ←/→ 保留给补充说明输入框的文本光标移动,不兼做页面导航),↑/↓ 移选项,\n * Space/Enter 选定(单选自动跳下一页,多选切换),Submit 页 Enter 提交,Esc 取消。\n * 与 .tsx 分离便于单测。\n *\n * RFC-174 M1:↑/k、↓/j 的判断改为查 control-registry(matchControlBinding),\n * 动作语义不变,仅键位判断来源从函数体内硬编码改为中心化 registry。\n */\nimport { matchControlBinding } from '../keybindings'\nexport interface QuestionOption {\n label: string\n description?: string\n}\nexport interface Question {\n /** 短标签(标签页/答案键)。 */\n header: string\n /** 完整问题文本。 */\n question: string\n /** 是否多选(多选用 Space 切换;单选 Enter/Space 选定并自动前进)。 */\n multiSelect: boolean\n options: QuestionOption[]\n /** grill_me allowFreeform:选项页底部渲染补充文本输入。 */\n freeformInput?: boolean\n /** 补充文本输入框占位文字。 */\n freeformPlaceholder?: string\n}\n\n/** 是否有任一问题启用了补充文本输入(决定 +1 标签页)。 */\nexport function hasFreeformInput(questions: Question[]): boolean {\n return questions.some((q) => q.freeformInput === true)\n}\n\nexport interface QuestionsState {\n /** 当前标签页:0..questions.length(最后一个 = Submit 页)。 */\n tab: number\n /** 当前问题页内的选项游标。 */\n cursor: number\n /** 每问已选选项下标集合(按 question 顺序)。 */\n selected: number[][]\n /** grill_me allowFreeform:用户输入的补充文字。 */\n freeformText: string\n /** 补充文字输入框的光标偏移(字符索引)——供 Cursor/TextInput 渲染真实光标。 */\n freeformOffset: number\n}\n\nexport function initQuestionsState(questions: Question[]): QuestionsState {\n return {\n tab: 0,\n cursor: 0,\n selected: questions.map(() => []),\n freeformText: '',\n freeformOffset: 0,\n }\n}\n\nexport type QuestionsAction = {\n type: 'tabPrev' | 'tabNext' | 'cursorPrev' | 'cursorNext' | 'choose' | 'cancel'\n} | null\n\n/** 把一次按键映射为动作。Tab 下页、Shift+Tab 上页、↑/k、↓/j、Space/Enter 选定、Esc 取消。\n * 问题切换只走 Tab/Shift+Tab——←/→ 保留给补充说明输入框的文本光标移动,不能兼做页面导航\n * (否则一旦焦点在输入框内,左右方向键就完全无法用于移动光标)。\n * onFreeformTab: 不映射 Space 为 choose(留给组件内文字输入),Enter 仍映射 choose(→ 跳 Submit)。 */\nexport function questionsDialogKey(\n input: string,\n key: {\n upArrow?: boolean\n downArrow?: boolean\n return?: boolean\n escape?: boolean\n tab?: boolean\n shift?: boolean\n },\n onFreeformTab = false,\n): QuestionsAction {\n if (key.escape) return { type: 'cancel' }\n if (key.tab) return { type: key.shift ? 'tabPrev' : 'tabNext' }\n if (matchControlBinding('list-nav-up', input, key)) return { type: 'cursorPrev' }\n if (matchControlBinding('list-nav-down', input, key)) return { type: 'cursorNext' }\n if (!onFreeformTab && (input === ' ' || key.return)) return { type: 'choose' }\n return null\n}\n\nexport interface ReduceResult {\n state: QuestionsState\n /** 终态:用户提交或取消(否则 undefined)。 */\n done?: 'submit' | 'cancel'\n}\n\n/** 纯 reducer:对当前 state 施加 action,返回新 state(及可能的终态)。 */\nexport function reduceQuestions(\n state: QuestionsState,\n action: QuestionsAction,\n questions: Question[],\n): ReduceResult {\n const hasFreeform = hasFreeformInput(questions)\n const submitTab = questions.length + (hasFreeform ? 1 : 0)\n const freeformTab = hasFreeform ? questions.length : -1\n switch (action?.type) {\n case 'cancel':\n return { state, done: 'cancel' }\n // Tab/Shift+Tab 循环切换(末页 Submit 再按 Tab 回到第一个问题,首页再按 Shift+Tab 回到\n // Submit 页)——与列表类导航组件(SelectDialog 等)的循环约定一致,避免用户在最后一页\n // 反复按 Tab 却毫无反应的死头体验。总页数 = submitTab + 1(含 Submit 页本身)。\n case 'tabPrev':\n return { state: { ...state, tab: (state.tab - 1 + submitTab + 1) % (submitTab + 1), cursor: 0 } }\n case 'tabNext':\n return { state: { ...state, tab: (state.tab + 1) % (submitTab + 1), cursor: 0 } }\n case 'cursorPrev': {\n if (state.tab === submitTab || state.tab === freeformTab) return { state }\n return { state: { ...state, cursor: Math.max(0, state.cursor - 1) } }\n }\n case 'cursorNext': {\n if (state.tab === submitTab || state.tab === freeformTab) return { state }\n const n = questions[state.tab]!.options.length\n return { state: { ...state, cursor: Math.min(n - 1, state.cursor + 1) } }\n }\n case 'choose': {\n if (state.tab === submitTab) return { state, done: 'submit' }\n if (state.tab === freeformTab) {\n return { state: { ...state, tab: submitTab } }\n }\n const q = questions[state.tab]!\n const cur = state.selected[state.tab] ?? []\n const nextSel = q.multiSelect\n ? cur.includes(state.cursor)\n ? cur.filter((i) => i !== state.cursor)\n : [...cur, state.cursor]\n : [state.cursor]\n const selected = state.selected.map((s, i) => (i === state.tab ? nextSel : s))\n const tab = q.multiSelect ? state.tab : Math.min(submitTab, state.tab + 1)\n return { state: { ...state, selected, tab, cursor: 0 } }\n }\n default:\n return { state }\n }\n}\n\n/** 从 state 抽出答案:{ [header]: 选中项 label 数组 }。 */\nexport function questionsAnswers(\n state: QuestionsState,\n questions: Question[],\n): Record<string, string[]> {\n const out: Record<string, string[]> = {}\n questions.forEach((q, i) => {\n out[q.header] = (state.selected[i] ?? [])\n .map((idx) => q.options[idx]?.label)\n .filter((l): l is string => typeof l === 'string')\n })\n return out\n}\n\n/** 是否每问至少一个答案(Submit 页提示用)。 */\nexport function allAnswered(state: QuestionsState, questions: Question[]): boolean {\n return questions.every((_, i) => (state.selected[i]?.length ?? 0) > 0)\n}\n\n/** 提取用户补充文字。 */\nexport function questionsFreeformText(state: QuestionsState): string {\n return state.freeformText\n}\n","/**\n * QuestionsDialog.tsx — AskUserQuestion 风格多问题复核对话框(渲染端).\n *\n * 标签页式:[问题1] [问题2] … [Submit],Tab/Shift+Tab 切页、↑/↓ 移选项、Space/Enter 选定、Esc 取消。\n * 单选选定即跳下一页;多选 Space 切换、Tab 到 Submit 页 Enter 提交。状态机在 questions-dialog-logic.ts。\n * 问题切换只用 Tab/Shift+Tab(不用 ←/→)——补充说明页有文本输入框,←/→ 必须专职做光标移动,\n * 不能像纯选项页那样兼职做页面导航(否则一进输入框方向键就失灵)。\n *\n * M20-05 grill_me allowFreeform:选项页底部渲染补充文本输入行;自由文字 Tab/Enter → Submit 提交时\n * 一并回传。补充文本输入复用 InputDialog 同款 Cursor + handleEditingKeyExtended + TextInput 基础\n * 设施(真实光标可视、左右移动、emacs 编辑键位),不再是仅支持尾部追加/删除的手搓实现。\n * 补充说明恒单行(同 InputDialog):Enter 等同 Tab,直接前进到下一页,与其余问题页\n * 「Enter 即前进」的语义对齐,不支持换行。\n *\n * 标签栏 UI 设计:\n * - 每个标签裸文本呈现(无 [ ] 框),状态仅由颜色区分(active=蓝+bold / answered=绿 / 未答=灰)\n * - 标签之间用 dim · 分隔\n * - header 超过 22 字截断加 …\n * - 正文问题文本与 title 相同时不重复渲染(单问题场景 title 即问题)\n */\nimport React, { useRef, useState } from 'react'\nimport { Typography } from '../components/Typography'\nimport { Box, Text } from 'ink'\nimport {\n initQuestionsState,\n questionsDialogKey,\n reduceQuestions,\n questionsAnswers,\n allAnswered,\n hasFreeformInput,\n type Question,\n type QuestionsState } from './questions-dialog-logic'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { HintBar } from '../layouts/HintBar'\nimport { Button } from '../components/Button'\nimport { FIGURES, SPACING, useColors } from '../theme'\nimport { i18next } from '../i18n'\nimport { KEY } from '../input/keyhints'\nimport { Cursor } from '../input/cursor'\nimport { handleEditingKeyExtended, sanitizeTextChunk } from '../input/text-input-logic'\nimport type { EditKeyEvent } from '../input/text-input-logic'\nimport { TextInput } from '../input/TextInput'\nimport { truncateByDisplayWidth } from '../term/truncate-display'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { useTabable } from '../router/Tabable'\n\nexport interface QuestionsDialogProps {\n title?: string\n questions: Question[]\n /** 提交回调(答案:{ [header]: 选中项 label 数组 } + freeformText 补充文字)。 */\n onSubmit: (answers: Record<string, string[]>, freeformText?: string) => void\n /** 取消回调(Esc)。 */\n onCancel?: () => void\n /** 补充文本输入框可用列宽(缺省 60,对齐 InputDialog 默认值;调用方按终端宽度动态传入)。 */\n columns?: number\n}\n\n/** Tab header 最大长度(超出截断 + …)。 */\nconst TAB_MAX = 22\n\nfunction tabLabel(header: string): string {\n return truncateByDisplayWidth(header, TAB_MAX + 1)\n}\n\nexport function QuestionsDialog({\n title,\n questions,\n onSubmit,\n onCancel,\n columns = 60 }: QuestionsDialogProps): React.ReactElement {\n const colors = useColors()\n const hasFreeform = hasFreeformInput(questions)\n const stateRef = useRef<QuestionsState | null>(null)\n stateRef.current ??= initQuestionsState(questions)\n const [, force] = useState(0)\n\n const freeformTab = hasFreeform ? questions.length : -1\n const submitTab = questions.length + (hasFreeform ? 1 : 0)\n\n // RFC-258:注册 onEscape 让 Router Esc handler 先走 cancel resolver(而非裸 goBack),\n // 避免 Esc 关闭弹窗后 resolver 永不调用导致 agent 阻塞。\n //\n // 翻页(Tab/Shift+Tab 在「问题页 ⇄ 补充说明页 ⇄ Submit 页」间移动)——单一真源,\n // 供 onTab 拦截器与组件键盘处理复用。返回 true = 已处理(吞掉本次按键)。\n const handlePageNav = (shift: boolean): boolean => {\n const s = stateRef.current!\n const navAction = questionsDialogKey('', { tab: true, shift } as never, s.tab === freeformTab)\n if (!navAction) return false\n const { state, done } = reduceQuestions(s, navAction, questions)\n stateRef.current = state\n if (done === 'cancel') {\n onCancel?.()\n return true\n }\n if (done === 'submit') {\n onSubmit(questionsAnswers(state, questions), state.freeformText || undefined)\n return true\n }\n force((n) => n + 1)\n return true\n }\n\n // RFC-258:注册 onEscape 让 Router Esc handler 先走 cancel resolver(而非裸 goBack)。\n //\n // RFC-265 onTab 拦截(终局修复 QuestionsDialog \"到不了 Submit 页/无法提交\"):\n // Router 的 Tab 处理优先级(PRIORITY_ROUTER=50)高于组件 useFallbackInput\n // (PRIORITY_FALLBACK=10),且 dispatch 里 handler 返回 true = stopPropagation——若不\n // 声明 onTab,Router 把 Tab 当\"焦点循环\"消费掉(本路由仅 1 个 Tabable,循环回自己=no-op\n // 但 `return true` 已停止传播),组件的翻页处理**永远收不到 Tab** → 用户卡在问题页、到不了\n // Submit 页、无法提交(用户报告的\"无法退出\")。修复:把翻页逻辑放进 onTab 本身(Router 会\n // 调它并据返回值决定是否 return true),而非低优先级的 fallback——这是 RFC-265 拦截器的\n // 正确用法:Tabable 自己声明如何处理 Tab,而非依赖冒泡到组件 fallback。\n useTabable({\n onEscape: () => { onCancel?.(); return true },\n onTab: (shift: boolean) => handlePageNav(shift),\n })\n\n useFallbackInput('questions-dialog', (input, key) => {\n const s = stateRef.current!\n const onFreeform = s.tab === freeformTab\n\n if (onFreeform) {\n // 空文本时 Esc 直接取消(对齐 InputDialog:handleEditingKeyExtended 对空文本的\n // escape 返回 null,必须在调用它之前拦截)。\n if (key.escape && s.freeformText.length === 0) {\n onCancel?.()\n return\n }\n // Tab/Shift+Tab 翻页:**有 Router 时**由 useTabable.onTab 拦截器处理(见 handlePageNav,\n // Router 优先级高、拦截后 stopPropagation,Tab 到不了这里);**无 Router 时**(如独立\n // 渲染/单元测试)useFallbackInput 降级裸 useInput、无 onTab 派发,故此处兜底同款翻页。\n // 两条路径复用 handlePageNav 单一真源,互斥不会双触发。\n if (key.tab) {\n handlePageNav(key.shift ?? false)\n return\n }\n // ←→:文本框内光标移动(对齐 InputDialog)——问题切换已完全交给 Tab/Shift+Tab,\n // 方向键在补充说明输入框里不再兼做页面导航。\n if (key.leftArrow) {\n const c = Cursor.fromText(s.freeformText, columns, s.freeformOffset).left()\n stateRef.current = { ...s, freeformText: c.text, freeformOffset: c.offset }\n force((n) => n + 1)\n return\n }\n if (key.rightArrow) {\n const c = Cursor.fromText(s.freeformText, columns, s.freeformOffset).right()\n stateRef.current = { ...s, freeformText: c.text, freeformOffset: c.offset }\n force((n) => n + 1)\n return\n }\n\n // 多字符批量输入(IME 一次 commit / bracketed paste / 测试环境 stdin.write 一次写入\n // 多字符)——handleEditingKeyExtended 的 isPrintable 只认单字符,批量必须单独处理\n // (对齐 InputDialog 同款分支,否则整段粘贴会被静默丢弃)。\n if (\n input.length > 1 &&\n !key.ctrl && !key.meta && !key.return && !key.escape &&\n !key.backspace && !key.delete && !key.leftArrow && !key.rightArrow\n ) {\n const chunk = sanitizeTextChunk(input)\n if (chunk) {\n const text = s.freeformText\n const offset = s.freeformOffset\n stateRef.current = {\n ...s,\n freeformText: text.slice(0, offset) + chunk + text.slice(offset),\n freeformOffset: offset + chunk.length }\n force((n) => n + 1)\n }\n return\n }\n\n // Enter 等同 Tab:前进到下一页(Submit 页)——与其余问题页「Enter 即前进」的语义对齐。\n // 补充说明恒单行(同 InputDialog),不支持换行,故 Enter 不区分 Shift。\n if (key.return) {\n const navAction = questionsDialogKey('', { tab: true }, true)!\n const { state, done } = reduceQuestions(s, navAction, questions)\n stateRef.current = state\n if (done === 'submit') {\n onSubmit(questionsAnswers(state, questions), state.freeformText || undefined)\n return\n }\n force((n) => n + 1)\n return\n }\n\n const editKey: EditKeyEvent = {\n name: key.escape\n ? 'escape'\n : key.backspace\n ? 'backspace'\n : key.delete\n ? 'delete'\n : input || 'unknown',\n ctrl: key.ctrl ?? false,\n // 补充说明框恒单行,不插入换行,shift 一律不传(对齐 InputDialog)。\n shift: false,\n alt: key.meta ?? false,\n meta: key.meta ?? false }\n\n const cursor = Cursor.fromText(s.freeformText, columns, s.freeformOffset)\n const result = handleEditingKeyExtended(cursor, editKey)\n if (!result) return\n\n stateRef.current = {\n ...s,\n freeformText: result.cursor.text,\n freeformOffset: result.cursor.offset }\n force((n) => n + 1)\n return\n }\n\n const action = questionsDialogKey(input, key as never)\n if (!action) return\n const { state, done } = reduceQuestions(s, action, questions)\n stateRef.current = state\n if (done === 'cancel') {\n onCancel?.()\n return\n }\n if (done === 'submit') {\n onSubmit(questionsAnswers(state, questions), state.freeformText || undefined)\n return\n }\n force((n) => n + 1)\n })\n\n const s = stateRef.current!\n const onSubmitTab = s.tab === submitTab\n\n const footer = s.tab === freeformTab\n ? (\n <HintBar\n items={[\n [KEY.enter, i18next.t('freeform.advance')],\n [KEY.esc, i18next.t('hint.cancel')],\n ]}\n />\n )\n : (\n <HintBar\n items={[\n [`${KEY.up}${KEY.down}`, i18next.t('hint.option')],\n [KEY.tab, i18next.t('hint.question')],\n onSubmitTab ? [KEY.enter, i18next.t('hint.submit')] : [`space/${KEY.enter}`, i18next.t('hint.choose')],\n [KEY.esc, i18next.t('hint.cancel')],\n ]}\n />\n )\n\n return (\n <DialogFrame\n header={title ? <DialogTitle>{title}</DialogTitle> : undefined}\n footer={footer}\n >\n {/* Tab 栏:裸标签 · 分隔,状态仅颜色区分。与 header 隔一行空白。 */}\n <Box flexDirection='row' marginTop={SPACING.tight}>\n {questions.map((q, i) => {\n const answered = (s.selected[i]?.length ?? 0) > 0\n const active = s.tab === i\n const color = active ? colors.selection : answered ? colors.success : colors.inactive\n return (\n <React.Fragment key={i}>\n {i > 0 && (\n <Typography type='secondary' dim> · </Typography>\n )}\n <Text color={color} bold={active}>\n {tabLabel(q.header)}\n </Text>\n </React.Fragment>\n )\n })}\n {hasFreeform && (\n <>\n <Typography type='secondary' dim> · </Typography>\n <Text\n color={s.tab === freeformTab ? colors.selection : s.freeformText ? colors.success : colors.inactive}\n bold={s.tab === freeformTab}\n >\n {i18next.t('freeform.tab')}\n </Text>\n </>\n )}\n <Typography type='secondary' dim> · </Typography>\n <Text color={onSubmitTab ? colors.selection : colors.inactive} bold={onSubmitTab}>\n {i18next.t('prompt.submit')}\n </Text>\n </Box>\n\n {/* 正文区 */}\n {onSubmitTab ? (\n <Box flexDirection='column' marginTop={SPACING.tight}>\n <Text color={allAnswered(s, questions) ? colors.success : colors.warning}>\n {allAnswered(s, questions) ? i18next.t('questions.readyToSubmit') : i18next.t('questions.someUnanswered')}\n </Text>\n <Box marginTop={SPACING.tight}>\n <Button variant='solid' brackets color='accent' label={i18next.t('prompt.submit')} focused={true} />\n </Box>\n </Box>\n ) : s.tab === freeformTab ? (\n <Box flexDirection='column' marginTop={SPACING.tight}>\n <Text color={colors.secondary}>{i18next.t('freeform.label')}</Text>\n <Box marginTop={SPACING.tight}>\n <TextInput\n value={s.freeformText}\n offset={s.freeformOffset}\n columns={columns}\n mode='dialog'\n placeholder={i18next.t('freeform.placeholder')}\n focused\n // RFC-317 M2:物理光标(IME 候选框锚定)——模态对话框路由独占。\n physicalCursor\n />\n </Box>\n </Box>\n ) : (\n <Box flexDirection='column' marginTop={SPACING.tight}>\n {/* 单问题场景 title 常即问题本身——相同则不重复渲染正文 */}\n {questions[s.tab]!.question !== title && (\n <Text color={colors.text}>{questions[s.tab]!.question}</Text>\n )}\n <Box flexDirection='column' marginTop={SPACING.tight}>\n {questions[s.tab]!.options.map((o, i) => {\n const isCursor = i === s.cursor\n const isChosen = (s.selected[s.tab] ?? []).includes(i)\n const mark = isChosen ? FIGURES.bullet : FIGURES.todoPending\n return (\n <Box key={i} flexDirection='column'>\n <Box flexDirection='row'>\n <Text color={isCursor ? colors.selection : colors.inactive} bold={isCursor}>\n {isCursor ? `${FIGURES.pointer} ` : ' '}\n </Text>\n <Text color={isCursor ? colors.selection : isChosen ? colors.success : colors.inactive} bold={isCursor}>\n {mark}\n </Text>\n <Text> </Text>\n <Text color={isCursor ? colors.selection : isChosen ? colors.success : colors.inactive} bold={isCursor}>\n {o.label}\n </Text>\n </Box>\n {o.description && (\n <Box marginLeft={SPACING.loose}>\n <Typography type='secondary' dim>\n {o.description}\n </Typography>\n </Box>\n )}\n </Box>\n )\n })}\n </Box>\n </Box>\n )}\n </DialogFrame>\n )\n}\n","/**\n * WizardDialog.tsx — 四步线性设置向导(Provider → Login → Model → Start)。\n *\n * 单一持久模态(非 select 弹窗栈),内部 step 状态驱动步骤切换,过渡无闪烁。\n * 纵向布局:顶部紧凑进度条 → 步骤标题+说明 → 分隔线 → 选项区 → 底部快捷键栏。\n * Esc 回退上一步(第一步时关闭向导)。错误原地展示。\n */\nimport React, { useState, useCallback, useEffect, useRef } from 'react'\nimport { Typography } from '../components/Typography'\nimport { Box, Text } from 'ink'\nimport { SPACING, FIGURES, SPINNER_FRAMES, useColors } from '../theme'\nimport type { ThemeColors } from '../theme'\nimport { i18next } from '../i18n'\nimport { DialogFrame } from '../layouts/DialogFrame'\nimport { Select } from './SelectPrimitive'\nimport { DialogTitle } from '../layouts/DialogTitle'\nimport { HintBar } from '../layouts/HintBar'\nimport { KEY } from '../input/keyhints'\nimport { TextInput } from '../input/TextInput'\nimport type { WizardRequest, OAuthHandle, OAuthFailureReason } from '../bootstrap/types'\nimport { Menu } from '../components/primitives/Menu'\nimport { useFallbackInput } from '../router/FocusBus'\nimport { reducedMotion } from '../utils/motion'\nimport { useAnimationFrame } from '../utils/animation-scheduler'\n\nconst TOTAL_STEPS = 4\n/** RFC-278:Step 0 provider 安装阶段图标显示——匹配 `runPluginInstall` onProgress 文案顺序。 */\nconst INSTALL_PHASE_LABELS = ['Resolving', 'Installing', 'Authorizing', 'Reloading'] as const\n/** RFC-290 D3:图标收编 `FIGURES`,与 `ProgressDialog` 阶段图标同源(此前各自自造裸字形)。 */\nconst INSTALL_PHASE_ICONS: Record<'pending' | 'active' | 'done', string> = {\n pending: FIGURES.todoPending,\n active: FIGURES.todoActive,\n done: FIGURES.check,\n}\n/** RFC-278:从 `onProgress` 文案(如 \"Resolving package…\")推导当前所处阶段索引,未匹配时回退 -1。 */\nfunction resolveInstallPhaseIndex(phase: string | null): number {\n if (!phase) return -1\n return INSTALL_PHASE_LABELS.findIndex((label) => phase.includes(label))\n}\n/** RFC-278:成功后停留展示终态的时长(ms)——足够用户看清结果,不引入明显等待感。 */\nconst INSTALL_SUCCESS_HOLD_MS = 900\n\ninterface WizardDialogProps {\n request: WizardRequest\n}\n\n/** 单行紧凑进度条 */\nfunction compactProgress(step: number, completed: Set<number>, names: readonly string[], colors: ThemeColors): React.ReactElement {\n return (\n <Box flexDirection='row' marginBottom={SPACING.tight}>\n {names.map((name, i) => {\n const isCompleted = completed.has(i)\n const isCurrent = i === step\n const status = isCompleted ? FIGURES.check : isCurrent ? FIGURES.todoDone : FIGURES.todoPending\n const color = isCompleted ? colors.inactive : isCurrent ? colors.text : colors.inactive\n return (\n <React.Fragment key={i}>\n {i > 0 && <Typography type='secondary'> · </Typography>}\n <Text color={color} bold={isCurrent}>{status} {name}</Text>\n </React.Fragment>\n )\n })}\n <Box marginLeft={SPACING.section}>\n <Typography type='secondary'>Step </Typography>\n <Text color={colors.text} bold>{step + 1}</Text>\n <Typography type='secondary'>/{TOTAL_STEPS}</Typography>\n </Box>\n </Box>\n )\n}\n\nfunction Separator(): React.ReactElement {\n return (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary' dim>{FIGURES.hr.repeat(60)}</Typography>\n </Box>\n )\n}\n\nexport function WizardDialog({ request }: WizardDialogProps): React.ReactElement {\n const colors = useColors()\n const [welcomed, setWelcomed] = useState(false)\n const [dontShowAgain, setDontShowAgain] = useState(false)\n const [step, setStep] = useState(() => Math.min(request.initialCurrent, TOTAL_STEPS - 1))\n const [completed, setCompleted] = useState<Set<number>>(() => new Set(request.initialCompleted))\n const [error, setError] = useState<string | null>(null)\n const [installing, setInstalling] = useState<string | null>(null)\n const [installPhase, setInstallPhase] = useState<string | null>(null)\n // RFC-278:安装成功终态(value 非空时短暂展示 \"✓ Installed\",再延迟 advance())。\n const [installSucceeded, setInstallSucceeded] = useState<string | null>(null)\n // RFC-326 §6.2:安装失败的 provider value(非 null = 处于可恢复的失败态)。\n const [installFailed, setInstallFailed] = useState<string | null>(null)\n // RFC-326 §6.3:OAuth 失败分类(非 null = 处于可恢复的授权失败态)。\n const [authFailure, setAuthFailure] = useState<OAuthFailureReason | null>(null)\n // RFC-326:安装成功后实际被授予的高危能力(终态披露用;空=无需披露)。\n const [installedCapabilities, setInstalledCapabilities] = useState<readonly string[]>([])\n // RFC-326 M2-05:装上但能力未授权(providers 被 fail-closed 跳过 → 零模型)。\n const [installPendingCaps, setInstallPendingCaps] = useState(false)\n const installTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n // 内联 provider 授权状态(RFC-274 M2:loginMode→authMode,账号登录/provider 授权语义切分)\n const [authMode, setAuthMode] = useState<'select' | 'oauth-wait' | 'key-input' | 'logging-in'>('select')\n const [oauthUrl, setOauthUrl] = useState<string | null>(null)\n const [oauthCode, setOauthCode] = useState<string | null>(null)\n const [oauthInstructions, setOauthInstructions] = useState<string | null>(null)\n const [apiKey, setApiKeyState] = useState('')\n const apiKeyRef = useRef('')\n // Step 0 选择的 provider id(供 providerAuthOptions 动态计算)\n const [selectedProvider, setSelectedProvider] = useState<string>(request.authProviderName ?? '')\n const setApiKey = useCallback((v: string) => { apiKeyRef.current = v; setApiKeyState(v) }, [])\n const oauthPollRef = useRef<OAuthHandle | null>(null)\n const cancelledRef = useRef(false)\n\n const exitWizard = useCallback(() => {\n cancelledRef.current = true\n oauthPollRef.current?.cancel()\n // RFC-278:Esc 中途退出时清理待触发的安装成功→advance 定时器,防止已卸载/已退出\n // 向导的组件在 timer 触发时仍调用 setInstallSucceeded/advance。\n if (installTimerRef.current) {\n clearTimeout(installTimerRef.current)\n installTimerRef.current = null\n }\n request.onDismiss()\n }, [request])\n\n // Esc → 回退上一步(第一步时关闭)\n const goBack = useCallback(() => {\n // 内联 provider 授权进行中:Esc 取消授权回到方式选择\n if (authMode !== 'select' && step === 1) {\n oauthPollRef.current?.cancel()\n setAuthMode('select')\n setOauthUrl(null)\n setOauthCode(null)\n setOauthInstructions(null)\n setApiKey('')\n apiKeyRef.current = ''\n setError(null)\n return\n }\n if (step === 0) {\n exitWizard()\n } else {\n setCompleted((prev) => {\n const next = new Set(prev)\n next.delete(step - 1)\n return next\n })\n setStep((s) => s - 1)\n setError(null)\n setAuthMode('select')\n setOauthUrl(null)\n setOauthCode(null)\n setOauthInstructions(null)\n setApiKey('')\n // RFC-278 独立审核发现1:对齐 advance() 的清理语义——防御性清理,防止未来新增的\n // 回退路径(如 Step2→Step1→Step0)携带残留的安装成功终态渲染出陈旧 UI。当前代码\n // 路径下不可达(Esc 在 installSucceeded 展示期走 exitWizard 而非 goBack),但清理\n // 成本极低,优先对齐防御纵深。\n setInstallSucceeded(null)\n setInstalledCapabilities([])\n setInstallPendingCaps(false)\n setInstallFailed(null)\n if (installTimerRef.current) {\n clearTimeout(installTimerRef.current)\n installTimerRef.current = null\n }\n }\n }, [step, exitWizard, authMode])\n\n useEffect(() => {\n return () => {\n oauthPollRef.current?.cancel()\n // RFC-278:unmount 兜底——exitWizard 未必是唯一的卸载路径(如宿主直接摘除组件),\n // 双保险清理避免已卸载组件上触发状态更新。\n if (installTimerRef.current) clearTimeout(installTimerRef.current)\n }\n }, [])\n\n const advance = useCallback(() => {\n setCompleted((prev) => new Set([...prev, step]))\n setError(null)\n setInstalling(null)\n setInstallSucceeded(null)\n setInstalledCapabilities([])\n setInstallPendingCaps(false)\n setInstallFailed(null)\n setAuthMode('select')\n setAuthFailure(null)\n setOauthUrl(null)\n setOauthCode(null)\n setOauthInstructions(null)\n setApiKey('')\n apiKeyRef.current = ''\n // OAuth 成功后立即释放 handle(防 unmount cleanup 时触发已完成流的 cancel → unhandled rejection)\n oauthPollRef.current = null\n if (step >= TOTAL_STEPS - 1) {\n exitWizard()\n request.onComplete()\n } else {\n setStep((s) => s + 1)\n }\n }, [step, exitWizard, request])\n\n // retryInstall 声明在 handleInstall 之后但需调用它——用 ref 解耦声明顺序,\n // 避免把 handleInstall 提前或让两者互相进入依赖数组造成重建循环。\n const handleInstallRef = useRef<((v: string) => Promise<void>) | null>(null)\n\n const handleInstall = useCallback(async (value: string) => {\n setInstalling(value)\n setInstallPhase(null)\n setError(null)\n const result = await request.onInstallProvider(value, (phase) => {\n if (cancelledRef.current) return\n setInstallPhase(phase)\n })\n if (cancelledRef.current) return\n setInstalling(null)\n setInstallPhase(null)\n // RFC-274 M2:优先用 CLI 层解析出的裸 provider id(result.providerId),\n // 而非继续用 alias(value)——OAuthRegistry/authStore 都按裸 id 索引,\n // 传 alias 会让后续 providerAuthOptions(selectedProvider) 查不到任何注册项。\n if (result.ok) {\n setSelectedProvider(result.providerId ?? value)\n setInstalledCapabilities(result.capabilities ?? [])\n setInstallPendingCaps(result.pendingCapabilities === true)\n // RFC-278:成功终态短暂展示(\"✓ Installed\")后再 advance(),避免用户来不及看清\n // 安装已成功就被跳到下一步;Esc/unmount 有对应 timer 清理(exitWizard + cleanup effect)。\n setInstallSucceeded(value)\n installTimerRef.current = setTimeout(() => {\n installTimerRef.current = null\n advance()\n }, INSTALL_SUCCESS_HOLD_MS)\n } else {\n // RFC-326 §6.2:安装失败必须给出下一步,而非只把错误串糊在屏幕上。\n // 记住失败的 provider value —— Retry 直接复投同一个,Choose another 清掉\n // 失败态回到列表。此前用户看到一行红字后无路可走:列表还在但没有任何提示\n // 说\"可以重试\"或\"换一个\",只能猜 Esc。\n setInstallFailed(value)\n setError(result.error)\n }\n }, [request, advance])\n\n handleInstallRef.current = handleInstall\n\n /** §6.2 恢复动作:重试同一个 provider。 */\n const retryInstall = useCallback(() => {\n const target = installFailed\n if (!target) return\n setInstallFailed(null)\n setError(null)\n void handleInstallRef.current?.(target)\n }, [installFailed])\n\n /** §6.2 恢复动作:放弃当前 provider,回到列表另选。 */\n const chooseAnotherProvider = useCallback(() => {\n setInstallFailed(null)\n setError(null)\n }, [])\n\n // handleRetryAuth 声明在 handleOAuthStart 之前但需调用它——同 handleInstallRef 的\n // 处理方式,用 ref 解耦声明顺序,避免互相进依赖数组造成重建循环。\n const handleOAuthStartRef = useRef<(() => Promise<void>) | null>(null)\n\n // 内联 provider OAuth 授权\n const handleOAuthStart = useCallback(async () => {\n if (!request.onProviderOAuthStart) return\n setError(null)\n setAuthMode('oauth-wait')\n const handle = await request.onProviderOAuthStart(selectedProvider)\n if (!handle) {\n setAuthMode('select')\n setError('OAuth flow not available for this provider.')\n return\n }\n oauthPollRef.current = handle\n setOauthUrl(handle.authorizationUrl)\n setOauthCode(handle.code ?? null)\n setOauthInstructions(handle.instructions ?? null)\n // 轮询\n const pollLoop = async () => {\n if (cancelledRef.current) return\n // 归一化:poll 可返回裸字符串(旧实现)或带失败分类的对象(RFC-326 §6.3)。\n const raw = await handle.poll()\n if (cancelledRef.current) return\n const status = typeof raw === 'string' ? raw : raw.status\n const reason = typeof raw === 'string' ? undefined : (raw as { reason?: OAuthFailureReason }).reason\n if (status === 'pending') {\n setTimeout(() => { pollLoop().catch(() => {}) }, 1500)\n } else if (status === 'done') {\n advance()\n } else if (reason === 'cancelled') {\n // 用户主动取消不是错误——静默回到方式选择,不留红字吓人。\n setAuthMode('select')\n } else if (reason === 'network') {\n // §6.3:网络/代理不通时主 CTA 是「打开代理设置」,而非让用户对着\n // \"authorization failed\" 反复重试一条根本走不通的链路。\n setAuthFailure('network')\n } else {\n setAuthFailure('rejected')\n }\n }\n pollLoop().catch(() => {})\n }, [request, advance, selectedProvider])\n\n /** §6.3:授权失败后重试——清失败态重新发起 OAuth。 */\n const handleRetryAuth = useCallback(() => {\n setAuthFailure(null)\n setError(null)\n void handleOAuthStartRef.current?.()\n }, [])\n\n /** §6.3:网络/代理错误的主 CTA——打开代理设置(宿主未提供时不显示该项)。 */\n const handleOpenProxySettings = useCallback(() => {\n setAuthFailure(null)\n request.onOpenProxySettings?.()\n }, [request])\n\n handleOAuthStartRef.current = handleOAuthStart\n\n /** §6.3:重新打开授权页——浏览器没弹出来时最常用的动作,不必退出重来。 */\n const handleOpenAgain = useCallback(() => {\n oauthPollRef.current?.openAgain?.()\n }, [])\n\n /**\n * §6.3:改用 API key——取消进行中的 OAuth 轮询后切到 key 输入态。\n * 必须先 cancel:否则旧的 pollLoop 仍在跑,用户填完 key 成功后可能被那条\n * 迟到的 'error' 分支重置回选择态(既有 pollLoop 在 error 时会 setAuthMode('select'))。\n */\n const handleSwitchToApiKey = useCallback(() => {\n oauthPollRef.current?.cancel()\n oauthPollRef.current = null\n setOauthUrl(null)\n setOauthCode(null)\n setOauthInstructions(null)\n setError(null)\n setApiKey('')\n setAuthMode('key-input')\n }, [setApiKey])\n\n // 内联 API key\n const handleApiKeySubmit = useCallback(async () => {\n if (!request.onSubmitProviderApiKey || !apiKeyRef.current.trim()) return\n setError(null)\n setAuthMode('logging-in')\n const result = await request.onSubmitProviderApiKey(selectedProvider, apiKeyRef.current.trim())\n if (result.ok) {\n advance()\n } else {\n setAuthMode('key-input')\n setError(result.error)\n }\n }, [request, advance, apiKey, selectedProvider])\n\n const handleModel = useCallback(async (modelId: string) => {\n setError(null)\n try {\n await request.onSelectModel(modelId)\n advance()\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e))\n }\n }, [request, advance])\n\n const handleComplete = useCallback(() => advance(), [advance])\n\n useFallbackInput('wizard-dialog', (input, key) => {\n // Welcome page: Enter → Start, Esc → Cancel, Space → toggle checkbox\n if (!welcomed) {\n if (key.return) {\n request.onDontShowAgain(dontShowAgain)\n setWelcomed(true)\n return\n }\n if (key.escape) { exitWizard(); return }\n if (input === ' ') { setDontShowAgain((v) => !v); return }\n return\n }\n // Esc 回退上一步(第一步时关闭向导)\n if (key.escape) { goBack(); return }\n })\n\n const stepHints: Array<[string, string]> = (() => {\n if (!welcomed) {\n return [\n [KEY.enter, request.welcomeStartLabel],\n [KEY.esc, request.welcomeCancelLabel],\n ['Space', request.welcomeDontShowAgainLabel],\n ]\n }\n const escLabel = step === 0 ? 'exit' : 'back'\n switch (step) {\n case 0: {\n const n = Math.min(request.providerOptions.length, 9)\n return [\n [`${KEY.up}${KEY.down}`, i18next.t('hint.select')],\n [`1-${n}`, i18next.t('hint.quick')],\n [KEY.enter, i18next.t('setup.actionInstall')],\n [KEY.esc, escLabel],\n ]\n }\n case 1: {\n if (authMode === 'key-input') return [[KEY.enter, 'submit'], [KEY.esc, escLabel]]\n if (authMode === 'oauth-wait') return [[KEY.esc, 'cancel']]\n const n = Math.min(request.providerAuthOptions(selectedProvider).length, 9)\n return [[`1-${n}`, i18next.t('hint.quick')], [KEY.enter, i18next.t('setup.actionLogin')], [KEY.esc, escLabel]]\n }\n case 2: {\n const n = Math.min(request.modelOptions.length, 9)\n return [\n [`${KEY.up}${KEY.down}`, i18next.t('hint.select')],\n [`1-${n}`, i18next.t('hint.quick')],\n [KEY.enter, i18next.t('hint.confirm')],\n [KEY.esc, escLabel],\n ]\n }\n case 3:\n return [[KEY.enter, i18next.t('hint.confirm')]]\n default:\n return [[KEY.esc, escLabel]]\n }\n })()\n\n return (\n <DialogFrame\n \n header={<DialogTitle>{request.title}</DialogTitle>}\n footer={<HintBar items={stepHints} />}\n >\n {!welcomed ? (\n <Box flexDirection='column' marginTop={SPACING.tight} paddingX={SPACING.section}>\n <Box marginBottom={SPACING.tight}>\n <Text bold>{request.welcomeTitle}</Text>\n </Box>\n {request.welcomeGreeting && (\n <Box marginBottom={SPACING.tight}>\n <Text>{request.welcomeGreeting}</Text>\n </Box>\n )}\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{request.welcomeDescription}</Typography>\n </Box>\n <Separator />\n <Box marginBottom={SPACING.tight}>\n <Text color={dontShowAgain ? colors.text : colors.inactive}>\n {dontShowAgain ? FIGURES.bullet : FIGURES.todoPending} {request.welcomeDontShowAgainLabel}\n </Text>\n </Box>\n </Box>\n ) : (\n <Box flexDirection='column' marginTop={SPACING.tight} paddingX={SPACING.section}>\n {compactProgress(step, completed, request.stepNames, colors)}\n\n <Box marginBottom={SPACING.tight} flexDirection='column'>\n <Text bold>{request.stepSubtitles[step]}</Text>\n </Box>\n\n <Separator />\n\n {error && (\n <Box marginBottom={SPACING.tight} flexDirection='column'>\n <Text color={colors.error}>{i18next.t('setup.errorTitle')}:</Text>\n <Text color={colors.error}>{error}</Text>\n </Box>\n )}\n\n <StepContent\n step={step}\n request={request}\n installing={installing}\n installPhase={installPhase}\n installSucceeded={installSucceeded}\n installedCapabilities={installedCapabilities}\n installPendingCaps={installPendingCaps}\n installFailed={installFailed}\n onRetryInstall={retryInstall}\n onChooseAnother={chooseAnotherProvider}\n authMode={authMode}\n setAuthMode={setAuthMode}\n oauthUrl={oauthUrl}\n oauthCode={oauthCode}\n oauthInstructions={oauthInstructions}\n apiKey={apiKey}\n apiKeyRef={apiKeyRef}\n setApiKey={setApiKey}\n onInstall={handleInstall}\n onOAuthStart={handleOAuthStart}\n onApiKeySubmit={handleApiKeySubmit}\n onOpenAgain={handleOpenAgain}\n onSwitchToApiKey={handleSwitchToApiKey}\n onRetryAuth={handleRetryAuth}\n onOpenProxySettings={handleOpenProxySettings}\n authFailure={authFailure}\n onModel={handleModel}\n onComplete={handleComplete}\n selectedProvider={selectedProvider}\n />\n </Box>\n )}\n </DialogFrame>\n )\n}\n\n// ── 步骤分发 ──\n\ninterface StepContentProps {\n step: number\n request: WizardRequest\n installing: string | null\n installPhase: string | null\n installSucceeded: string | null\n installedCapabilities: readonly string[]\n installPendingCaps: boolean\n installFailed: string | null\n onRetryInstall: () => void\n onChooseAnother: () => void\n authMode: 'select' | 'oauth-wait' | 'key-input' | 'logging-in'\n setAuthMode: (m: 'select' | 'oauth-wait' | 'key-input' | 'logging-in') => void\n oauthUrl: string | null\n oauthCode: string | null\n oauthInstructions: string | null\n apiKey: string\n setApiKey: (v: string) => void\n apiKeyRef: React.MutableRefObject<string>\n onInstall: (value: string) => Promise<void>\n onOAuthStart: () => Promise<void>\n onApiKeySubmit: () => Promise<void>\n onOpenAgain: () => void\n onSwitchToApiKey: () => void\n onRetryAuth: () => void\n onOpenProxySettings: () => void\n authFailure: OAuthFailureReason | null\n onModel: (modelId: string) => Promise<void>\n onComplete: () => void\n selectedProvider: string\n}\n\nfunction StepContent(p: StepContentProps): React.ReactElement {\n switch (p.step) {\n case 0: return <StepProvider request={p.request} installing={p.installing} installPhase={p.installPhase} installSucceeded={p.installSucceeded} installedCapabilities={p.installedCapabilities} installPendingCaps={p.installPendingCaps} installFailed={p.installFailed} onRetryInstall={p.onRetryInstall} onChooseAnother={p.onChooseAnother} onInstall={p.onInstall} />\n case 1: return (\n <StepProviderAuth\n request={p.request}\n authMode={p.authMode}\n setAuthMode={p.setAuthMode}\n oauthUrl={p.oauthUrl}\n oauthCode={p.oauthCode}\n oauthInstructions={p.oauthInstructions}\n apiKey={p.apiKey}\n apiKeyRef={p.apiKeyRef}\n setApiKey={p.setApiKey}\n onOAuthStart={p.onOAuthStart}\n onApiKeySubmit={p.onApiKeySubmit}\n onOpenAgain={p.onOpenAgain}\n onSwitchToApiKey={p.onSwitchToApiKey}\n onRetryAuth={p.onRetryAuth}\n onOpenProxySettings={p.onOpenProxySettings}\n authFailure={p.authFailure}\n selectedProvider={p.selectedProvider}\n />\n )\n case 2: return <StepModel request={p.request} onModel={p.onModel} />\n case 3: return <StepComplete request={p.request} onComplete={p.onComplete} />\n default: return <Text>Unknown step</Text>\n }\n}\n\n/** RFC-278:Step 0 active 阶段行内 spinner——复用 `ProgressDialog.tsx` 同款帧节奏/reduced-motion 判断。 */\n// RFC-306 L1:80ms → 160ms(安装 spinner 同档降帧)\nconst SPINNER_FRAME_MS = 160\nfunction InstallSpinner(): React.ReactElement | null {\n const colors = useColors()\n const rm = reducedMotion()\n const [frame, setFrame] = useState(0)\n // RFC-319 M2:纯动效走统一调度器(rm 时零注册;下方 `if (rm) return null` 保留)。\n useAnimationFrame(SPINNER_FRAME_MS, (f) => setFrame(f % SPINNER_FRAMES.length))\n if (rm) return null\n return <Text color={colors.accent}> {SPINNER_FRAMES[frame]}</Text>\n}\n\n// ── Step 0: Provider ──\n\nfunction StepProvider({ request, installing, installPhase, installSucceeded, installedCapabilities, installPendingCaps, installFailed, onRetryInstall, onChooseAnother, onInstall }: {\n request: WizardRequest\n installing: string | null\n installPhase: string | null\n installSucceeded: string | null\n installedCapabilities: readonly string[]\n installPendingCaps: boolean\n installFailed: string | null\n onRetryInstall: () => void\n onChooseAnother: () => void\n onInstall: (v: string) => Promise<void>\n}): React.ReactElement {\n const colors = useColors()\n const [sel, setSel] = useState(0)\n const opts = request.providerOptions\n const busy = installing !== null\n\n // RFC-278:安装成功终态——阶段列表全部勾选完成 + 醒目的成功提示,短暂停留后由\n // handleInstall 的 setTimeout 触发 advance()(本组件不感知延迟本身,只负责渲染)。\n if (installSucceeded) {\n return (\n <Box flexDirection='column'>\n {INSTALL_PHASE_LABELS.map((label) => (\n <Box key={label}>\n <Text color={colors.success}>{INSTALL_PHASE_ICONS.done} </Text>\n <Text color={colors.success}>{label}</Text>\n </Box>\n ))}\n <Box marginTop={SPACING.tight}>\n <Text color={colors.success} bold>{FIGURES.check} Installed {installSucceeded}</Text>\n </Box>\n {/*\n RFC-326:能力授予的**终态披露**。\n 向导安装走 `runPluginInstall(..., { autoTrust: true })`,它跳过的只是确认\n 弹窗——`trustPlugin` + `grantPluginCapabilities` 照常执行(见\n extension-plugin.ts)。对照 CLI 路径 `otto extension install` 会明确打印\n 「已授予高危能力…可随时撤销」,而向导因 autoTrust 连带压制全部 toast,\n 用户全程无感:在最不了解 otto 权限模型的时刻被静默授予了处理其全部凭据\n 与请求流量的能力。\n 修法刻意选\"终态披露 + 可撤销入口\"而非\"安装前加一次确认\"——后者会与\n RFC-326 §4 原则 4(一屏一个决定、单一主 CTA)冲突,把首次流程再加一道\n 打断。这里只在成功态补一行事实说明,不拦路。\n */}\n {/*\n RFC-326 M2-05:装上但能力未授权时,该插件的 providers 被 fail-closed\n 整体跳过——\"✓ Installed\" 是真的,但它贡献零个模型。不说明的话,用户会\n 在下一步面对空模型列表且不知道为什么。用 warning 色而非 secondary,\n 因为这是需要用户行动的状态,不是背景信息。\n */}\n {installPendingCaps && (\n <Box marginTop={SPACING.tight}>\n <Text color={colors.warning}>{i18next.t('setup.installPendingCapabilities')}</Text>\n </Box>\n )}\n {installedCapabilities.length > 0 && (\n <Box marginTop={SPACING.tight} flexDirection='column'>\n <Typography type='secondary'>\n {i18next.t('setup.capabilityGranted', { caps: installedCapabilities.join(', ') })}\n </Typography>\n <Typography type='secondary'>{i18next.t('setup.capabilityRevokeHint')}</Typography>\n </Box>\n )}\n </Box>\n )\n }\n\n // RFC-326 §6.2:安装失败 → 显式恢复动作,而非只留一行红字让用户猜下一步。\n // (错误原文由 WizardDialog 顶部的 error 区渲染,此处只给可执行的出路。)\n if (installFailed) {\n return (\n <Box flexDirection='column'>\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>\n {i18next.t('setup.installFailedRecovery', { provider: installFailed })}\n </Typography>\n </Box>\n <Menu.Root\n id='wizard-install-failed'\n onPick={(i) => { if (i === 0) onRetryInstall(); else onChooseAnother() }}\n >\n <Menu.Item label={i18next.t('setup.actionRetryInstall')} />\n <Menu.Item label={i18next.t('setup.actionChooseAnotherProvider')} />\n </Menu.Root>\n </Box>\n )\n }\n\n if (busy) {\n const activeIdx = resolveInstallPhaseIndex(installPhase)\n return (\n <Box flexDirection='column'>\n {INSTALL_PHASE_LABELS.map((label, i) => {\n const isDone = activeIdx >= 0 && i < activeIdx\n const isActive = i === activeIdx || (activeIdx < 0 && i === 0)\n const icon = isDone ? INSTALL_PHASE_ICONS.done : isActive ? INSTALL_PHASE_ICONS.active : INSTALL_PHASE_ICONS.pending\n const color = isDone ? colors.success : isActive ? colors.accent : colors.inactive\n return (\n <Box key={label}>\n <Text color={color}>{icon} </Text>\n <Text color={isActive ? colors.text : color}>{label} {installing}</Text>\n {isActive ? <InstallSpinner /> : null}\n </Box>\n )\n })}\n </Box>\n )\n }\n\n // RFC-268 M2:手抄竖排(原 pointer 无编号)收编到 Menu 原语——补齐编号 + 键盘归\n // useRovingFocus(↑↓ 漫游 / 1-9 直选 onInstall / Enter 确认)。注:providerOptions\n // 来自插件列表可能 >9,数字键仅覆盖前 9 项,其余靠 ↑↓+Enter(RFC-268 D5)。\n return (\n <Menu.Root\n id='wizard-dialog-2'\n onPick={(i) => { if (!busy && opts[i]) void onInstall(opts[i]!.value) }}\n activeIndex={sel}\n onActiveChange={setSel}\n >\n {opts.map((opt) => (\n <Menu.Item key={opt.value} label={opt.label} detail={opt.detail} color={colors.selection} />\n ))}\n </Menu.Root>\n )\n}\n\n// ── Step 1: Provider Auth(RFC-274:不是账号登录,是 provider 授权/connect) ──\n\nfunction StepProviderAuth({ request, authMode, setAuthMode, oauthUrl, oauthCode, oauthInstructions, apiKey, apiKeyRef, setApiKey, onOAuthStart, onApiKeySubmit, onOpenAgain, onSwitchToApiKey, onRetryAuth, onOpenProxySettings, authFailure, selectedProvider }: {\n request: WizardRequest\n authMode: 'select' | 'oauth-wait' | 'key-input' | 'logging-in'\n setAuthMode: (m: 'select' | 'oauth-wait' | 'key-input' | 'logging-in') => void\n oauthUrl: string | null\n oauthCode: string | null\n oauthInstructions: string | null\n apiKey: string\n apiKeyRef: React.MutableRefObject<string>\n setApiKey: (v: string) => void\n onOAuthStart: () => Promise<void>\n onApiKeySubmit: () => Promise<void>\n onOpenAgain: () => void\n onSwitchToApiKey: () => void\n onRetryAuth: () => void\n onOpenProxySettings: () => void\n authFailure: OAuthFailureReason | null\n selectedProvider: string\n}): React.ReactElement {\n const colors = useColors()\n\n // RFC-234 外 bug 修复:useInput/useState 必须在所有条件 return 之前注册(hooks 规则)。\n // 此前 key-input/oauth-wait/logging-in 的早 return 导致 useInput 未注册,\n // key-input 模式下键盘输入完全不工作。\n const [sel, setSel] = useState(0)\n const mode: string = authMode\n const opts = request.providerAuthOptions(selectedProvider)\n\n useFallbackInput('wizard-dialog-3', (input, key) => {\n if (mode === 'key-input') {\n if (key.return && apiKeyRef.current.trim()) { void onApiKeySubmit(); return }\n if (key.escape) { setAuthMode('select'); return }\n if (input && input.length > 0) { setApiKey(apiKeyRef.current + input); return }\n if (key.backspace || key.delete) { setApiKey(apiKeyRef.current.slice(0, -1)); return }\n return\n }\n if (mode !== 'select') return\n // RFC-274 M2 修复:导航循环长度必须按真实选项数(opts.length),不能写死 % 2——\n // 部分 provider(如未装 OAuth 的插件)只有 1 个选项(API key),% 2 会在只有 1\n // 项或未来出现 3+ 项时选中越界/循环错误。\n if (key.upArrow || key.downArrow) setSel((s) => (s + 1) % Math.max(opts.length, 1))\n if (key.return && opts[sel]) {\n const opt = opts[sel]!\n if (opt.value === 'oauth' && request.onProviderOAuthStart) void onOAuthStart()\n else if (opt.value === 'key') { setApiKey(''); setAuthMode('key-input') }\n }\n // 数字快捷键按真实选项数量启用,不假设固定 2 项。\n const n = input ? Number(input) : NaN\n if (Number.isInteger(n) && n >= 1 && n <= opts.length) {\n const opt = opts[n - 1]!\n if (opt.value === 'oauth' && request.onProviderOAuthStart) void onOAuthStart()\n else if (opt.value === 'key') { setApiKey(''); setAuthMode('key-input') }\n }\n })\n\n // RFC-326 §6.3:授权失败按分类给对症出路,而非一律 \"authorization failed\"。\n if (authFailure) {\n const isNetwork = authFailure === 'network'\n return (\n <Box flexDirection='column'>\n <Box marginBottom={SPACING.tight}>\n <Text color={colors.error}>\n {i18next.t(isNetwork ? 'setup.authFailedNetwork' : 'setup.authFailedRejected')}\n </Text>\n </Box>\n <Menu.Root\n id='wizard-auth-failed'\n onPick={(i) => {\n // 网络态首项是「打开代理设置」(主 CTA);其余情形首项即重试。\n if (isNetwork && i === 0) { onOpenProxySettings(); return }\n const idx = isNetwork ? i - 1 : i\n if (idx === 0) onRetryAuth()\n else onSwitchToApiKey()\n }}\n >\n {isNetwork\n ? <Menu.Item label={i18next.t('setup.actionOpenProxySettings')} />\n : null}\n <Menu.Item label={i18next.t('setup.actionRetryAuth')} />\n <Menu.Item label={i18next.t('setup.actionUseApiKeyInstead')} />\n </Menu.Root>\n </Box>\n )\n }\n\n if (request.onProviderOAuthStart && authMode === 'oauth-wait') {\n // device-flow 场景(oauthCode 存在)与纯浏览器 pkce 场景共用同一个 'oauth-wait'\n // 状态——不在选项级别区分(见 RFC-274 §4\"实施修正记录\"),仅在这个等待态渲染层\n // 按 code 是否存在分叉展示内容。\n return (\n <Box flexDirection='column'>\n <Box marginBottom={SPACING.tight}>\n <Text>{oauthCode ? 'Complete device authorization' : 'Sign in via OAuth'}</Text>\n </Box>\n {oauthCode ? (\n <>\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>1. Open this page:</Typography>\n </Box>\n {oauthUrl && (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'> {oauthUrl}</Typography>\n </Box>\n )}\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>2. Enter this code:</Typography>\n </Box>\n <Box marginBottom={SPACING.tight}>\n <Text bold color={colors.text}> {oauthCode}</Text>\n </Box>\n {oauthInstructions && (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{oauthInstructions}</Typography>\n </Box>\n )}\n </>\n ) : (\n <>\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>Open this URL in your browser to authorize:</Typography>\n </Box>\n {oauthUrl && (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{oauthUrl}</Typography>\n </Box>\n )}\n </>\n )}\n <Box marginBottom={SPACING.tight}>\n <Text color={colors.text}>{i18next.t('setup.oauthWaiting')}</Text>\n </Box>\n {/*\n RFC-326 §6.3:等待态必须给出 Open again / Use API key instead / Cancel。\n 此前只有 Esc(取消整个授权)——浏览器没弹出来是 OAuth 最常见的失败形态\n (无头环境、默认浏览器未配置、误关标签页),用户只能退出重来才能再看一眼\n 链接,或者干脆放弃 OAuth 却不知道还能改用 API key。\n Cancel 保持走 Esc(既有 goBack 语义,footer 已提示),此处只补两个正向动作。\n */}\n <Menu.Root\n id='wizard-oauth-wait'\n onPick={(i) => {\n if (i === 0) onOpenAgain()\n else onSwitchToApiKey()\n }}\n >\n <Menu.Item label={i18next.t('setup.actionOpenAgain')} />\n <Menu.Item label={i18next.t('setup.actionUseApiKeyInstead')} />\n </Menu.Root>\n </Box>\n )\n }\n\n // 已选择授权方式 → 进入对应内联流程\n if (authMode === 'key-input') {\n return (\n <Box flexDirection='column'>\n <Box marginBottom={SPACING.tight}><Text>Paste your API key</Text></Box>\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>Generate a key on your provider dashboard and paste it below.</Typography>\n </Box>\n {/* RFC-326 §6.3:提交前说明保存位置 + \"不写入项目或聊天记录\"。粘贴 API key\n 是首次流程里信任成本最高的一步,不说清去向等于要求用户盲信。 */}\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{i18next.t('setup.apiKeyStorageNote')}</Typography>\n </Box>\n <Box marginBottom={SPACING.tight}>\n <TextInput\n value={apiKey}\n offset={0}\n columns={60}\n mode='dialog'\n mask={true}\n placeholder={i18next.t('setup.wizardApiKeyPlaceholder')}\n focused={true}\n />\n </Box>\n </Box>\n )\n }\n\n if (authMode === 'logging-in') {\n return (\n <Box flexDirection='column'>\n <Text color={colors.text}>{i18next.t('setup.loggingIn')}</Text>\n </Box>\n )\n }\n\n // 有内联回调 → 直接在向导内进入(useInput 已在顶部注册)\n return (\n <Box flexDirection='column'>\n {selectedProvider && (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{selectedProvider}</Typography>\n </Box>\n )}\n <Box flexDirection='column'>\n {opts.map((opt, i) => {\n return (\n <Box key={opt.value} marginBottom={SPACING.tight} flexDirection='column'>\n <Text color={i === sel ? colors.selection : colors.inactive} bold={i === sel}>\n {i === sel ? FIGURES.pointer : ' '} {opt.label}\n </Text>\n {opt.detail && (\n <Box marginLeft={SPACING.section}>\n <Typography type='secondary'>{opt.detail}</Typography>\n </Box>\n )}\n </Box>\n )\n })}\n </Box>\n </Box>\n )\n}\n\n// ── Step 2: Model(Select.Root 自管焦点:↑↓/Enter/数字键) ──\n\nfunction StepModel({ request, onModel }: {\n request: WizardRequest; onModel: (id: string) => Promise<void>\n}): React.ReactElement {\n // RFC-326 §6.4:不可用模型必须说明\"为什么\",而不是笼统灰掉——用户看到\n // \"缺凭据\"才知道该去登录,看到\"订阅风险\"才知道换个模型而非重新授权。\n const reasonLabel = (reason?: string): string =>\n reason === 'unauthenticated'\n ? i18next.t('setup.modelUnusableUnauthenticated')\n : reason === 'oauth-429-risk'\n ? i18next.t('setup.modelUnusableOauthRisk')\n : ''\n const hasRecommended = request.modelOptions.some((opt) => opt.recommended)\n return (\n <Box flexDirection='column'>\n {hasRecommended && (\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>{i18next.t('setup.modelRecommendedHint')}</Typography>\n </Box>\n )}\n <Select.Root\n maxVisible={8}\n frame={false}\n onValueChange={(value) => { void onModel(value) }}\n >\n {request.modelOptions.map((opt) => {\n const reason = reasonLabel(opt.unusableReason)\n // 推荐/不可用原因并入 detail 行:Select.Item 的 detail 是既有的次要信息\n // 通道,不新造渲染分支——避免与 SelectPrimitive 的窗口/滚动逻辑打架。\n const detail = [\n opt.recommended ? i18next.t('setup.modelRecommendedTag') : '',\n reason,\n opt.detail ?? '',\n ].filter(Boolean).join(' · ')\n return (\n <Select.Item key={opt.value} value={opt.value} detail={detail || undefined}>\n {opt.label}\n </Select.Item>\n )\n })}\n </Select.Root>\n </Box>\n )\n}\n\n// ── Step 3: Complete ──\n\nfunction StepComplete({ request, onComplete }: {\n request: WizardRequest; onComplete: () => void\n}): React.ReactElement {\n const colors = useColors()\n useFallbackInput('wizard-dialog-4', (_input, key) => {\n if (key.return) onComplete()\n })\n\n return (\n <Box flexDirection='column'>\n <Box marginBottom={SPACING.tight}>\n <Typography type='secondary'>Try asking something like:</Typography>\n </Box>\n <Box flexDirection='column' marginBottom={SPACING.tight}>\n {request.examplePrompts.map((p, i) => (\n <Typography key={i} type='secondary'> \"{p}\"</Typography>\n ))}\n </Box>\n <Text color={colors.text}>Press Enter to start chatting</Text>\n </Box>\n )\n}\n"],"mappings":"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"}