Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions docs/features/background-agent-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Background Agent Selector — 底部统一后台 Agent 切换器

> Feature Flag: 无(直接启用)
> 实现状态:完整可用
> 依赖:`viewingAgentTaskId` / `enterTeammateView` / `exitTeammateView` 已有机制

## 一、功能概述

Background Agent Selector 是渲染在 PromptInput 下方的常驻状态条,列出当前所有 **backgrounded 的 local_agent 任务**(包括 `/fork` 派生的 fork agent 和 Task/AgentTool 调用 `run_in_background: true` 派生的子 agent)。用户可以用 ↑/↓ 方向键在 `main` 和各 agent 之间切换焦点,按 Enter 把 REPL 主视图替换为所选 agent 的实时 transcript,再按 Enter 选中 `main` 即可回到主对话。

整个机制完全复用官方已有的 teammate transcript 查看基础设施,不引入新的视图层 / 数据流,仅新增一条 footer pill 类型。

### 核心特性

- **统一入口**:`/fork`、Task 派生的 subagent、所有 `run_in_background: true` 的 agent 都在同一栏显示
- **就地切换**:prompt 为空时按 ↓ 溢出进入底部 selector,↑↓ 选中某行,Enter 即切主视图
- **实时状态**:每行显示 agent 类型 + 描述 + 运行时长 + 已消耗 token;running 时圆点为绿色
- **Keep-alive 视图**:agent 完成后在 `evictAfter` grace 窗口内保留一段时间,用户可回看
- **零界面侵入**:tasks 数为 0 时 selector 完全不渲染,不占屏幕高度
- **与旧 Dialog 共存**:Shift+↓ 打开的 `BackgroundTasksDialog` 原有行为保留,selector 只作为展示 + 快捷切换

## 二、用户交互

### 触发方式

有任何 background agent 时,selector 自动出现在 `bypass permissions on` 行下方:

```
claude-code | Opus 4.7 (1M context) | ctx:4%
▶▶ bypass permissions on (shift+tab to cycle)

○ main ↑/↓ to select · Enter to view
● Explore Research src/hooks 23s · ↓ 10.9k tokens
○ Explore Research src/components 22s · ↓ 9.5k tokens
○ Explore Research src/utils 21s · ↓ 13.6k tokens
```
Comment on lines +28 to +36
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to code fence.

The fenced code block should specify a language for proper rendering and linting compliance.

📝 Proposed fix
-```
+```text
   claude-code | Opus 4.7 (1M context) | ctx:4%
   ▶▶ bypass permissions on (shift+tab to cycle)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
