소스 검색

fix(theme): update startupAccent color description to reflect Claude Code CN pastel green

Jarvis 2 달 전
부모
커밋
b2cf3ac6b3

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ node_modules/
 dist/
 dist/
 cli
 cli
 cli-dev
 cli-dev
+package-lock.json

+ 41 - 34
README.md

@@ -26,6 +26,7 @@ curl -fsSL https://raw.githubusercontent.com/go-enols/Claude-CN/main/install.sh
 此脚本将自动检测系统、安装 Bun(如需要)、克隆代码库、构建并启用所有功能,然后将其添加到 PATH 中。
 此脚本将自动检测系统、安装 Bun(如需要)、克隆代码库、构建并启用所有功能,然后将其添加到 PATH 中。
 
 
 安装完成后,运行以下命令启动:
 安装完成后,运行以下命令启动:
+
 ```bash
 ```bash
 export ANTHROPIC_API_KEY="sk-ant-..."
 export ANTHROPIC_API_KEY="sk-ant-..."
 claude-cn
 claude-cn
@@ -71,12 +72,12 @@ bun run compile
 
 
 ### 构建变体
 ### 构建变体
 
 
-| 命令 | 输出 | 功能 | 说明 |
-|---|---|---|---|
-| `bun run build` | `./cli` | 仅 `VOICE_MODE` | 生产级二进制文件 |
-| `bun run build:dev` | `./cli-dev` | 仅 `VOICE_MODE` | 开发版本标识 |
-| `bun run build:dev:full` | `./cli-dev` | 所有45+实验性标志 | 完整解锁构建 |
-| `bun run compile` | `./dist/cli` | 仅 `VOICE_MODE` | 备用输出目录 |
+| 命令                     | 输出         | 功能              | 说明             |
+| ------------------------ | ------------ | ----------------- | ---------------- |
+| `bun run build`          | `./cli`      | 仅 `VOICE_MODE`   | 生产级二进制文件 |
+| `bun run build:dev`      | `./cli-dev`  | 仅 `VOICE_MODE`   | 开发版本标识     |
+| `bun run build:dev:full` | `./cli-dev`  | 所有45+实验性标志 | 完整解锁构建     |
+| `bun run compile`        | `./dist/cli` | 仅 `VOICE_MODE`   | 备用输出目录     |
 
 
 ### 单独启用功能标志
 ### 单独启用功能标志
 
 
@@ -124,6 +125,12 @@ export ANTHROPIC_API_KEY="sk-ant-..."
 ./cli --model claude-sonnet-4-6-20250514
 ./cli --model claude-sonnet-4-6-20250514
 ```
 ```
 
 