claude-code | Opus 4.7 (1M context) | ctx:4%
▶▶ bypass permissions on (shift+tab to cycle)
○ main ↑/↓ to select · Enter to view
● Explore Research src/hooks 23s · ↓ 10.9k tokens
○ Explore Research src/components 22s · ↓ 9.5k tokens
○ Explore Research src/utils 21s · ↓ 13.6k tokens
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/features/background-agent-selector.md` around lines 28 - 36, The fenced
code block that begins with "claude-code | Opus 4.7 (1M context) | ctx:4%" lacks
a language specifier; update the opening backticks (the triple-``` fence) to
include a language such as "text" (e.g., ```text) so the block renders and lints
correctly and leave the block contents unchanged.


### 键盘路由

| 位置 / 状态 | 按键 | 行为 |
|---|---|---|
| PromptInput 非空 | ↑↓ | 光标移动 / 翻历史(不变) |
| PromptInput 空 + 历史底部 | ↓ | 焦点下放到 selector,高亮到 `● main` |
| Selector 聚焦(`footerSelection === 'bg_agent'`) | ↓ | 高亮下移,-1 → 0 → ... → N-1 |
| Selector 聚焦 | ↑ | 高亮上移;在 `main` 再 ↑ → 焦点回 PromptInput |
| Selector 聚焦 | Enter | `-1` → `exitTeammateView`;`>=0` → `enterTeammateView(agentId)`。焦点保留在 pill |
| Selector 聚焦 | Esc | `footer:clearSelection`,焦点回 PromptInput |

### 视觉规则

- `● main` / `● <agent>`:当前被**查看**(viewingAgentTaskId 指向)或被**光标聚焦**(pill focused 时以光标为准)的一行
- running 状态的 agent:圆点渲染为 `success` 色(绿色),与 `BackgroundTasksDialog` 状态语义对齐
- 右上角 hint 随状态变化:
- pill 聚焦:`↑/↓ to select · Enter to view`
- 已选中 running agent:`shift+↓ to manage · x to stop`
- 已选中 terminal agent:`shift+↓ to manage · x to clear`
- 未选中任何 agent:`shift+↓ to manage background agents`

## 三、实现架构

### 3.1 数据层:`useBackgroundAgentTasks`

文件:`src/hooks/useBackgroundAgentTasks.ts`

封装对 `useAppState(s => s.tasks)` 的过滤:

```ts
export function useBackgroundAgentTasks(): LocalAgentTaskState[] {
const tasks = useAppState(s => s.tasks)
return useMemo(() => {
const now = Date.now()
return Object.values(tasks)
.filter(isLocalAgentTask)
.filter(t => t.agentType !== 'main-session')
.filter(t => t.isBackgrounded !== false)
.filter(t => t.evictAfter === undefined || t.evictAfter > now)
.sort((a, b) => a.startTime - b.startTime)
}, [tasks])
}
```

`/fork` 和 `AgentTool` 的 `run_in_background: true` 底层都走 `registerAsyncAgent → runAsyncAgentLifecycle`,最终写入同一个 `appState.tasks` Map;此 hook 是唯一数据源,Selector 和 PromptInput 的 `bgAgentList` 都消费它。

### 3.2 状态层:新增两个字段

文件:`src/state/AppStateStore.ts`

```ts
export type FooterItem =
| 'tasks' | 'tmux' | 'bagel' | 'teams' | 'bridge' | 'companion'
| 'bg_agent' // ← 新增

export type AppState = DeepImmutable<{
// ...
selectedBgAgentIndex: number // -1 = main, 0..N-1 = 选中的 agent
}>
```

- `'bg_agent'` 作为 `FooterItem` 加入 footer pill 体系,享受既有的 `footer:up` / `footer:down` / `footer:openSelected` keybinding 路由
- `selectedBgAgentIndex` 记录 selector 的光标位置,与 `viewingAgentTaskId`("正在看什么")独立;它不可从 `viewingAgentTaskId` 派生——Enter 后光标留在 pill 继续导航,查看目标才变

### 3.3 键盘路由:PromptInput footer pill 分支

文件:`src/components/PromptInput/PromptInput.tsx`

1. **`bg_agent` 进入 footerItems[0]**:保证 prompt ↓ 溢出时(`handleHistoryDown` → `selectFooterItem(footerItems[0])`)直接进入 selector,而不是 `tasks` 等其他 pill
2. **`footer:up` 分支**:`bgAgentSelected` 时 `selectedBgAgentIndex > -1` 则递减;在 -1 → `selectFooterItem(null)` 退出 pill
3. **`footer:down` 分支**:`selectedBgAgentIndex < bgAgentList.length - 1` 则递增,到底 clamp
4. **`footer:openSelected` 分支**:index === -1 → `exitTeammateView`;否则 `enterTeammateView(bgAgentList[i].agentId)`。**不清理 pill 焦点**,光标留在 selector 上继续导航
5. **`selectFooterItem('bg_agent')`**:入 pill 时重置 `selectedBgAgentIndex = -1`(光标落到 `main`)

### 3.4 渲染层:`BackgroundAgentSelector`

文件:`src/components/tasks/BackgroundAgentSelector.tsx`

纯展示组件,不订阅键盘:

```tsx
const tasks = useBackgroundAgentTasks()
const viewingId = useAppState(s => s.viewingAgentTaskId)
const footerSelection = useAppState(s => s.footerSelection)
const selectedBgIndex = useAppState(s => s.selectedBgAgentIndex)

if (tasks.length === 0) return null

const pillFocused = footerSelection === 'bg_agent'
const highlightedId = pillFocused
? (selectedBgIndex === -1 ? null : tasks[selectedBgIndex]?.agentId ?? null)
: (viewingId ?? null)
```

**高亮派生规则**:pill 聚焦 → 跟 `selectedBgAgentIndex`;未聚焦 → 镜像 `viewingAgentTaskId`。这样当用户通过 Shift+↓ Dialog 或 `enterTeammateView` 其它途径切换视图时,selector 也会正确反映。

### 3.5 主视图切换:复用 `viewingAgentTaskId`

REPL.tsx 主体仍复用原有查看逻辑:

```ts
const viewedTask = viewingAgentTaskId ? tasks[viewingAgentTaskId] : undefined
const viewedAgentTask = ... (isLocalAgentTask(viewedTask) ? viewedTask : undefined)
const displayedMessages = viewedAgentTask ? displayedAgentMessages : messages
```

当 `enterTeammateView(agentId)` 把 `viewingAgentTaskId` 设成某个 local_agent 的 id:

- `viewedAgentTask` 解析成该 agent
- `displayedMessages` 切换到 agent 的 messages
- 消息列表、spinner、unseen divider 等一整套组件自动用 agent transcript 重渲染
- 主对话流被"暂停"(并非销毁,回到 `main` 时仍在原处)

`enterTeammateView` 同步负责:设 `retain: true` 阻止 eviction、清 `evictAfter`、触发 disk bootstrap 从 `agent-<id>.jsonl` 加载完整 transcript 到 `task.messages`。

#### Fork agent prompt 归一化

`/fork` agent 的 transcript 和普通 subagent 不同:它继承 main agent 的上下文,真实初始消息形态是:

```text
...parent messages
assistant([...tool_use])
user([tool_result..., text("<fork-boilerplate>...Your directive: <prompt>")])
...fork live messages
```

这里的 prompt 文本混在 `[tool_result..., text]` 多 block user message 里。消息渲染管线会优先把这条 user message 当作 tool-result plumbing 来处理,导致 `<fork-boilerplate>` 里的用户 prompt 不稳定可见。为保证切换到 fork agent 时总能看到用户发起的 fork prompt,REPL.tsx 对 fork 视图做一次展示层归一化:

1. 仅当 `viewedAgentTask.agentType === 'fork'` 时启用,不影响普通 Explore / Task subagent。
2. 从原始 messages 中识别包含 `<fork-boilerplate>` 的 carrier message。
3. 剥离 carrier message 里的 boilerplate text block,但保留 `tool_result` blocks,避免破坏父 assistant `tool_use` 的承接关系。
4. 强制插入一条独立 `createUserMessage({ content: viewedAgentTask.prompt })` 作为可见用户 prompt。
5. 插入位置优先为 boilerplate carrier 后;如果 sidechain bootstrap 还没读到 carrier,则插到最后一条 inherited `assistant tool_use` 后面,确保 prompt 接在 main 上下文之后,而不是跑到视图顶部。

这个归一化只影响 UI 展示用的 `displayedAgentMessages`,不回写 `task.messages`,也不改变发送给模型的 fork transcript。

### 3.6 生命周期

完全复用官方既有机制:

- **运行中**:`isBackgroundTask()` 谓词为真,selector 列出
- **完成 / 失败 / 中止**:`completeAgentTask` / `failAgentTask` / `killAsyncAgent` 设 `status` 为 terminal
- **回访后退出**:`exitTeammateView` 调 `release(task)`——清 `retain`、清 `messages`、terminal 状态下设 `evictAfter = now + PANEL_GRACE_MS (30s)`
- **evictAfter 过期**:`useBackgroundAgentTasks` 过滤时自然剔除,selector 行消失
- **手动清除**:`stopOrDismissAgent(taskId)` 设 `evictAfter = 0`,立即消失

## 四、设计决策

1. **数据源单一**:`useBackgroundAgentTasks` 是唯一过滤点,PromptInput 也复用,避免过滤条件散落
2. **pill 聚焦保留**:Enter 切视图后不松焦,让 ↑↓ 连续导航,贴近官方体验
3. **`bg_agent` 放 footerItems[0]**:确保 ↓ 溢出直接进入 selector 而非其它 pill
4. **selector 不订阅键盘**:所有按键路由集中在 PromptInput 的 `footer:*` 分支,避免 selector 组件和 PromptInput 双重 `useInput` 的冲突
5. **`selectedBgAgentIndex` 存 AppState 而非局部 state**:selector 和 PromptInput 分别在两棵不同子树,需要全局字段协调;该值不能从 `viewingAgentTaskId` 派生
6. **与 `BackgroundTasksDialog` 共存**:Shift+↓ 行为完全不变,selector 是补充快捷入口;Dialog 仍管 shell / workflow / monitor_mcp 等 selector 不显示的 task 类型
7. **fork prompt 展示层兜底**:fork prompt 不依赖 boilerplate 自身渲染,统一在 `displayedAgentMessages` 中合成独立用户消息;普通 subagent 不走该分支,避免 prompt 重复

## 五、关键 API 复用

| 官方已有能力 | selector 如何使用 |
|---|---|
| `AppState.tasks` | 单一数据源,无需 file watcher / output JSONL 订阅 |
| `registerAsyncAgent` | `/fork` 和 AgentTool 共用,selector 不区分来源 |
| `enterTeammateView(id)` | Enter 时调用,负责 retain + disk bootstrap |
| `exitTeammateView` | Enter 选中 `main` 时调用 |
| `release(task)` + `PANEL_GRACE_MS` | 30s keep-alive,selector 自动生效 |
| `useElapsedTime` | 每行时长显示,非 running 自动停 interval |
| `formatTokens` (`utils/format.ts`) | token 数 1k 缩写 |
| `footer:up` / `footer:down` / `footer:openSelected` keybinding | 键盘路由复用 Footer context |

## 六、文件索引

| 文件 | 职责 |
|------|------|
| `src/hooks/useBackgroundAgentTasks.ts` | 数据过滤 hook(backgrounded local_agent + evictAfter 过滤 + startTime 排序) |
| `src/components/tasks/BackgroundAgentSelector.tsx` | 底部 selector UI,纯展示 |
| `src/components/PromptInput/PromptInput.tsx` | 新增 `'bg_agent'` footer pill + 对应的 `footer:up/down/openSelected` 分支 |
| `src/state/AppStateStore.ts` | `FooterItem` 加 `'bg_agent'`;新增 `selectedBgAgentIndex` 字段 |
| `src/main.tsx` | `getDefaultAppState` 同步初始化 `selectedBgAgentIndex: -1` |
| `src/screens/REPL.tsx` | 在 PromptInput + SessionBackgroundHint 之后挂载 `<BackgroundAgentSelector />`;切换 agent 主视图;对 fork transcript 做 prompt 归一化 |
| `src/components/messages/AssistantToolUseMessage.tsx` | 新增 `defaultCollapsed?: boolean` prop,为后续详情视图默认折叠工具块预留 |
| `src/components/messages/UserTextMessage.tsx` | 识别 `<fork-boilerplate>`,交给 fork 专用 renderer 处理 |
| `src/components/messages/UserForkBoilerplateMessage.tsx` | 将 fork boilerplate text 折叠为纯用户 prompt;作为 transcript 中原位渲染的兼容路径 |

## 七、已知限制

- `Date.now()` 在 `useBackgroundAgentTasks` 的 useMemo 里冻结于 `[tasks]` 触发时:若长时间没有新 task 变更事件,某个 terminal agent 的 grace 期过期后不会立即从 selector 消失,要等下一次 tasks 变化才刷新。在典型使用(主对话一直在产生消息)下感知不到,暂不额外加 interval。
- Selector 当前不处理 Shell Task / Workflow / Monitor MCP 等类型——这些仍走 `BackgroundTasksDialog`(Shift+↓)管理。
- `AssistantToolUseMessage` 的 `defaultCollapsed` prop 目前无调用方传值,保留作为后续"agent 详情视图内工具块默认折叠"扩展点。
6 changes: 5 additions & 1 deletion src/commands/fork/fork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export async function call(
// Omitting subagent_type triggers implicit fork.
const input = {
prompt: directive,
fork: true, // 触发 AgentTool 的 fork 路径:继承父会话上下文 + system prompt + 模型
run_in_background: true, // fork always runs async
description: `Fork: ${directive.slice(0, 30)}${directive.length > 30 ? '...' : ''}`,
// description 只显示在底部 selector / BackgroundTasksDialog,保持简短标签
// 即可;用户输入的 prompt 会作为第一条用户消息呈现在主视图里,这里不要
// 重复显示。
description: 'forked from main',
};

// Call AgentTool with proper parameters:
Expand Down
44 changes: 44 additions & 0 deletions src/components/PromptInput/PromptInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useSetPromptOverlayDialog } from '../../context/promptOverlayContext.js
import { formatImageRef, formatPastedTextRef, getPastedTextRefNumLines, parseReferences } from '../../history.js';
import type { VerificationStatus } from '../../hooks/useApiKeyVerification.js';
import { type HistoryMode, useArrowKeyHistory } from '../../hooks/useArrowKeyHistory.js';
import { useBackgroundAgentTasks } from '../../hooks/useBackgroundAgentTasks.js';
import { useDoublePress } from '../../hooks/useDoublePress.js';
import { useHistorySearch } from '../../hooks/useHistorySearch.js';
import type { IDESelection } from '../../hooks/useIdeSelection.js';
Expand Down Expand Up @@ -415,6 +416,16 @@ function PromptInput({
// First ↓ selects the pill, second ↓ moves to row 0. Prevents double-select
// of pill + row when both bg tasks (pill) and forked agents (rows) are visible.
const coordinatorTaskIndex = useAppState(s => s.coordinatorTaskIndex);
const selectedBgAgentIndex = useAppState(s => s.selectedBgAgentIndex);
const setSelectedBgAgentIndex = useCallback(
(v: number | ((prev: number) => number)) =>
setAppState(prev => {
const next = typeof v === 'function' ? v(prev.selectedBgAgentIndex) : v;
if (next === prev.selectedBgAgentIndex) return prev;
return { ...prev, selectedBgAgentIndex: next };
}),
[setAppState],
);
const setCoordinatorTaskIndex = useCallback(
(v: number | ((prev: number) => number)) =>
setAppState(prev => {
Expand Down Expand Up @@ -501,10 +512,13 @@ function PromptInput({
(runningTaskCount > 0 || (process.env.USER_TYPE === 'ant' && coordinatorTaskCount > 0)) &&
!shouldHideTasksFooter(tasks, showSpinnerTree);
const teamsFooterVisible = cachedTeams.length > 0;
const bgAgentList = useBackgroundAgentTasks();
const bgAgentFooterVisible = bgAgentList.length > 0;

Comment on lines +515 to 517
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp selectedBgAgentIndex when the background-agent list shrinks.

If tasks complete while the pill stays focused, the index can point past the end of bgAgentList, leaving no valid selection and making Enter a no-op until manual recovery.

Suggested change
   const bgAgentList = useBackgroundAgentTasks();
   const bgAgentFooterVisible = bgAgentList.length > 0;
+  useEffect(() => {
+    setSelectedBgAgentIndex(prev => {
+      const max = bgAgentList.length - 1;
+      const clamped = Math.max(-1, Math.min(prev, max));
+      return clamped;
+    });
+  }, [bgAgentList.length, setSelectedBgAgentIndex]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bgAgentList = useBackgroundAgentTasks();
const bgAgentFooterVisible = bgAgentList.length > 0;
const bgAgentList = useBackgroundAgentTasks();
const bgAgentFooterVisible = bgAgentList.length > 0;
useEffect(() => {
setSelectedBgAgentIndex(prev => {
const max = bgAgentList.length - 1;
const clamped = Math.max(-1, Math.min(prev, max));
return clamped;
});
}, [bgAgentList.length, setSelectedBgAgentIndex]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/PromptInput/PromptInput.tsx` around lines 515 - 517, When the
background-agent list (useBackgroundAgentTasks -> bgAgentList) shrinks the
current selectedBgAgentIndex can exceed the new length; add a clamp step
whenever bgAgentList changes to setSelectedBgAgentIndex(prev => Math.max(0,
Math.min(prev, bgAgentList.length - 1))) (or clear to -1 if you use -1 for no
selection) so Enter still targets a valid item. Implement this in an effect that
depends on bgAgentList (or inside the reducer/handler that mutates bgAgentList)
and reference selectedBgAgentIndex, bgAgentList, useBackgroundAgentTasks, and
setSelectedBgAgentIndex to locate where to apply the clamp.

const footerItems = useMemo(
() =>
[
bgAgentFooterVisible && 'bg_agent',
tasksFooterVisible && 'tasks',
tmuxFooterVisible && 'tmux',
bagelFooterVisible && 'bagel',
Expand All @@ -513,6 +527,7 @@ function PromptInput({
companionFooterVisible && 'companion',
].filter(Boolean) as FooterItem[],
[
bgAgentFooterVisible,
tasksFooterVisible,
tmuxFooterVisible,
bagelFooterVisible,
Expand Down Expand Up @@ -540,13 +555,17 @@ function PromptInput({
const _bagelSelected = footerItemSelected === 'bagel';
const teamsSelected = footerItemSelected === 'teams';
const bridgeSelected = footerItemSelected === 'bridge';
const bgAgentSelected = footerItemSelected === 'bg_agent';

function selectFooterItem(item: FooterItem | null): void {
setAppState(prev => (prev.footerSelection === item ? prev : { ...prev, footerSelection: item }));
if (item === 'tasks') {
setTeammateFooterIndex(0);
setCoordinatorTaskIndex(minCoordinatorIndex);
}
if (item === 'bg_agent') {
setSelectedBgAgentIndex(-1);
}
}

// delta: +1 = down/right, -1 = up/left. Returns true if nav happened
Expand Down Expand Up @@ -1808,6 +1827,15 @@ function PromptInput({
useKeybindings(
{
'footer:up': () => {
// ↑ in bg_agent pill: move selection up (-1 = main). At -1, leave pill.
if (bgAgentSelected) {
if (selectedBgAgentIndex > -1) {
setSelectedBgAgentIndex(prev => prev - 1);
} else {
selectFooterItem(null);
}
return;
}
// ↑ scrolls within the coordinator task list before leaving the pill
if (
tasksSelected &&
Expand All @@ -1821,6 +1849,13 @@ function PromptInput({
navigateFooter(-1, true);
},
'footer:down': () => {
// ↓ in bg_agent pill: move selection down through agents. Clamp at last.
if (bgAgentSelected) {
if (selectedBgAgentIndex < bgAgentList.length - 1) {
setSelectedBgAgentIndex(prev => prev + 1);
}
return;
}
// ↓ scrolls within the coordinator task list, never leaves the pill
if (tasksSelected && process.env.USER_TYPE === 'ant' && coordinatorTaskCount > 0) {
if (coordinatorTaskIndex < coordinatorTaskCount - 1) {
Expand Down Expand Up @@ -1906,6 +1941,15 @@ function PromptInput({
setShowBridgeDialog(true);
selectFooterItem(null);
break;
case 'bg_agent':
if (selectedBgAgentIndex === -1) {
exitTeammateView(setAppState);
} else {
const picked = bgAgentList[selectedBgAgentIndex];
if (picked) enterTeammateView(picked.agentId, setAppState);
}
// Keep the pill focused so ↑/↓ continue to work after Enter.
break;
}
},
'footer:clearSelection': () => {
Expand Down
3 changes: 3 additions & 0 deletions src/components/messages/AssistantToolUseMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Props = {
inProgressToolCallCount?: number;
lookups: ReturnType<typeof buildMessageLookups>;
isTranscriptMode?: boolean;
defaultCollapsed?: boolean;
};

export function AssistantToolUseMessage({
Expand All @@ -45,6 +46,7 @@ export function AssistantToolUseMessage({
inProgressToolCallCount,
lookups,
isTranscriptMode,
defaultCollapsed,
}: Props): React.ReactNode {
const terminalSize = useTerminalSize();
const [theme] = useTheme();
Expand Down Expand Up @@ -167,6 +169,7 @@ export function AssistantToolUseMessage({
</Box>
{!isResolved &&
!isQueued &&
!defaultCollapsed &&
(isClassifierChecking ? (
<MessageResponse height={1}>
<Text dimColor>
Expand Down
Loading