+### 快速使用
+
+- 编译好后可安装CC-Switch已切换自定义模型
+
+- 前往 **_[cc-switch GitHub Releases](https://github.com/farion1231/cc-switch/blob/main/docs/release-notes/v3.12.3-zh.md)_** 页面下载最新版本的安装包。
+
 ---
 ---
 
 
 ## 核心改动
 ## 核心改动
@@ -152,24 +159,24 @@ Anthropic 在每次对话中注入系统级指令,超出模型本身的约束
 
 
 Claude Code 附带数十个通过 `bun:bundle` 编译时开关门控的功能标志。本构建解锁了所有能正常编译的 45+ 个标志,包括:
 Claude Code 附带数十个通过 `bun:bundle` 编译时开关门控的功能标志。本构建解锁了所有能正常编译的 45+ 个标志,包括:
 
 
-| 功能标志 | 说明 |
-|---|---|
-| `ULTRAPLAN` | Claude Code 网页上的远程多智能体规划(Opus级别) |
-| `ULTRATHINK` | 深度思考模式 - 输入"ultrathink"提升推理努力 |
-| `VOICE_MODE` | 按键通话语音输入和听写 |
-| `AGENT_TRIGGERS` | 后台自动化本地 cron/触发器工具 |
-| `BRIDGE_MODE` | IDE 远程控制桥(VS Code、JetBrains) |
-| `TOKEN_BUDGET` | Token 预算跟踪和使用警告 |
-| `BUILTIN_EXPLORE_PLAN_AGENTS` | 内置探索/规划智能体预设 |
-| `VERIFICATION_AGENT` | 任务验证智能体 |
-| `BASH_CLASSIFIER` | 分类器辅助的 bash 权限决策 |
-| `EXTRACT_MEMORIES` | 查询后自动记忆提取 |
-| `HISTORY_PICKER` | 交互式提示历史选择器 |
-| `MESSAGE_ACTIONS` | UI 中的消息操作入口点 |
-| `QUICK_SEARCH` | 提示快速搜索 |
-| `SHOT_STATS` | Shot分布统计 |
-| `COMPACTION_REMINDERS` | 上下文压缩周围的智能提醒 |
-| `CACHED_MICROCOMPACT` | 通过查询流程的缓存微压缩状态 |
+| 功能标志                      | 说明                                             |
+| ----------------------------- | ------------------------------------------------ |
+| `ULTRAPLAN`                   | Claude Code 网页上的远程多智能体规划(Opus级别) |
+| `ULTRATHINK`                  | 深度思考模式 - 输入"ultrathink"提升推理努力      |
+| `VOICE_MODE`                  | 按键通话语音输入和听写                           |
+| `AGENT_TRIGGERS`              | 后台自动化本地 cron/触发器工具                   |
+| `BRIDGE_MODE`                 | IDE 远程控制桥(VS Code、JetBrains)             |
+| `TOKEN_BUDGET`                | Token 预算跟踪和使用警告                         |
+| `BUILTIN_EXPLORE_PLAN_AGENTS` | 内置探索/规划智能体预设                          |
+| `VERIFICATION_AGENT`          | 任务验证智能体                                   |
+| `BASH_CLASSIFIER`             | 分类器辅助的 bash 权限决策                       |
+| `EXTRACT_MEMORIES`            | 查询后自动记忆提取                               |
+| `HISTORY_PICKER`              | 交互式提示历史选择器                             |
+| `MESSAGE_ACTIONS`             | UI 中的消息操作入口点                            |
+| `QUICK_SEARCH`                | 提示快速搜索                                     |
+| `SHOT_STATS`                  | Shot分布统计                                     |
+| `COMPACTION_REMINDERS`        | 上下文压缩周围的智能提醒                         |
+| `CACHED_MICROCOMPACT`         | 通过查询流程的缓存微压缩状态                     |
 
 
 完整的功能标志审计请参阅 [FEATURES.md](FEATURES.md)。
 完整的功能标志审计请参阅 [FEATURES.md](FEATURES.md)。
 
 
@@ -206,16 +213,16 @@ src/
 
 
 ## 技术栈
 ## 技术栈
 
 
-| | |
-|---|---|
-| 运行时 | [Bun](https://bun.sh) |
-| 语言 | TypeScript |
-| 终端 UI | React + [Ink](https://github.com/vadimdemedes/ink) |
-| CLI 解析 | [Commander.js](https://github.com/tj/commander.js) |
-| Schema 验证 | Zod v4 |
-| 代码搜索 | ripgrep(已打包) |
-| 协议 | MCP、LSP |
-| API | Anthropic Messages API |
+|             |                                                    |
+| ----------- | -------------------------------------------------- |
+| 运行时      | [Bun](https://bun.sh)                              |
+| 语言        | TypeScript                                         |
+| 终端 UI     | React + [Ink](https://github.com/vadimdemedes/ink) |
+| CLI 解析    | [Commander.js](https://github.com/tj/commander.js) |
+| Schema 验证 | Zod v4                                             |
+| 代码搜索    | ripgrep(已打包)                                  |
+| 协议        | MCP、LSP                                           |
+| API         | Anthropic Messages API                             |
 
 
 ---
 ---
 
 

+ 1 - 1
src/components/LogoV2/ChannelsNotice.tsx

@@ -135,7 +135,7 @@ export function ChannelsNotice() {
   }
   }
   let t2;
   let t2;
   if ($[24] !== flag) {
   if ($[24] !== flag) {
-    t2 = <Text dimColor={true}>Experimental · inbound messages will be pushed into this session, this carries prompt injection risks. Restart Free Code without {flag} to disable.</Text>;
+    t2 = <Text dimColor={true}>Experimental · inbound messages will be pushed into this session, this carries prompt injection risks. Restart Claude Code CN without {flag} to disable.</Text>;
     $[24] = flag;
     $[24] = flag;
     $[25] = t2;
     $[25] = t2;
   } else {
   } else {

+ 1 - 1
src/components/LogoV2/CondensedLogo.tsx

@@ -88,7 +88,7 @@ export function CondensedLogo() {
   }
   }
   let t5;
   let t5;
   if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
   if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
-    t5 = <Text bold={true}>Free Code</Text>;
+    t5 = <Text bold={true}>Claude Code CN</Text>;
     $[8] = t5;
     $[8] = t5;
   } else {
   } else {
     t5 = $[8];
     t5 = $[8];

+ 1 - 1
src/components/LogoV2/GuestPassesUpsell.tsx

@@ -60,7 +60,7 @@ export function GuestPassesUpsell() {
   let t0;
   let t0;
   if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
   if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
     const reward = getCachedReferrerReward();
     const reward = getCachedReferrerReward();
-    t0 = <Text dimColor={true}><Text color="startupAccent">[✻]</Text> <Text color="startupAccent">[✻]</Text>{" "}<Text color="startupAccent">[✻]</Text> ·{" "}{reward ? `分享 Free Code 并赚取 ${formatCreditAmount(reward)} 的额外用量 · /passes` : "3 个访客通行证在 /passes"}</Text>;
+    t0 = <Text dimColor={true}><Text color="startupAccent">[✻]</Text> <Text color="startupAccent">[✻]</Text>{" "}<Text color="startupAccent">[✻]</Text> ·{" "}{reward ? `分享 Claude Code CN 并赚取 ${formatCreditAmount(reward)} 的额外用量 · /passes` : "3 个访客通行证在 /passes"}</Text>;
     $[0] = t0;
     $[0] = t0;
   } else {
   } else {
     t0 = $[0];
     t0 = $[0];

+ 2 - 2
src/components/LogoV2/LogoV2.tsx

@@ -248,8 +248,8 @@ export function LogoV2() {
   }
   }
   const layoutMode = getLayoutMode(columns);
   const layoutMode = getLayoutMode(columns);
   const userTheme = resolveThemeSetting(getGlobalConfig().theme);
   const userTheme = resolveThemeSetting(getGlobalConfig().theme);
-  const borderTitle = ` ${color("startupAccent", userTheme)("Free Code")} ${color("inactive", userTheme)(`v${version}`)} `;
-  const compactBorderTitle = color("startupAccent", userTheme)(" Free Code ");
+  const borderTitle = ` ${color("startupAccent", userTheme)("Claude Code CN")} ${color("inactive", userTheme)(`v${version}`)} `;
+  const compactBorderTitle = color("startupAccent", userTheme)(" Claude Code CN ");
   if (layoutMode === "compact") {
   if (layoutMode === "compact") {
     let welcomeMessage = formatWelcomeMessage(username);
     let welcomeMessage = formatWelcomeMessage(username);
     if (stringWidth(welcomeMessage) > columns - 4) {
     if (stringWidth(welcomeMessage) > columns - 4) {

+ 3 - 3
src/components/LogoV2/WelcomeV2.tsx

@@ -9,7 +9,7 @@ export function WelcomeV2() {
   if (env.terminal === "Apple_Terminal") {
   if (env.terminal === "Apple_Terminal") {
     let t0;
     let t0;
     if ($[0] !== theme) {
     if ($[0] !== theme) {
-      t0 = <AppleTerminalWelcomeV2 theme={theme} welcomeMessage="欢迎使用 Free Code" />;
+      t0 = <AppleTerminalWelcomeV2 theme={theme} welcomeMessage="欢迎使用 Claude Code CN" />;
       $[0] = theme;
       $[0] = theme;
       $[1] = t0;
       $[1] = t0;
     } else {
     } else {
@@ -28,7 +28,7 @@ export function WelcomeV2() {
     let t7;
     let t7;
     let t8;
     let t8;
     if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
     if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
-      t0 = <Text><Text color="startupAccent">{"欢迎使用 Free Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
+      t0 = <Text><Text color="startupAccent">{"欢迎使用 Claude Code CN"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
       t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
       t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
       t2 = <Text>{"                                                          "}</Text>;
       t2 = <Text>{"                                                          "}</Text>;
       t3 = <Text>{"                                                          "}</Text>;
       t3 = <Text>{"                                                          "}</Text>;
@@ -113,7 +113,7 @@ export function WelcomeV2() {
   let t5;
   let t5;
   let t6;
   let t6;
   if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
   if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
-    t0 = <Text><Text color="startupAccent">{"欢迎使用 Free Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
+    t0 = <Text><Text color="startupAccent">{"欢迎使用 Claude Code CN"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
     t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
     t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
     t2 = <Text>{"                                                          "}</Text>;
     t2 = <Text>{"                                                          "}</Text>;
     t3 = <Text>{"     *                                       \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591     "}</Text>;
     t3 = <Text>{"     *                                       \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591     "}</Text>;

+ 7 - 7
src/components/LogoV2/feedConfigs.tsx

@@ -39,7 +39,7 @@ export function createWhatsNewFeed(releaseNotes: string[]): FeedConfig {
       text: note
       text: note
     };
     };
   });
   });
-  const emptyMessage = "external" === 'ant' ? '无法获取最新的 claude-cli-internal 提交' : '查看 Free Code 更新日志以获取更新';
+  const emptyMessage = "external" === 'ant' ? '无法获取最新的 claude-cli-internal 提交' : '查看 Claude Code CN 更新日志以获取更新';
   return {
   return {
     title: "external" === 'ant' ? "新功能 [ANT-ONLY: Latest CC commits]" : "新功能",
     title: "external" === 'ant' ? "新功能 [ANT-ONLY: Latest CC commits]" : "新功能",
     lines,
     lines,
@@ -73,17 +73,17 @@ export function createProjectOnboardingFeed(steps: Step[]): FeedConfig {
 }
 }
 export function createGuestPassesFeed(): FeedConfig {
 export function createGuestPassesFeed(): FeedConfig {
   const reward = getCachedReferrerReward();
   const reward = getCachedReferrerReward();
-  const subtitle = reward ? `分享 Free Code 并赚取 ${formatCreditAmount(reward)} 的额外用量` : '与朋友分享 Free Code';
+  const subtitle = reward ? `分享 Claude Code CN 并赚取 ${formatCreditAmount(reward)} 的额外用量` : '与朋友分享 Claude Code CN';
   return {
   return {
     title: '3 个访客通行证',
     title: '3 个访客通行证',
     lines: [],
     lines: [],
     customContent: {
     customContent: {
       content: <>
       content: <>
-          <Box marginY={1}>
-            <Text color="startupAccent">[✻] [✻] [✻]</Text>
-          </Box>
-          <Text dimColor>{subtitle}</Text>
-        </>,
+        <Box marginY={1}>
+          <Text color="startupAccent">[✻] [✻] [✻]</Text>
+        </Box>
+        <Text dimColor>{subtitle}</Text>
+      </>,
       width: 48
       width: 48
     },
     },
     footer: '/passes'
     footer: '/passes'

+ 310 - 310
src/screens/REPL.tsx

@@ -97,8 +97,8 @@ import { logError } from '../utils/log.js';
 /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
 /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
 const useVoiceIntegration: typeof import('../hooks/useVoiceIntegration.js').useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({
 const useVoiceIntegration: typeof import('../hooks/useVoiceIntegration.js').useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({
   stripTrailing: () => 0,
   stripTrailing: () => 0,
-  handleKeyEvent: () => {},
-  resetAnchor: () => {}
+  handleKeyEvent: () => { },
+  resetAnchor: () => { }
 });
 });
 const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler : () => null;
 const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler : () => null;
 // Frustration detection is ant-only (dogfooding). Conditional require so external
 // Frustration detection is ant-only (dogfooding). Conditional require so external
@@ -106,11 +106,11 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V
 // on every messages change, plus the GrowthBook fetch).
 // on every messages change, plus the GrowthBook fetch).
 const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = "external" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
 const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = "external" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
   state: 'closed',
   state: 'closed',
-  handleTranscriptSelect: () => {}
+  handleTranscriptSelect: () => { }
 });
 });
 // Ant-only org warning. Conditional require so the org UUID list is
 // Ant-only org warning. Conditional require so the org UUID list is
 // eliminated from external builds (one UUID is on excluded-strings).
 // eliminated from external builds (one UUID is on excluded-strings).
-const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = "external" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {};
+const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = "external" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => { };
 // Dead code elimination: conditional import for coordinator mode
 // Dead code elimination: conditional import for coordinator mode
 const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
 const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
   name: string;
   name: string;
@@ -192,7 +192,7 @@ import { useInboxPoller } from '../hooks/useInboxPoller.js';
 // Dead code elimination: conditional import for loop mode
 // Dead code elimination: conditional import for loop mode
 /* eslint-disable @typescript-eslint/no-require-imports */
 /* eslint-disable @typescript-eslint/no-require-imports */
 const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
 const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
-const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => {};
+const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => { };
 const PROACTIVE_FALSE = () => false;
 const PROACTIVE_FALSE = () => false;
 const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
 const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
 const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
 const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
@@ -299,7 +299,7 @@ const EMPTY_MCP_CLIENTS: MCPServerConnection[] = [];
 // Stable stub for useAssistantHistory's non-KAIROS branch — avoids a new
 // Stable stub for useAssistantHistory's non-KAIROS branch — avoids a new
 // function identity each render, which would break composedOnScroll's memo.
 // function identity each render, which would break composedOnScroll's memo.
 const HISTORY_STUB = {
 const HISTORY_STUB = {
-  maybeLoadOlder: (_: ScrollBoxHandle) => {}
+  maybeLoadOlder: (_: ScrollBoxHandle) => { }
 };
 };
 // Window after a user-initiated scroll during which type-into-empty does NOT
 // Window after a user-initiated scroll during which type-into-empty does NOT
 // repin to bottom. Josh Rosen's workflow: Claude emits long output → scroll
 // repin to bottom. Josh Rosen's workflow: Claude emits long output → scroll
@@ -450,28 +450,28 @@ function TranscriptSearchBar({
   const off = cursorOffset;
   const off = cursorOffset;
   const cursorChar = off < query.length ? query[off] : ' ';
   const cursorChar = off < query.length ? query[off] : ' ';
   return <Box borderTopDimColor borderBottom={false} borderLeft={false} borderRight={false} borderStyle="single" marginTop={1} paddingLeft={2} width="100%"
   return <Box borderTopDimColor borderBottom={false} borderLeft={false} borderRight={false} borderStyle="single" marginTop={1} paddingLeft={2} width="100%"
-  // applySearchHighlight scans the whole screen buffer. The query
-  // text rendered here IS on screen — /foo matches its own 'foo' in
-  // the bar. With no content matches that's the ONLY visible match →
-  // gets CURRENT → underlined. noSelect makes searchHighlight.ts:76
-  // skip these cells (same exclusion as gutters). You can't text-
-  // select the bar either; it's transient chrome, fine.
-  noSelect>
-      <Text>/</Text>
-      <Text>{query.slice(0, off)}</Text>
-      <Text inverse>{cursorChar}</Text>
-      {off < query.length && <Text>{query.slice(off + 1)}</Text>}
-      <Box flexGrow={1} />
-      {indexStatus === 'building' ? <Text dimColor>索引中… </Text> : indexStatus ? <Text dimColor>{indexStatus.ms}毫秒索引完成 </Text> : count === 0 && query ? <Text color="error">无匹配 </Text> : count > 0 ?
-    // Engine-counted (indexOf on extractSearchText). May drift from
-    // render-count for ghost/phantom messages — badge is a rough
-    // location hint. scanElement gives exact per-message positions
-    // but counting ALL would cost ~1-3ms × matched-messages.
-    <Text dimColor>
-          {current}/{count}
-          {'  '}
-        </Text> : null}
-    </Box>;
+    // applySearchHighlight scans the whole screen buffer. The query
+    // text rendered here IS on screen — /foo matches its own 'foo' in
+    // the bar. With no content matches that's the ONLY visible match →
+    // gets CURRENT → underlined. noSelect makes searchHighlight.ts:76
+    // skip these cells (same exclusion as gutters). You can't text-
+    // select the bar either; it's transient chrome, fine.
+    noSelect>
+    <Text>/</Text>
+    <Text>{query.slice(0, off)}</Text>
+    <Text inverse>{cursorChar}</Text>
+    {off < query.length && <Text>{query.slice(off + 1)}</Text>}
+    <Box flexGrow={1} />
+    {indexStatus === 'building' ? <Text dimColor>索引中… </Text> : indexStatus ? <Text dimColor>{indexStatus.ms}毫秒索引完成 </Text> : count === 0 && query ? <Text color="error">无匹配 </Text> : count > 0 ?
+      // Engine-counted (indexOf on extractSearchText). May drift from
+      // render-count for ghost/phantom messages — badge is a rough
+      // location hint. scanElement gives exact per-message positions
+      // but counting ALL would cost ~1-3ms × matched-messages.
+      <Text dimColor>
+        {current}/{count}
+        {'  '}
+      </Text> : null}
+  </Box>;
 }
 }
 const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
 const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
 const TITLE_STATIC_PREFIX = '✳';
 const TITLE_STATIC_PREFIX = '✳';
@@ -607,8 +607,8 @@ export function REPL({
   const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
   const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
   const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
   const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
   const disableMessageActions = feature('MESSAGE_ACTIONS') ?
   const disableMessageActions = feature('MESSAGE_ACTIONS') ?
-  // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
-  useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_MESSAGE_ACTIONS), []) : false;
+    // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
+    useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_MESSAGE_ACTIONS), []) : false;
 
 
   // Log REPL mount/unmount lifecycle
   // Log REPL mount/unmount lifecycle
   useEffect(() => {
   useEffect(() => {
@@ -873,11 +873,11 @@ export function REPL({
 
 
   // Ref for the bridge result callback — set after useReplBridge initializes,
   // Ref for the bridge result callback — set after useReplBridge initializes,
   // read in the onQuery finally block to notify mobile clients that a turn ended.
   // read in the onQuery finally block to notify mobile clients that a turn ended.
-  const sendBridgeResultRef = useRef<() => void>(() => {});
+  const sendBridgeResultRef = useRef<() => void>(() => { });
 
 
   // Ref for the synchronous restore callback — set after restoreMessageSync is
   // Ref for the synchronous restore callback — set after restoreMessageSync is
   // defined, read in the onQuery finally block for auto-restore on interrupt.
   // defined, read in the onQuery finally block for auto-restore on interrupt.
-  const restoreMessageSyncRef = useRef<(m: UserMessage) => void>(() => {});
+  const restoreMessageSyncRef = useRef<(m: UserMessage) => void>(() => { });
 
 
   // Ref to the fullscreen layout's scroll box for keyboard scrolling.
   // Ref to the fullscreen layout's scroll box for keyboard scrolling.
   // Null when fullscreen mode is disabled (ref never attached).
   // Null when fullscreen mode is disabled (ref never attached).
@@ -1135,7 +1135,7 @@ export function REPL({
   // session from mid-conversation context.
   // session from mid-conversation context.
   const haikuTitleAttemptedRef = useRef((initialMessages?.length ?? 0) > 0);
   const haikuTitleAttemptedRef = useRef((initialMessages?.length ?? 0) > 0);
   const agentTitle = mainThreadAgentDefinition?.agentType;
   const agentTitle = mainThreadAgentDefinition?.agentType;
-  const terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Free Code';
+  const terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Claude Code CN';
   const isWaitingForApproval = toolUseConfirmQueue.length > 0 || promptQueue.length > 0 || pendingWorkerRequest || pendingSandboxRequest;
   const isWaitingForApproval = toolUseConfirmQueue.length > 0 || promptQueue.length > 0 || pendingWorkerRequest || pendingSandboxRequest;
   // Local-jsx commands (like /plugin, /config) show user-facing dialogs that
   // Local-jsx commands (like /plugin, /config) show user-facing dialogs that
   // wait for input. Require jsx != null — if the flag is stuck true but jsx
   // wait for input. Require jsx != null — if the flag is stuck true but jsx
@@ -1254,8 +1254,8 @@ export function REPL({
   const cursorNavRef = useRef<MessageActionsNav | null>(null);
   const cursorNavRef = useRef<MessageActionsNav | null>(null);
   // Memoized so Messages' React.memo holds.
   // Memoized so Messages' React.memo holds.
   const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
   const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
-  // eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
-  [dividerIndex, messages.length]);
+    // eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
+    [dividerIndex, messages.length]);
   // Re-pin scroll to bottom and clear the unseen-messages baseline. Called
   // Re-pin scroll to bottom and clear the unseen-messages baseline. Called
   // on any user-driven return-to-live action (submit, type-into-empty,
   // on any user-driven return-to-live action (submit, type-into-empty,
   // overlay appear/dismiss).
   // overlay appear/dismiss).
@@ -1284,13 +1284,13 @@ export function REPL({
   const {
   const {
     maybeLoadOlder
     maybeLoadOlder
   } = feature('KAIROS') ?
   } = feature('KAIROS') ?
-  // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
-  useAssistantHistory({
-    config: remoteSessionConfig,
-    setMessages,
-    scrollRef,
-    onPrepend: shiftDivider
-  }) : HISTORY_STUB;
+      // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
+      useAssistantHistory({
+        config: remoteSessionConfig,
+        setMessages,
+        scrollRef,
+        onPrepend: shiftDivider
+      }) : HISTORY_STUB;
   // Compose useUnseenDivider's callbacks with the lazy-load trigger.
   // Compose useUnseenDivider's callbacks with the lazy-load trigger.
   const composedOnScroll = useCallback((sticky: boolean, handle: ScrollBoxHandle) => {
   const composedOnScroll = useCallback((sticky: boolean, handle: ScrollBoxHandle) => {
     lastUserScrollTsRef.current = Date.now();
     lastUserScrollTsRef.current = Date.now();
@@ -1601,12 +1601,12 @@ export function REPL({
       swarmStartTimeRef.current = null;
       swarmStartTimeRef.current = null;
       swarmBudgetInfoRef.current = undefined;
       swarmBudgetInfoRef.current = undefined;
       setMessages(prev => [...prev, createTurnDurationMessage(totalMs, deferredBudget,
       setMessages(prev => [...prev, createTurnDurationMessage(totalMs, deferredBudget,
-      // Count only what recordTranscript will persist — ephemeral
-      // progress ticks and non-ant attachments are filtered by
-      // isLoggableMessage and never reach disk. Using raw prev.length
-      // would make checkResumeConsistency report false delta<0 for
-      // every turn that ran a progress-emitting tool.
-      count(prev, isLoggableMessage))]);
+        // Count only what recordTranscript will persist — ephemeral
+        // progress ticks and non-ant attachments are filtered by
+        // isLoggableMessage and never reach disk. Using raw prev.length
+        // would make checkResumeConsistency report false delta<0 for
+        // every turn that ran a progress-emitting tool.
+        count(prev, isLoggableMessage))]);
     }
     }
   }, [hasRunningTeammates, setMessages]);
   }, [hasRunningTeammates, setMessages]);
 
 
@@ -1673,19 +1673,19 @@ export function REPL({
     setToolJSX
     setToolJSX
   });
   });
   const showSpinner = (!toolJSX || toolJSX.showSpinner === true) && toolUseConfirmQueue.length === 0 && promptQueue.length === 0 && (
   const showSpinner = (!toolJSX || toolJSX.showSpinner === true) && toolUseConfirmQueue.length === 0 && promptQueue.length === 0 && (
-  // Show spinner during input processing, API call, while teammates are running,
-  // or while pending task notifications are queued (prevents spinner bounce between consecutive notifications)
-  isLoading || userInputOnProcessing || hasRunningTeammates ||
-  // Keep spinner visible while task notifications are queued for processing.
-  // Without this, the spinner briefly disappears between consecutive notifications
-  // (e.g., multiple background agents completing in rapid succession) because
-  // isLoading goes false momentarily between processing each one.
-  getCommandQueueLength() > 0) &&
-  // Hide spinner when waiting for leader to approve permission request
-  !pendingWorkerRequest && !onlySleepToolActive && (
-  // Hide spinner when streaming text is visible (the text IS the feedback),
-  // but keep it when isBriefOnly suppresses the streaming text display
-  !visibleStreamingText || isBriefOnly);
+    // Show spinner during input processing, API call, while teammates are running,
+    // or while pending task notifications are queued (prevents spinner bounce between consecutive notifications)
+    isLoading || userInputOnProcessing || hasRunningTeammates ||
+    // Keep spinner visible while task notifications are queued for processing.
+    // Without this, the spinner briefly disappears between consecutive notifications
+    // (e.g., multiple background agents completing in rapid succession) because
+    // isLoading goes false momentarily between processing each one.
+    getCommandQueueLength() > 0) &&
+    // Hide spinner when waiting for leader to approve permission request
+    !pendingWorkerRequest && !onlySleepToolActive && (
+      // Hide spinner when streaming text is visible (the text IS the feedback),
+      // but keep it when isBriefOnly suppresses the streaming text display
+      !visibleStreamingText || isBriefOnly);
 
 
   // Check if any permission or ask question prompt is currently visible
   // Check if any permission or ask question prompt is currently visible
   // This is used to prevent the survey from opening while prompts are active
   // This is used to prevent the survey from opening while prompts are active
@@ -2331,9 +2331,9 @@ export function REPL({
     addNotification({
     addNotification({
       key: 'sandbox-unavailable',
       key: 'sandbox-unavailable',
       jsx: <>
       jsx: <>
-          <Text color="warning">沙箱已禁用</Text>
-          <Text dimColor> · /sandbox</Text>
-        </>,
+        <Text color="warning">沙箱已禁用</Text>
+        <Text dimColor> · /sandbox</Text>
+      </>,
       priority: 'medium'
       priority: 'medium'
     });
     });
   }, [addNotification]);
   }, [addNotification]);
@@ -2694,7 +2694,7 @@ export function REPL({
       if (text && !text.startsWith(`<${LOCAL_COMMAND_STDOUT_TAG}>`) && !text.startsWith(`<${COMMAND_MESSAGE_TAG}>`) && !text.startsWith(`<${COMMAND_NAME_TAG}>`) && !text.startsWith(`<${BASH_INPUT_TAG}>`)) {
       if (text && !text.startsWith(`<${LOCAL_COMMAND_STDOUT_TAG}>`) && !text.startsWith(`<${COMMAND_MESSAGE_TAG}>`) && !text.startsWith(`<${COMMAND_NAME_TAG}>`) && !text.startsWith(`<${BASH_INPUT_TAG}>`)) {
         haikuTitleAttemptedRef.current = true;
         haikuTitleAttemptedRef.current = true;
         void generateSessionTitle(text, new AbortController().signal).then(title => {
         void generateSessionTitle(text, new AbortController().signal).then(title => {
-          if (title) setHaikuTitle(title);else haikuTitleAttemptedRef.current = false;
+          if (title) setHaikuTitle(title); else haikuTitleAttemptedRef.current = false;
         }, () => {
         }, () => {
           haikuTitleAttemptedRef.current = false;
           haikuTitleAttemptedRef.current = false;
         });
         });
@@ -2768,11 +2768,11 @@ export function REPL({
       });
       });
     }
     }
     queryCheckpoint('query_context_loading_start');
     queryCheckpoint('query_context_loading_start');
-    const [,, defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([
-    // IMPORTANT: do this after setMessages() above, to avoid UI jank
-    checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
-    // Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
-    feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(toolPermissionContext, setAppState, store.getState().fastMode) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), freshMcpClients), getUserContext(), getSystemContext()]);
+    const [, , defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([
+      // IMPORTANT: do this after setMessages() above, to avoid UI jank
+      checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
+      // Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
+      feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(toolPermissionContext, setAppState, store.getState().fastMode) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), freshMcpClients), getUserContext(), getSystemContext()]);
     const userContext = {
     const userContext = {
       ...baseUserContext,
       ...baseUserContext,
       ...getCoordinatorUserContext(freshMcpClients, isScratchpadEnabled() ? getScratchpadDir() : undefined),
       ...getCoordinatorUserContext(freshMcpClients, isScratchpadEnabled() ? getScratchpadDir() : undefined),
@@ -3118,9 +3118,9 @@ export function REPL({
       if (typeof content === 'string' && !initialMsg.message.planContent) {
       if (typeof content === 'string' && !initialMsg.message.planContent) {
         // Route through onSubmit for proper processing including UserPromptSubmit hooks
         // Route through onSubmit for proper processing including UserPromptSubmit hooks
         void onSubmit(content, {
         void onSubmit(content, {
-          setCursorOffset: () => {},
-          clearBuffer: () => {},
-          resetHistory: () => {}
+          setCursorOffset: () => { },
+          clearBuffer: () => { },
+          resetHistory: () => { }
         });
         });
       } else {
       } else {
         // Plan messages or complex content (images, etc.) - send directly to model
         // Plan messages or complex content (images, etc.) - send directly to model
@@ -3129,10 +3129,10 @@ export function REPL({
         const newAbortController = createAbortController();
         const newAbortController = createAbortController();
         setAbortController(newAbortController);
         setAbortController(newAbortController);
         void onQuery([initialMsg.message], newAbortController, true,
         void onQuery([initialMsg.message], newAbortController, true,
-        // shouldQuery
-        [],
-        // additionalAllowedTools
-        mainLoopModel);
+          // shouldQuery
+          [],
+          // additionalAllowedTools
+          mainLoopModel);
       }
       }
 
 
       // Reset ref after a delay to allow new initial messages
       // Reset ref after a delay to allow new initial messages
@@ -3534,18 +3534,18 @@ export function REPL({
       setStashedPrompt(undefined);
       setStashedPrompt(undefined);
     }
     }
   }, [queryGuard,
   }, [queryGuard,
-  // isLoading is read at the !isLoading checks above for input-clearing
-  // and submitCount gating. It's derived from isQueryActive || isExternalLoading,
-  // so including it here ensures the closure captures the fresh value.
-  isLoading, isExternalLoading, inputMode, commands, setInputValue, setInputMode, setPastedContents, setSubmitCount, setIDESelection, setToolJSX, getToolUseContext,
-  // messages is read via messagesRef.current inside the callback to
-  // keep onSubmit stable across message updates (see L2384/L2400/L2662).
-  // Without this, each setMessages call (~30× per turn) recreates
-  // onSubmit, pinning the REPL render scope (1776B) + that render's
-  // messages array in downstream closures (PromptInput, handleAutoRunIssue).
-  // Heap analysis showed ~9 REPL scopes and ~15 messages array versions
-  // accumulating after #20174/#20175, all traced to this dep.
-  mainLoopModel, pastedContents, ideSelection, setUserInputOnProcessing, setAbortController, addNotification, onQuery, stashedPrompt, setStashedPrompt, setAppState, onBeforeQuery, canUseTool, remoteSession, setMessages, awaitPendingHooks, repinScroll]);
+    // isLoading is read at the !isLoading checks above for input-clearing
+    // and submitCount gating. It's derived from isQueryActive || isExternalLoading,
+    // so including it here ensures the closure captures the fresh value.
+    isLoading, isExternalLoading, inputMode, commands, setInputValue, setInputMode, setPastedContents, setSubmitCount, setIDESelection, setToolJSX, getToolUseContext,
+    // messages is read via messagesRef.current inside the callback to
+    // keep onSubmit stable across message updates (see L2384/L2400/L2662).
+    // Without this, each setMessages call (~30× per turn) recreates
+    // onSubmit, pinning the REPL render scope (1776B) + that render's
+    // messages array in downstream closures (PromptInput, handleAutoRunIssue).
+    // Heap analysis showed ~9 REPL scopes and ~15 messages array versions
+    // accumulating after #20174/#20175, all traced to this dep.
+    mainLoopModel, pastedContents, ideSelection, setUserInputOnProcessing, setAbortController, addNotification, onQuery, stashedPrompt, setStashedPrompt, setAppState, onBeforeQuery, canUseTool, remoteSession, setMessages, awaitPendingHooks, repinScroll]);
 
 
   // Callback for when user submits input while viewing a teammate's transcript
   // Callback for when user submits input while viewing a teammate's transcript
   const onAgentSubmit = useCallback(async (input: string, task: InProcessTeammateTaskState | LocalAgentTaskState, helpers: PromptInputHelpers) => {
   const onAgentSubmit = useCallback(async (input: string, task: InProcessTeammateTaskState | LocalAgentTaskState, helpers: PromptInputHelpers) => {
@@ -3566,8 +3566,8 @@ export function REPL({
           addNotification({
           addNotification({
             key: `resume-agent-failed-${task.id}`,
             key: `resume-agent-failed-${task.id}`,
             jsx: <Text color="error">
             jsx: <Text color="error">
-                  Failed to resume agent: {errorMessage(err)}
-                </Text>,
+              Failed to resume agent: {errorMessage(err)}
+            </Text>,
             priority: 'low'
             priority: 'low'
           });
           });
         });
         });
@@ -3585,9 +3585,9 @@ export function REPL({
     const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
     const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
     setAutoRunIssueReason(null); // Clear the state
     setAutoRunIssueReason(null); // Clear the state
     onSubmit(command, {
     onSubmit(command, {
-      setCursorOffset: () => {},
-      clearBuffer: () => {},
-      resetHistory: () => {}
+      setCursorOffset: () => { },
+      clearBuffer: () => { },
+      resetHistory: () => { }
     }).catch(err => {
     }).catch(err => {
       logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
       logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
     });
     });
@@ -3600,9 +3600,9 @@ export function REPL({
   const handleSurveyRequestFeedback = useCallback(() => {
   const handleSurveyRequestFeedback = useCallback(() => {
     const command = "external" === 'ant' ? '/issue' : '/feedback';
     const command = "external" === 'ant' ? '/issue' : '/feedback';
     onSubmit(command, {
     onSubmit(command, {
-      setCursorOffset: () => {},
-      clearBuffer: () => {},
-      resetHistory: () => {}
+      setCursorOffset: () => { },
+      clearBuffer: () => { },
+      resetHistory: () => { }
     }).catch(err => {
     }).catch(err => {
       logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
       logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
     });
     });
@@ -3617,9 +3617,9 @@ export function REPL({
   onSubmitRef.current = onSubmit;
   onSubmitRef.current = onSubmit;
   const handleOpenRateLimitOptions = useCallback(() => {
   const handleOpenRateLimitOptions = useCallback(() => {
     void onSubmitRef.current('/rate-limit-options', {
     void onSubmitRef.current('/rate-limit-options', {
-      setCursorOffset: () => {},
-      clearBuffer: () => {},
-      resetHistory: () => {}
+      setCursorOffset: () => { },
+      clearBuffer: () => { },
+      resetHistory: () => { }
     });
     });
   }, []);
   }, []);
   const handleExit = useCallback(async () => {
   const handleExit = useCallback(async () => {
@@ -3636,14 +3636,14 @@ export function REPL({
     }
     }
     const showWorktree = getCurrentWorktreeSession() !== null;
     const showWorktree = getCurrentWorktreeSession() !== null;
     if (showWorktree) {
     if (showWorktree) {
-      setExitFlow(<ExitFlow showWorktree onDone={() => {}} onCancel={() => {
+      setExitFlow(<ExitFlow showWorktree onDone={() => { }} onCancel={() => {
         setExitFlow(null);
         setExitFlow(null);
         setIsExiting(false);
         setIsExiting(false);
       }} />);
       }} />);
       return;
       return;
     }
     }
     const exitMod = await exit.load();
     const exitMod = await exit.load();
-    const exitFlowResult = await exitMod.call(() => {});
+    const exitFlowResult = await exitMod.call(() => { });
     setExitFlow(exitFlowResult);
     setExitFlow(exitFlowResult);
     // If call() returned without killing the process (bg session detach),
     // If call() returned without killing the process (bg session detach),
     // clear isExiting so the UI is usable on reattach. No-op on the normal
     // clear isExiting so the UI is usable on reattach. No-op on the normal
@@ -3757,18 +3757,18 @@ export function REPL({
   };
   };
   const messageActionCaps: MessageActionCaps = {
   const messageActionCaps: MessageActionCaps = {
     copy: text =>
     copy: text =>
-    // setClipboard RETURNS OSC 52 — caller must stdout.write (tmux side-effects load-buffer, but that's tmux-only).
-    void setClipboard(text).then(raw => {
-      if (raw) process.stdout.write(raw);
-      addNotification({
-        // Same key as text-selection copy — repeated copies replace toast, don't queue.
-        key: 'selection-copied',
-        text: 'copied',
-        color: 'success',
-        priority: 'immediate',
-        timeoutMs: 2000
-      });
-    }),
+      // setClipboard RETURNS OSC 52 — caller must stdout.write (tmux side-effects load-buffer, but that's tmux-only).
+      void setClipboard(text).then(raw => {
+        if (raw) process.stdout.write(raw);
+        addNotification({
+          // Same key as text-selection copy — repeated copies replace toast, don't queue.
+          key: 'selection-copied',
+          text: 'copied',
+          color: 'success',
+          priority: 'immediate',
+          timeoutMs: 2000
+        });
+      }),
     edit: async msg => {
     edit: async msg => {
       // Same skip-confirm check as /rewind: lossless → direct, else confirm dialog.
       // Same skip-confirm check as /rewind: lossless → direct, else confirm dialog.
       const rawIdx = findRawIndex(msg.uuid);
       const rawIdx = findRawIndex(msg.uuid);
@@ -3864,14 +3864,14 @@ export function REPL({
   const executeQueuedInput = useCallback(async (queuedCommands: QueuedCommand[]) => {
   const executeQueuedInput = useCallback(async (queuedCommands: QueuedCommand[]) => {
     await handlePromptSubmit({
     await handlePromptSubmit({
       helpers: {
       helpers: {
-        setCursorOffset: () => {},
-        clearBuffer: () => {},
-        resetHistory: () => {}
+        setCursorOffset: () => { },
+        clearBuffer: () => { },
+        resetHistory: () => { }
       },
       },
       queryGuard,
       queryGuard,
       commands,
       commands,
-      onInputChange: () => {},
-      setPastedContents: () => {},
+      onInputChange: () => { },
+      setPastedContents: () => { },
       setToolJSX,
       setToolJSX,
       getToolUseContext,
       getToolUseContext,
       messages,
       messages,
@@ -3932,8 +3932,8 @@ export function REPL({
       // User hasn't interacted since response ended, check other conditions
       // User hasn't interacted since response ended, check other conditions
       const idleTimeSinceResponse = Date.now() - lastQueryCompletionTime;
       const idleTimeSinceResponse = Date.now() - lastQueryCompletionTime;
       if (!isLoading && !toolJSX &&
       if (!isLoading && !toolJSX &&
-      // Use ref to get current dialog state, avoiding stale closure
-      focusedInputDialogRef.current === undefined && idleTimeSinceResponse >= getGlobalConfig().messageIdleNotifThresholdMs) {
+        // Use ref to get current dialog state, avoiding stale closure
+        focusedInputDialogRef.current === undefined && idleTimeSinceResponse >= getGlobalConfig().messageIdleNotifThresholdMs) {
         void sendNotification({
         void sendNotification({
           message: 'Claude 正在等待您的输入',
           message: 'Claude 正在等待您的输入',
           notificationType: 'idle_prompt'
           notificationType: 'idle_prompt'
@@ -3965,13 +3965,13 @@ export function REPL({
       addNotif({
       addNotif({
         key: 'idle-return-hint',
         key: 'idle-return-hint',
         jsx: mode === 'hint_v2' ? <>
         jsx: mode === 'hint_v2' ? <>
-                <Text dimColor>new task? </Text>
-                <Text color="suggestion">/clear</Text>
-                <Text dimColor> to save </Text>
-                <Text color="suggestion">{formattedTokens} tokens</Text>
-              </> : <Text color="warning">
-                new task? /clear to save {formattedTokens} tokens
-              </Text>,
+          <Text dimColor>new task? </Text>
+          <Text color="suggestion">/clear</Text>
+          <Text dimColor> to save </Text>
+          <Text color="suggestion">{formattedTokens} tokens</Text>
+        </> : <Text color="warning">
+          new task? /clear to save {formattedTokens} tokens
+        </Text>,
         priority: 'medium',
         priority: 'medium',
         // Persist until submit — the hint fires at T+75min idle, user may
         // Persist until submit — the hint fires at T+75min idle, user may
         // not return for hours. removeNotification in useEffect cleanup
         // not return for hours. removeNotification in useEffect cleanup
@@ -4023,17 +4023,17 @@ export function REPL({
 
 
   // Voice input integration (VOICE_MODE builds only)
   // Voice input integration (VOICE_MODE builds only)
   const voice = feature('VOICE_MODE') ?
   const voice = feature('VOICE_MODE') ?
-  // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
-  useVoiceIntegration({
-    setInputValueRaw,
-    inputValueRef,
-    insertTextRef
-  }) : {
-    stripTrailing: () => 0,
-    handleKeyEvent: () => {},
-    resetAnchor: () => {},
-    interimRange: null
-  };
+    // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
+    useVoiceIntegration({
+      setInputValueRaw,
+      inputValueRef,
+      insertTextRef
+    }) : {
+      stripTrailing: () => 0,
+      handleKeyEvent: () => { },
+      resetAnchor: () => { },
+      interimRange: null
+    };
   useInboxPoller({
   useInboxPoller({
     enabled: isAgentSwarmsEnabled(),
     enabled: isAgentSwarmsEnabled(),
     isLoading,
     isLoading,
@@ -4236,11 +4236,11 @@ export function REPL({
       event.stopImmediatePropagation();
       event.stopImmediatePropagation();
     }
     }
   },
   },
-  // Search needs virtual scroll (jumpRef drives VirtualMessageList). [
-  // kills it, so !dumpMode — after [ there's nothing to jump in.
-  {
-    isActive: screen === 'transcript' && virtualScrollActive && !searchOpen && !dumpMode
-  });
+    // Search needs virtual scroll (jumpRef drives VirtualMessageList). [
+    // kills it, so !dumpMode — after [ there's nothing to jump in.
+    {
+      isActive: screen === 'transcript' && virtualScrollActive && !searchOpen && !dumpMode
+    });
   const {
   const {
     setQuery: setHighlight,
     setQuery: setHighlight,
     scanElement,
     scanElement,
@@ -4331,12 +4331,12 @@ export function REPL({
       })();
       })();
     }
     }
   },
   },
-  // !searchOpen: typing 'v' or '[' in the search bar is search input, not
-  // a command. No !dumpMode here — v should work after [ (the [ handler
-  // guards itself inline).
-  {
-    isActive: screen === 'transcript' && virtualScrollActive && !searchOpen
-  });
+    // !searchOpen: typing 'v' or '[' in the search bar is search input, not
+    // a command. No !dumpMode here — v should work after [ (the [ handler
+    // guards itself inline).
+    {
+      isActive: screen === 'transcript' && virtualScrollActive && !searchOpen
+    });
 
 
   // Fresh `less` per transcript entry. Prevents stale highlights matching
   // Fresh `less` per transcript entry. Prevents stale highlights matching
   // unrelated normal-mode text (overlay is alt-screen-global) and avoids
   // unrelated normal-mode text (overlay is alt-screen-global) and avoids
@@ -4404,78 +4404,78 @@ export function REPL({
     const transcriptScrollRef = isFullscreenEnvEnabled() && !disableVirtualScroll && !dumpMode ? scrollRef : undefined;
     const transcriptScrollRef = isFullscreenEnvEnabled() && !disableVirtualScroll && !dumpMode ? scrollRef : undefined;
     const transcriptMessagesElement = <Messages messages={transcriptMessages} tools={tools} commands={commands} verbose={true} toolJSX={null} toolUseConfirmQueue={[]} inProgressToolUseIDs={inProgressToolUseIDs} isMessageSelectorVisible={false} conversationId={conversationId} screen={screen} agentDefinitions={agentDefinitions} streamingToolUses={transcriptStreamingToolUses} showAllInTranscript={showAllInTranscript} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} hidePastThinking={true} streamingThinking={streamingThinking} scrollRef={transcriptScrollRef} jumpRef={jumpRef} onSearchMatchesChange={onSearchMatchesChange} scanElement={scanElement} setPositions={setPositions} disableRenderCap={dumpMode} />;
     const transcriptMessagesElement = <Messages messages={transcriptMessages} tools={tools} commands={commands} verbose={true} toolJSX={null} toolUseConfirmQueue={[]} inProgressToolUseIDs={inProgressToolUseIDs} isMessageSelectorVisible={false} conversationId={conversationId} screen={screen} agentDefinitions={agentDefinitions} streamingToolUses={transcriptStreamingToolUses} showAllInTranscript={showAllInTranscript} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} hidePastThinking={true} streamingThinking={streamingThinking} scrollRef={transcriptScrollRef} jumpRef={jumpRef} onSearchMatchesChange={onSearchMatchesChange} scanElement={scanElement} setPositions={setPositions} disableRenderCap={dumpMode} />;
     const transcriptToolJSX = toolJSX && <Box flexDirection="column" width="100%">
     const transcriptToolJSX = toolJSX && <Box flexDirection="column" width="100%">
-        {toolJSX.jsx}
-      </Box>;
+      {toolJSX.jsx}
+    </Box>;
     const transcriptReturn = <KeybindingSetup>
     const transcriptReturn = <KeybindingSetup>
-        <AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
-        <GlobalKeybindingHandlers {...globalKeybindingProps} />
-        {feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
-        <CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
-        {transcriptScrollRef ?
-      // ScrollKeybindingHandler must mount before CancelRequestHandler so
-      // ctrl+c-with-selection copies instead of cancelling the active task.
-      // Its raw useInput handler only stops propagation when a selection
-      // exists — without one, ctrl+c falls through to CancelRequestHandler.
-      <ScrollKeybindingHandler scrollRef={scrollRef}
-      // Yield wheel/ctrl+u/d to UltraplanChoiceDialog's own scroll
-      // handler while the modal is showing.
-      isActive={focusedInputDialog !== 'ultraplan-choice'}
-      // g/G/j/k/ctrl+u/ctrl+d would eat keystrokes the search bar
-      // wants. Off while searching.
-      isModal={!searchOpen}
-      // Manual scroll exits the search context — clear the yellow
-      // current-match marker. Positions are (msg, rowOffset)-keyed;
-      // j/k changes scrollTop so rowOffset is stale → wrong row
-      // gets yellow. Next n/N re-establishes via step()→jump().
-      onScroll={() => jumpRef.current?.disarmSearch()} /> : null}
-        <CancelRequestHandler {...cancelRequestProps} />
-        {transcriptScrollRef ? <FullscreenLayout scrollRef={scrollRef} scrollable={<>
-                {transcriptMessagesElement}
-                {transcriptToolJSX}
-                <SandboxViolationExpandedView />
-              </>} bottom={searchOpen ? <TranscriptSearchBar jumpRef={jumpRef}
-      // Seed was tried (c01578c8) — broke /hello muscle
-      // memory (cursor lands after 'foo', /hello → foohello).
-      // Cancel-restore handles the 'don't lose prior search'
-      // concern differently (onCancel re-applies searchQuery).
-      initialQuery="" count={searchCount} current={searchCurrent} onClose={q => {
-        // Enter — commit. 0-match guard: junk query shouldn't
-        // persist (badge hidden, n/N dead anyway).
-        setSearchQuery(searchCount > 0 ? q : '');
-        setSearchOpen(false);
-        // onCancel path: bar unmounts before its useEffect([query])
-        // can fire with ''. Without this, searchCount stays stale
-        // (n guard at :4956 passes) and VML's matches[] too
-        // (nextMatch walks the old array). Phantom nav, no
-        // highlight. onExit (Enter, q non-empty) still commits.
-        if (!q) {
-          setSearchCount(0);
-          setSearchCurrent(0);
+      <AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
+      <GlobalKeybindingHandlers {...globalKeybindingProps} />
+      {feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
+      <CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
+      {transcriptScrollRef ?
+        // ScrollKeybindingHandler must mount before CancelRequestHandler so
+        // ctrl+c-with-selection copies instead of cancelling the active task.
+        // Its raw useInput handler only stops propagation when a selection
+        // exists — without one, ctrl+c falls through to CancelRequestHandler.
+        <ScrollKeybindingHandler scrollRef={scrollRef}
+          // Yield wheel/ctrl+u/d to UltraplanChoiceDialog's own scroll
+          // handler while the modal is showing.
+          isActive={focusedInputDialog !== 'ultraplan-choice'}
+          // g/G/j/k/ctrl+u/ctrl+d would eat keystrokes the search bar
+          // wants. Off while searching.
+          isModal={!searchOpen}
+          // Manual scroll exits the search context — clear the yellow
+          // current-match marker. Positions are (msg, rowOffset)-keyed;
+          // j/k changes scrollTop so rowOffset is stale → wrong row
+          // gets yellow. Next n/N re-establishes via step()→jump().
+          onScroll={() => jumpRef.current?.disarmSearch()} /> : null}
+      <CancelRequestHandler {...cancelRequestProps} />
+      {transcriptScrollRef ? <FullscreenLayout scrollRef={scrollRef} scrollable={<>
+        {transcriptMessagesElement}
+        {transcriptToolJSX}
+        <SandboxViolationExpandedView />
+      </>} bottom={searchOpen ? <TranscriptSearchBar jumpRef={jumpRef}
+        // Seed was tried (c01578c8) — broke /hello muscle
+        // memory (cursor lands after 'foo', /hello → foohello).
+        // Cancel-restore handles the 'don't lose prior search'
+        // concern differently (onCancel re-applies searchQuery).
+        initialQuery="" count={searchCount} current={searchCurrent} onClose={q => {
+          // Enter — commit. 0-match guard: junk query shouldn't
+          // persist (badge hidden, n/N dead anyway).
+          setSearchQuery(searchCount > 0 ? q : '');
+          setSearchOpen(false);
+          // onCancel path: bar unmounts before its useEffect([query])
+          // can fire with ''. Without this, searchCount stays stale
+          // (n guard at :4956 passes) and VML's matches[] too
+          // (nextMatch walks the old array). Phantom nav, no
+          // highlight. onExit (Enter, q non-empty) still commits.
+          if (!q) {
+            setSearchCount(0);
+            setSearchCurrent(0);
+            jumpRef.current?.setSearchQuery('');
+          }
+        }} onCancel={() => {
+          // Esc/ctrl+c/ctrl+g — undo. Bar's effect last fired
+          // with whatever was typed. searchQuery (REPL state)
+          // is unchanged since / (onClose = commit, didn't run).
+          // Two VML calls: '' restores anchor (0-match else-
+          // branch), then searchQuery re-scans from anchor's
+          // nearest. Both synchronous — one React batch.
+          // setHighlight explicit: REPL's sync-effect dep is
+          // searchQuery (unchanged), wouldn't re-fire.
+          setSearchOpen(false);
           jumpRef.current?.setSearchQuery('');
           jumpRef.current?.setSearchQuery('');
-        }
-      }} onCancel={() => {
-        // Esc/ctrl+c/ctrl+g — undo. Bar's effect last fired
-        // with whatever was typed. searchQuery (REPL state)
-        // is unchanged since / (onClose = commit, didn't run).
-        // Two VML calls: '' restores anchor (0-match else-
-        // branch), then searchQuery re-scans from anchor's
-        // nearest. Both synchronous — one React batch.
-        // setHighlight explicit: REPL's sync-effect dep is
-        // searchQuery (unchanged), wouldn't re-fire.
-        setSearchOpen(false);
-        jumpRef.current?.setSearchQuery('');
-        jumpRef.current?.setSearchQuery(searchQuery);
-        setHighlight(searchQuery);
-      }} setHighlight={setHighlight} /> : <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={true} status={editorStatus || undefined} searchBadge={searchQuery && searchCount > 0 ? {
-        current: searchCurrent,
-        count: searchCount
-      } : undefined} />} /> : <>
-            {transcriptMessagesElement}
-            {transcriptToolJSX}
-            <SandboxViolationExpandedView />
-            <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={false} suppressShowAll={dumpMode} status={editorStatus || undefined} />
-          </>}
-      </KeybindingSetup>;
+          jumpRef.current?.setSearchQuery(searchQuery);
+          setHighlight(searchQuery);
+        }} setHighlight={setHighlight} /> : <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={true} status={editorStatus || undefined} searchBadge={searchQuery && searchCount > 0 ? {
+          current: searchCurrent,
+          count: searchCount
+        } : undefined} />} /> : <>
+        {transcriptMessagesElement}
+        {transcriptToolJSX}
+        <SandboxViolationExpandedView />
+        <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={false} suppressShowAll={dumpMode} status={editorStatus || undefined} />
+      </>}
+    </KeybindingSetup>;
     // The virtual-scroll branch (FullscreenLayout above) needs
     // The virtual-scroll branch (FullscreenLayout above) needs
     // <AlternateScreen>'s <Box height={rows}> constraint — without it,
     // <AlternateScreen>'s <Box height={rows}> constraint — without it,
     // ScrollBox's flexGrow has no ceiling, viewport = content height,
     // ScrollBox's flexGrow has no ceiling, viewport = content height,
@@ -4486,8 +4486,8 @@ export function REPL({
     // unwrapped — it wants native terminal scrollback.
     // unwrapped — it wants native terminal scrollback.
     if (transcriptScrollRef) {
     if (transcriptScrollRef) {
       return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
       return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
-          {transcriptReturn}
-        </AlternateScreen>;
+        {transcriptReturn}
+      </AlternateScreen>;
     }
     }
     return transcriptReturn;
     return transcriptReturn;
   }
   }
@@ -4549,11 +4549,11 @@ export function REPL({
   // early return above wraps its virtual-scroll branch the same way; only
   // early return above wraps its virtual-scroll branch the same way; only
   // the 30-cap dump branch stays unwrapped for native terminal scrollback.
   // the 30-cap dump branch stays unwrapped for native terminal scrollback.
   const mainReturn = <KeybindingSetup>
   const mainReturn = <KeybindingSetup>
-      <AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
-      <GlobalKeybindingHandlers {...globalKeybindingProps} />
-      {feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
-      <CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
-      {/* ScrollKeybindingHandler must mount before CancelRequestHandler so
+    <AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
+    <GlobalKeybindingHandlers {...globalKeybindingProps} />
+    {feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
+    <CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
+    {/* ScrollKeybindingHandler must mount before CancelRequestHandler so
           ctrl+c-with-selection copies instead of cancelling the active task.
           ctrl+c-with-selection copies instead of cancelling the active task.
           Its raw useInput handler only stops propagation when a selection
           Its raw useInput handler only stops propagation when a selection
           exists — without one, ctrl+c falls through to CancelRequestHandler.
           exists — without one, ctrl+c falls through to CancelRequestHandler.
@@ -4561,40 +4561,40 @@ export function REPL({
           the modal's inner ScrollBox is not keyboard-driven. onScroll
           the modal's inner ScrollBox is not keyboard-driven. onScroll
           stays suppressed while a modal is showing so scroll doesn't
           stays suppressed while a modal is showing so scroll doesn't
           stamp divider/pill state. */}
           stamp divider/pill state. */}
-      <ScrollKeybindingHandler scrollRef={scrollRef} isActive={isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission')} onScroll={centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll} />
-      {feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
-      <CancelRequestHandler {...cancelRequestProps} />
-      <MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
-        <FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
+    <ScrollKeybindingHandler scrollRef={scrollRef} isActive={isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission')} onScroll={centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll} />
+    {feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
+    <CancelRequestHandler {...cancelRequestProps} />
+    <MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
+      <FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
         setCursor(null);
         setCursor(null);
         jumpToNew(scrollRef.current);
         jumpToNew(scrollRef.current);
       }} scrollable={<>
       }} scrollable={<>
-              <TeammateViewHeader />
-              <Messages messages={displayedMessages} tools={tools} commands={commands} verbose={verbose} toolJSX={toolJSX} toolUseConfirmQueue={toolUseConfirmQueue} inProgressToolUseIDs={viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs} isMessageSelectorVisible={isMessageSelectorVisible} conversationId={conversationId} screen={screen} streamingToolUses={streamingToolUses} showAllInTranscript={showAllInTranscript} agentDefinitions={agentDefinitions} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} streamingText={isLoading && !viewedAgentTask ? visibleStreamingText : null} isBriefOnly={viewedAgentTask ? false : isBriefOnly} unseenDivider={viewedAgentTask ? undefined : unseenDivider} scrollRef={isFullscreenEnvEnabled() ? scrollRef : undefined} trackStickyPrompt={isFullscreenEnvEnabled() ? true : undefined} cursor={cursor} setCursor={setCursor} cursorNavRef={cursorNavRef} />
-              <AwsAuthStatusBox />
-              {/* Hide the processing placeholder while a modal is showing —
+        <TeammateViewHeader />
+        <Messages messages={displayedMessages} tools={tools} commands={commands} verbose={verbose} toolJSX={toolJSX} toolUseConfirmQueue={toolUseConfirmQueue} inProgressToolUseIDs={viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs} isMessageSelectorVisible={isMessageSelectorVisible} conversationId={conversationId} screen={screen} streamingToolUses={streamingToolUses} showAllInTranscript={showAllInTranscript} agentDefinitions={agentDefinitions} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} streamingText={isLoading && !viewedAgentTask ? visibleStreamingText : null} isBriefOnly={viewedAgentTask ? false : isBriefOnly} unseenDivider={viewedAgentTask ? undefined : unseenDivider} scrollRef={isFullscreenEnvEnabled() ? scrollRef : undefined} trackStickyPrompt={isFullscreenEnvEnabled() ? true : undefined} cursor={cursor} setCursor={setCursor} cursorNavRef={cursorNavRef} />
+        <AwsAuthStatusBox />
+        {/* Hide the processing placeholder while a modal is showing —
                   it would sit at the last visible transcript row right above
                   it would sit at the last visible transcript row right above
                   the ▔ divider, showing "❯ /config" as redundant clutter
                   the ▔ divider, showing "❯ /config" as redundant clutter
                   (the modal IS the /config UI). Outside modals it stays so
                   (the modal IS the /config UI). Outside modals it stays so
                   the user sees their input echoed while Claude processes. */}
                   the user sees their input echoed while Claude processes. */}
-              {!disabled && placeholderText && !centeredModal && <UserTextMessage param={{
+        {!disabled && placeholderText && !centeredModal && <UserTextMessage param={{
           text: placeholderText,
           text: placeholderText,
           type: 'text'
           type: 'text'
         }} addMargin={true} verbose={verbose} />}
         }} addMargin={true} verbose={verbose} />}
-              {toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
-                    {toolJSX.jsx}
-                  </Box>}
-              {"external" === 'ant' && <TungstenLiveMonitor />}
-              {feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
-              <Box flexGrow={1} />
-              {showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
-              {!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
-              {isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
-            </>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
-              {feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
-              <Box flexDirection="column" flexGrow={1}>
-                {permissionStickyFooter}
-                {/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
+        {toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
+          {toolJSX.jsx}
+        </Box>}
+        {"external" === 'ant' && <TungstenLiveMonitor />}
+        {feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
+        <Box flexGrow={1} />
+        {showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
+        {!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
+        {isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
+      </>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
+        {feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
+        <Box flexDirection="column" flexGrow={1}>
+          {permissionStickyFooter}
+          {/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
                   /issue) render here, NOT inside scrollable. They stay mounted
                   /issue) render here, NOT inside scrollable. They stay mounted
                   while the main conversation streams behind them, so ScrollBox
                   while the main conversation streams behind them, so ScrollBox
                   relayouts on each new message would drag them around. bottom
                   relayouts on each new message would drag them around. bottom
@@ -4603,13 +4603,13 @@ export function REPL({
                   stays in scrollable: the main loop is paused so no jiggle,
                   stays in scrollable: the main loop is paused so no jiggle,
                   and their tall content (DiffDetailView renders up to 400
                   and their tall content (DiffDetailView renders up to 400
                   lines with no internal scroll) needs the outer ScrollBox. */}
                   lines with no internal scroll) needs the outer ScrollBox. */}
-                {toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && <Box flexDirection="column" width="100%">
-                      {toolJSX.jsx}
-                    </Box>}
-                {!showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && <Box width="100%" flexDirection="column">
-                      <TaskListV2 tasks={tasksV2} isStandalone={true} />
-                    </Box>}
-                {focusedInputDialog === 'sandbox-permission' && <SandboxPermissionRequest key={sandboxPermissionRequestQueue[0]!.hostPattern.host} hostPattern={sandboxPermissionRequestQueue[0]!.hostPattern} onUserResponse={(response: {
+          {toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && <Box flexDirection="column" width="100%">
+            {toolJSX.jsx}
+          </Box>}
+          {!showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && <Box width="100%" flexDirection="column">
+            <TaskListV2 tasks={tasksV2} isStandalone={true} />
+          </Box>}
+          {focusedInputDialog === 'sandbox-permission' && <SandboxPermissionRequest key={sandboxPermissionRequestQueue[0]!.hostPattern.host} hostPattern={sandboxPermissionRequestQueue[0]!.hostPattern} onUserResponse={(response: {
             allow: boolean;
             allow: boolean;
             persistToSettings: boolean;
             persistToSettings: boolean;
           }) => {
           }) => {
@@ -4658,7 +4658,7 @@ export function REPL({
               sandboxBridgeCleanupRef.current.delete(approvedHost);
               sandboxBridgeCleanupRef.current.delete(approvedHost);
             }
             }
           }} />}
           }} />}
-                {focusedInputDialog === 'prompt' && <PromptDialog key={promptQueue[0]!.request.prompt} title={promptQueue[0]!.title} toolInputSummary={promptQueue[0]!.toolInputSummary} request={promptQueue[0]!.request} onRespond={selectedKey => {
+          {focusedInputDialog === 'prompt' && <PromptDialog key={promptQueue[0]!.request.prompt} title={promptQueue[0]!.title} toolInputSummary={promptQueue[0]!.toolInputSummary} request={promptQueue[0]!.request} onRespond={selectedKey => {
             const item = promptQueue[0];
             const item = promptQueue[0];
             if (!item) return;
             if (!item) return;
             item.resolve({
             item.resolve({
@@ -4672,12 +4672,12 @@ export function REPL({
             item.reject(new Error('Prompt cancelled by user'));
             item.reject(new Error('Prompt cancelled by user'));
             setPromptQueue(([, ...tail]) => tail);
             setPromptQueue(([, ...tail]) => tail);
           }} />}
           }} />}
-                {/* Show pending indicator on worker while waiting for leader approval */}
-                {pendingWorkerRequest && <WorkerPendingPermission toolName={pendingWorkerRequest.toolName} description={pendingWorkerRequest.description} />}
-                {/* Show pending indicator for sandbox permission on worker side */}
-                {pendingSandboxRequest && <WorkerPendingPermission toolName="Network Access" description={`Waiting for leader to approve network access to ${pendingSandboxRequest.host}`} />}
-                {/* Worker sandbox permission requests from swarm workers */}
-                {focusedInputDialog === 'worker-sandbox-permission' && <SandboxPermissionRequest key={workerSandboxPermissions.queue[0]!.requestId} hostPattern={{
+          {/* Show pending indicator on worker while waiting for leader approval */}
+          {pendingWorkerRequest && <WorkerPendingPermission toolName={pendingWorkerRequest.toolName} description={pendingWorkerRequest.description} />}
+          {/* Show pending indicator for sandbox permission on worker side */}
+          {pendingSandboxRequest && <WorkerPendingPermission toolName="Network Access" description={`Waiting for leader to approve network access to ${pendingSandboxRequest.host}`} />}
+          {/* Worker sandbox permission requests from swarm workers */}
+          {focusedInputDialog === 'worker-sandbox-permission' && <SandboxPermissionRequest key={workerSandboxPermissions.queue[0]!.requestId} hostPattern={{
             host: workerSandboxPermissions.queue[0]!.host,
             host: workerSandboxPermissions.queue[0]!.host,
             port: undefined
             port: undefined
           } as NetworkHostPattern} onUserResponse={(response: {
           } as NetworkHostPattern} onUserResponse={(response: {
@@ -4721,7 +4721,7 @@ export function REPL({
               }
               }
             }));
             }));
           }} />}
           }} />}
-                {focusedInputDialog === 'elicitation' && <ElicitationDialog key={elicitation.queue[0]!.serverName + ':' + String(elicitation.queue[0]!.requestId)} event={elicitation.queue[0]!} onResponse={(action, content) => {
+          {focusedInputDialog === 'elicitation' && <ElicitationDialog key={elicitation.queue[0]!.serverName + ':' + String(elicitation.queue[0]!.requestId)} event={elicitation.queue[0]!} onResponse={(action, content) => {
             const currentRequest = elicitation.queue[0];
             const currentRequest = elicitation.queue[0];
             if (!currentRequest) return;
             if (!currentRequest) return;
             // Call respond callback to resolve Promise
             // Call respond callback to resolve Promise
@@ -4750,7 +4750,7 @@ export function REPL({
             }));
             }));
             currentRequest?.onWaitingDismiss?.(action);
             currentRequest?.onWaitingDismiss?.(action);
           }} />}
           }} />}
-                {focusedInputDialog === 'cost' && <CostThresholdDialog onDone={() => {
+          {focusedInputDialog === 'cost' && <CostThresholdDialog onDone={() => {
             setShowCostDialog(false);
             setShowCostDialog(false);
             setHaveShownCostDialog(true);
             setHaveShownCostDialog(true);
             saveGlobalConfig(current => ({
             saveGlobalConfig(current => ({
@@ -4759,7 +4759,7 @@ export function REPL({
             }));
             }));
             logEvent('tengu_cost_threshold_acknowledged', {});
             logEvent('tengu_cost_threshold_acknowledged', {});
           }} />}
           }} />}
-                {focusedInputDialog === 'idle-return' && idleReturnPending && <IdleReturnDialog idleMinutes={idleReturnPending.idleMinutes} totalInputTokens={getTotalInputTokens()} onDone={async action => {
+          {focusedInputDialog === 'idle-return' && idleReturnPending && <IdleReturnDialog idleMinutes={idleReturnPending.idleMinutes} totalInputTokens={getTotalInputTokens()} onDone={async action => {
             const pending = idleReturnPending;
             const pending = idleReturnPending;
             setIdleReturnPending(null);
             setIdleReturnPending(null);
             logEvent('tengu_idle_return_action', {
             logEvent('tengu_idle_return_action', {
@@ -4801,13 +4801,13 @@ export function REPL({
             }
             }
             skipIdleCheckRef.current = true;
             skipIdleCheckRef.current = true;
             void onSubmitRef.current(pending.input, {
             void onSubmitRef.current(pending.input, {
-              setCursorOffset: () => {},
-              clearBuffer: () => {},
-              resetHistory: () => {}
+              setCursorOffset: () => { },
+              clearBuffer: () => { },
+              resetHistory: () => { }
             });
             });
           }} />}
           }} />}
-                {focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
-                {"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
+          {focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
+          {"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
             setShowModelSwitchCallout(false);
             setShowModelSwitchCallout(false);
             if (selection === 'switch' && modelAlias) {
             if (selection === 'switch' && modelAlias) {
               setAppState(prev => ({
               setAppState(prev => ({
@@ -4817,8 +4817,8 @@ export function REPL({
               }));
               }));
             }
             }
           }} />}
           }} />}
-                {"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
-                {focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
+          {"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
+          {focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
             setShowEffortCallout(false);
             setShowEffortCallout(false);
             if (selection !== 'dismiss') {
             if (selection !== 'dismiss') {
               setAppState(prev => ({
               setAppState(prev => ({
@@ -4827,7 +4827,7 @@ export function REPL({
               }));
               }));
             }
             }
           }} />}
           }} />}
-                {focusedInputDialog === 'remote-callout' && <RemoteCallout onDone={selection => {
+          {focusedInputDialog === 'remote-callout' && <RemoteCallout onDone={selection => {
             setAppState(prev => {
             setAppState(prev => {
               if (!prev.showRemoteCallout) return prev;
               if (!prev.showRemoteCallout) return prev;
               return {
               return {
@@ -4842,17 +4842,17 @@ export function REPL({
             });
             });
           }} />}
           }} />}
 
 
-                {exitFlow}
+          {exitFlow}
 
 
-                {focusedInputDialog === 'plugin-hint' && hintRecommendation && <PluginHintMenu pluginName={hintRecommendation.pluginName} pluginDescription={hintRecommendation.pluginDescription} marketplaceName={hintRecommendation.marketplaceName} sourceCommand={hintRecommendation.sourceCommand} onResponse={handleHintResponse} />}
+          {focusedInputDialog === 'plugin-hint' && hintRecommendation && <PluginHintMenu pluginName={hintRecommendation.pluginName} pluginDescription={hintRecommendation.pluginDescription} marketplaceName={hintRecommendation.marketplaceName} sourceCommand={hintRecommendation.sourceCommand} onResponse={handleHintResponse} />}
 
 
-                {focusedInputDialog === 'lsp-recommendation' && lspRecommendation && <LspRecommendationMenu pluginName={lspRecommendation.pluginName} pluginDescription={lspRecommendation.pluginDescription} fileExtension={lspRecommendation.fileExtension} onResponse={handleLspResponse} />}
+          {focusedInputDialog === 'lsp-recommendation' && lspRecommendation && <LspRecommendationMenu pluginName={lspRecommendation.pluginName} pluginDescription={lspRecommendation.pluginDescription} fileExtension={lspRecommendation.fileExtension} onResponse={handleLspResponse} />}
 
 
-                {focusedInputDialog === 'desktop-upsell' && <DesktopUpsellStartup onDone={() => setShowDesktopUpsellStartup(false)} />}
+          {focusedInputDialog === 'desktop-upsell' && <DesktopUpsellStartup onDone={() => setShowDesktopUpsellStartup(false)} />}
 
 
-                {feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && <UltraplanChoiceDialog plan={ultraplanPendingChoice.plan} sessionId={ultraplanPendingChoice.sessionId} taskId={ultraplanPendingChoice.taskId} setMessages={setMessages} readFileState={readFileState.current} getAppState={() => store.getState()} setConversationId={setConversationId} /> : null}
+          {feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && <UltraplanChoiceDialog plan={ultraplanPendingChoice.plan} sessionId={ultraplanPendingChoice.sessionId} taskId={ultraplanPendingChoice.taskId} setMessages={setMessages} readFileState={readFileState.current} getAppState={() => store.getState()} setConversationId={setConversationId} /> : null}
 
 
-                {feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && <UltraplanLaunchDialog onChoice={(choice, opts) => {
+          {feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && <UltraplanLaunchDialog onChoice={(choice, opts) => {
             const blurb = ultraplanLaunchPending.blurb;
             const blurb = ultraplanLaunchPending.blurb;
             setAppState(prev => prev.ultraplanLaunchPending ? {
             setAppState(prev => prev.ultraplanLaunchPending ? {
               ...prev,
               ...prev,
@@ -4892,26 +4892,26 @@ export function REPL({
             }).then(appendStdout).catch(logError);
             }).then(appendStdout).catch(logError);
           }} /> : null}
           }} /> : null}
 
 
-                {mrRender()}
-
-                {!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && <>
-                      {autoRunIssueReason && <AutoRunIssueNotification onRun={handleAutoRunIssue} onCancel={handleCancelAutoRunIssue} reason={getAutoRunIssueReasonText(autoRunIssueReason)} />}
-                      {postCompactSurvey.state !== 'closed' ? <FeedbackSurvey state={postCompactSurvey.state} lastResponse={postCompactSurvey.lastResponse} handleSelect={postCompactSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} /> : memorySurvey.state !== 'closed' ? <FeedbackSurvey state={memorySurvey.state} lastResponse={memorySurvey.lastResponse} handleSelect={memorySurvey.handleSelect} handleTranscriptSelect={memorySurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} message="How well did Claude use its memory? (optional)" /> : <FeedbackSurvey state={feedbackSurvey.state} lastResponse={feedbackSurvey.lastResponse} handleSelect={feedbackSurvey.handleSelect} handleTranscriptSelect={feedbackSurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback} />}
-                      {/* Frustration-triggered transcript sharing prompt */}
-                      {frustrationDetection.state !== 'closed' && <FeedbackSurvey state={frustrationDetection.state} lastResponse={null} handleSelect={() => {}} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />}
-                      {/* Skill improvement survey - appears when improvements detected (ant-only) */}
-                      {"external" === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
-                      {showIssueFlagBanner && <IssueFlagBanner />}
-                      {}
-                      <PromptInput debug={debug} ideSelection={ideSelection} hasSuppressedDialogs={!!hasSuppressedDialogs} isLocalJSXCommandActive={isShowingLocalJSXCommand} getToolUseContext={getToolUseContext} toolPermissionContext={toolPermissionContext} setToolPermissionContext={setToolPermissionContext} apiKeyStatus={apiKeyStatus} commands={commands} agents={agentDefinitions.activeAgents} isLoading={isLoading} onExit={handleExit} verbose={verbose} messages={messages} onAutoUpdaterResult={setAutoUpdaterResult} autoUpdaterResult={autoUpdaterResult} input={inputValue} onInputChange={setInputValue} mode={inputMode} onModeChange={setInputMode} stashedPrompt={stashedPrompt} setStashedPrompt={setStashedPrompt} submitCount={submitCount} onShowMessageSelector={handleShowMessageSelector} onMessageActionsEnter={
-            // Works during isLoading — edit cancels first; uuid selection survives appends.
-            feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined} mcpClients={mcpClients} pastedContents={pastedContents} setPastedContents={setPastedContents} vimMode={vimMode} setVimMode={setVimMode} showBashesDialog={showBashesDialog} setShowBashesDialog={setShowBashesDialog} onSubmit={onSubmit} onAgentSubmit={onAgentSubmit} isSearchingHistory={isSearchingHistory} setIsSearchingHistory={setIsSearchingHistory} helpOpen={isHelpOpen} setHelpOpen={setIsHelpOpen} insertTextRef={feature('VOICE_MODE') ? insertTextRef : undefined} voiceInterimRange={voice.interimRange} />
-                      <SessionBackgroundHint onBackgroundSession={handleBackgroundSession} isLoading={isLoading} />
-                    </>}
-                {cursor &&
-          // inputValue is REPL state; typed text survives the round-trip.
-          <MessageActionsBar cursor={cursor} />}
-                {focusedInputDialog === 'message-selector' && <MessageSelector messages={messages} preselectedMessage={messageSelectorPreselect} onPreRestore={onCancel} onRestoreCode={async (message: UserMessage) => {
+          {mrRender()}
+
+          {!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && <>
+            {autoRunIssueReason && <AutoRunIssueNotification onRun={handleAutoRunIssue} onCancel={handleCancelAutoRunIssue} reason={getAutoRunIssueReasonText(autoRunIssueReason)} />}
+            {postCompactSurvey.state !== 'closed' ? <FeedbackSurvey state={postCompactSurvey.state} lastResponse={postCompactSurvey.lastResponse} handleSelect={postCompactSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} /> : memorySurvey.state !== 'closed' ? <FeedbackSurvey state={memorySurvey.state} lastResponse={memorySurvey.lastResponse} handleSelect={memorySurvey.handleSelect} handleTranscriptSelect={memorySurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} message="How well did Claude use its memory? (optional)" /> : <FeedbackSurvey state={feedbackSurvey.state} lastResponse={feedbackSurvey.lastResponse} handleSelect={feedbackSurvey.handleSelect} handleTranscriptSelect={feedbackSurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback} />}
+            {/* Frustration-triggered transcript sharing prompt */}
+            {frustrationDetection.state !== 'closed' && <FeedbackSurvey state={frustrationDetection.state} lastResponse={null} handleSelect={() => { }} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />}
+            {/* Skill improvement survey - appears when improvements detected (ant-only) */}
+            {"external" === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
+            {showIssueFlagBanner && <IssueFlagBanner />}
+            { }
+            <PromptInput debug={debug} ideSelection={ideSelection} hasSuppressedDialogs={!!hasSuppressedDialogs} isLocalJSXCommandActive={isShowingLocalJSXCommand} getToolUseContext={getToolUseContext} toolPermissionContext={toolPermissionContext} setToolPermissionContext={setToolPermissionContext} apiKeyStatus={apiKeyStatus} commands={commands} agents={agentDefinitions.activeAgents} isLoading={isLoading} onExit={handleExit} verbose={verbose} messages={messages} onAutoUpdaterResult={setAutoUpdaterResult} autoUpdaterResult={autoUpdaterResult} input={inputValue} onInputChange={setInputValue} mode={inputMode} onModeChange={setInputMode} stashedPrompt={stashedPrompt} setStashedPrompt={setStashedPrompt} submitCount={submitCount} onShowMessageSelector={handleShowMessageSelector} onMessageActionsEnter={
+              // Works during isLoading — edit cancels first; uuid selection survives appends.
+              feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined} mcpClients={mcpClients} pastedContents={pastedContents} setPastedContents={setPastedContents} vimMode={vimMode} setVimMode={setVimMode} showBashesDialog={showBashesDialog} setShowBashesDialog={setShowBashesDialog} onSubmit={onSubmit} onAgentSubmit={onAgentSubmit} isSearchingHistory={isSearchingHistory} setIsSearchingHistory={setIsSearchingHistory} helpOpen={isHelpOpen} setHelpOpen={setIsHelpOpen} insertTextRef={feature('VOICE_MODE') ? insertTextRef : undefined} voiceInterimRange={voice.interimRange} />
+            <SessionBackgroundHint onBackgroundSession={handleBackgroundSession} isLoading={isLoading} />
+          </>}
+          {cursor &&
+            // inputValue is REPL state; typed text survives the round-trip.
+            <MessageActionsBar cursor={cursor} />}
+          {focusedInputDialog === 'message-selector' && <MessageSelector messages={messages} preselectedMessage={messageSelectorPreselect} onPreRestore={onCancel} onRestoreCode={async (message: UserMessage) => {
             await fileHistoryRewind((updater: (prev: FileHistoryState) => FileHistoryState) => {
             await fileHistoryRewind((updater: (prev: FileHistoryState) => FileHistoryState) => {
               setAppState(prev => ({
               setAppState(prev => ({
                 ...prev,
                 ...prev,
@@ -4993,16 +4993,16 @@ export function REPL({
             setIsMessageSelectorVisible(false);
             setIsMessageSelectorVisible(false);
             setMessageSelectorPreselect(undefined);
             setMessageSelectorPreselect(undefined);
           }} />}
           }} />}
-                {"external" === 'ant' && <DevBar />}
-              </Box>
-              {feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
-            </Box>} />
-      </MCPConnectionManager>
-    </KeybindingSetup>;
+          {"external" === 'ant' && <DevBar />}
+        </Box>
+        {feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
+      </Box>} />
+    </MCPConnectionManager>
+  </KeybindingSetup>;
   if (isFullscreenEnvEnabled()) {
   if (isFullscreenEnvEnabled()) {
     return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
     return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
-        {mainReturn}
-      </AlternateScreen>;
+      {mainReturn}
+    </AlternateScreen>;
   }
   }
   return mainReturn;
   return mainReturn;
 }
 }

+ 4 - 4
src/utils/theme.ts

@@ -117,7 +117,7 @@ const lightTheme: Theme = {
   autoAccept: 'rgb(135,0,255)', // Electric violet
   autoAccept: 'rgb(135,0,255)', // Electric violet
   bashBorder: 'rgb(255,0,135)', // Vibrant pink
   bashBorder: 'rgb(255,0,135)', // Vibrant pink
   claude: 'rgb(215,119,87)', // Claude orange
   claude: 'rgb(215,119,87)', // Claude orange
-  startupAccent: 'rgb(124,176,133)', // Free Code pastel green
+  startupAccent: 'rgb(124,176,133)', // Claude Code CN pastel green
   claudeShimmer: 'rgb(245,149,117)', // Lighter claude orange for shimmer effect
   claudeShimmer: 'rgb(245,149,117)', // Lighter claude orange for shimmer effect
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(87,105,247)', // Medium blue for system spinner
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(87,105,247)', // Medium blue for system spinner
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(117,135,255)', // Lighter blue for system spinner shimmer
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(117,135,255)', // Lighter blue for system spinner shimmer
@@ -364,7 +364,7 @@ const lightDaltonizedTheme: Theme = {
   autoAccept: 'rgb(135,0,255)', // Electric violet
   autoAccept: 'rgb(135,0,255)', // Electric violet
   bashBorder: 'rgb(0,102,204)', // Blue instead of pink
   bashBorder: 'rgb(0,102,204)', // Blue instead of pink
   claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia
   claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia
-  startupAccent: 'rgb(124,176,133)', // Free Code pastel green
+  startupAccent: 'rgb(124,176,133)', // Claude Code CN pastel green
   claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect
   claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(51,102,255)', // Bright blue for system spinner
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(51,102,255)', // Bright blue for system spinner
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(101,152,255)', // Lighter bright blue for system spinner shimmer
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(101,152,255)', // Lighter bright blue for system spinner shimmer
@@ -446,7 +446,7 @@ const darkTheme: Theme = {
   autoAccept: 'rgb(175,135,255)', // Electric violet
   autoAccept: 'rgb(175,135,255)', // Electric violet
   bashBorder: 'rgb(253,93,177)', // Bright pink
   bashBorder: 'rgb(253,93,177)', // Bright pink
   claude: 'rgb(215,119,87)', // Claude orange
   claude: 'rgb(215,119,87)', // Claude orange
-  startupAccent: 'rgb(184,225,174)', // Free Code pastel green
+  startupAccent: 'rgb(184,225,174)', // Claude Code CN pastel green
   claudeShimmer: 'rgb(235,159,127)', // Lighter claude orange for shimmer effect
   claudeShimmer: 'rgb(235,159,127)', // Lighter claude orange for shimmer effect
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(147,165,255)', // Blue for system spinner
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(147,165,255)', // Blue for system spinner
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(177,195,255)', // Lighter blue for system spinner shimmer
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(177,195,255)', // Lighter blue for system spinner shimmer
@@ -528,7 +528,7 @@ const darkDaltonizedTheme: Theme = {
   autoAccept: 'rgb(175,135,255)', // Electric violet
   autoAccept: 'rgb(175,135,255)', // Electric violet
   bashBorder: 'rgb(51,153,255)', // Bright blue
   bashBorder: 'rgb(51,153,255)', // Bright blue
   claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia
   claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia
-  startupAccent: 'rgb(184,225,174)', // Free Code pastel green
+  startupAccent: 'rgb(184,225,174)', // Claude Code CN pastel green
   claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect
   claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(153,204,255)', // Light blue for system spinner
   claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(153,204,255)', // Light blue for system spinner
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(183,224,255)', // Lighter blue for system spinner shimmer
   claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(183,224,255)', // Lighter blue for system spinner shimmer