feat: 参考 claude code 官方实现,改进 sub agent 以及 fork agent 的渲染方式#416
Conversation
当前的sub agent渲染无法看到完整的上下文。这个pr参考claude code官方的效果,修改了sub agent的渲染,选择agent后能看到sub-agent完整的上下文,同时也支持sub agent继续提问。具体功能见docs/features/background-agent-selector.md
📝 WalkthroughWalkthroughAdds a Background Agent Selector UI and state for navigating backgrounded local agent tasks, plus supporting hook, REPL mounting, PromptInput footer integration and fork-prompt message normalization; includes docs and minor message/component tweaks. ChangesBackground Agent Selector Feature
Sequence DiagramsequenceDiagram
actor User
participant PromptInput
participant BackgroundAgentSelector
participant AppState
participant REPL
participant TeammateView
User->>PromptInput: Focus bg_agent footer pill
PromptInput->>AppState: setSelectedBgAgentIndex(index)
AppState->>BackgroundAgentSelector: state change -> recompute highlight
BackgroundAgentSelector-->>User: show highlighted row + hint
User->>PromptInput: Press Enter on bg_agent pill
PromptInput->>AppState: setViewingAgentTaskId(task.id)
AppState->>REPL: viewingAgentTaskId changed
REPL->>REPL: compute displayedAgentMessages (strip boilerplate, insert prompt)
REPL-->>User: render agent view
REPL->>BackgroundAgentSelector: update context (viewing vs pill focused)
BackgroundAgentSelector-->>User: update hint (Esc to exit)
User->>REPL: Press Esc
REPL->>AppState: exitTeammateView()
AppState->>PromptInput: restore/retain pill focus
PromptInput-->>User: bg_agent pill focused
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/messages/AssistantToolUseMessage.tsx (1)
99-114:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
defaultCollapsedis not applied in theisTransparentWrapperbranchThe transparent-wrapper path (lines 99–114) unconditionally calls
renderToolUseProgressMessage, bypassing thedefaultCollapsedflag. REPLTool is a concrete transparent wrapper that returnstrueforisTransparentWrapper(), and if this component is mounted in a collapsed background-agent view, its progress output will still be rendered despitedefaultCollapsed === true.If the intent is to suppress all in-progress UI in the collapsed view, the guard should also be applied here:
Proposed fix
if (isTransparentWrapper) { if (isQueued || isResolved) return null; + if (defaultCollapsed) return null; return ( <Box flexDirection="column" width="100%" backgroundColor={bg}> {renderToolUseProgressMessage(🤖 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/messages/AssistantToolUseMessage.tsx` around lines 99 - 114, The transparent-wrapper branch (isTransparentWrapper) currently always renders progress via renderToolUseProgressMessage and ignores the defaultCollapsed flag, so when defaultCollapsed is true (collapsed background-agent view) progress UI still appears; update the isTransparentWrapper branch in AssistantToolUseMessage to check defaultCollapsed (and/or collapsedView equivalent) alongside isQueued/isResolved and return null when defaultCollapsed is true, ensuring renderToolUseProgressMessage (and its args like tool, tools, lookups, param.id, progressMessagesForMessage, { verbose, inProgressToolCallCount, isTranscriptMode }, terminalSize) is only called when not collapsed.
🧹 Nitpick comments (4)
src/hooks/useBackgroundAgentTasks.ts (1)
2-6: ⚡ Quick winUse
src/*path aliases for imports in TS files.The new relative imports should be switched to
src/...aliases for consistency with repository rules.As per coding guidelines, "Use `src/*` path alias in imports; do not use relative paths like `../../../`".♻️ Suggested import update
-import { useAppState } from '../state/AppState.js' +import { useAppState } from 'src/state/AppState.js' import { isLocalAgentTask, type LocalAgentTaskState, -} from '../tasks/LocalAgentTask/LocalAgentTask.js' +} from 'src/tasks/LocalAgentTask/LocalAgentTask.js'🤖 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/hooks/useBackgroundAgentTasks.ts` around lines 2 - 6, Replace the relative import paths with the repository's src/* path aliases: update the import of useAppState to import from "src/state/AppState" and update the imports of isLocalAgentTask and LocalAgentTaskState to import from "src/tasks/LocalAgentTask/LocalAgentTask"; keep the same exported symbols (useAppState, isLocalAgentTask, LocalAgentTaskState) and file extensions consistent with the codebase.src/components/PromptInput/PromptInput.tsx (1)
29-29: ⚡ Quick winUse
src/*alias for the new hook import.This new import should follow the repo import alias convention to avoid adding new relative-path debt.
Suggested change
-import { useBackgroundAgentTasks } from '../../hooks/useBackgroundAgentTasks.js'; +import { useBackgroundAgentTasks } from 'src/hooks/useBackgroundAgentTasks.js';As per coding guidelines, "Use
src/*path alias in imports; do not use relative paths like../../../."🤖 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` at line 29, The import in PromptInput.tsx uses a relative path for the new hook; change the import of useBackgroundAgentTasks to use the repo alias (src/*) instead of '../../hooks/...'. Locate the import statement that currently reads import { useBackgroundAgentTasks } from '../../hooks/useBackgroundAgentTasks.js' and update it to import from 'src/hooks/useBackgroundAgentTasks' (remove the relative path and .js extension if project convention omits it).src/components/tasks/BackgroundAgentSelector.tsx (1)
2-6: ⚡ Quick winSwitch new local imports to
src/*aliases.These newly added relative imports should use the project alias style.
Suggested change
-import { useBackgroundAgentTasks } from '../../hooks/useBackgroundAgentTasks.js'; -import { useElapsedTime } from '../../hooks/useElapsedTime.js'; -import { useAppState } from '../../state/AppState.js'; -import type { LocalAgentTaskState } from '../../tasks/LocalAgentTask/LocalAgentTask.js'; -import { formatTokens } from '../../utils/format.js'; +import { useBackgroundAgentTasks } from 'src/hooks/useBackgroundAgentTasks.js'; +import { useElapsedTime } from 'src/hooks/useElapsedTime.js'; +import { useAppState } from 'src/state/AppState.js'; +import type { LocalAgentTaskState } from 'src/tasks/LocalAgentTask/LocalAgentTask.js'; +import { formatTokens } from 'src/utils/format.js';As per coding guidelines, "Use
src/*path alias in imports; do not use relative paths like../../../."🤖 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/tasks/BackgroundAgentSelector.tsx` around lines 2 - 6, Update the new relative imports in BackgroundAgentSelector.tsx to use the project path-alias style (src/*) instead of relative paths: replace the imports for useBackgroundAgentTasks, useElapsedTime, useAppState, the LocalAgentTaskState type, and formatTokens so they import from their src/* alias locations; ensure the imported symbol names (useBackgroundAgentTasks, useElapsedTime, useAppState, LocalAgentTaskState, formatTokens) remain unchanged so existing references in the file keep working.src/screens/REPL.tsx (1)
247-248: ⚡ Quick winUse
src/*aliases for the new internal imports.These additions reintroduce relative paths in newly touched code. Please switch the new imports to the
src/*alias form instead of adding more../…references.Suggested cleanup
-import { BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, LOCAL_COMMAND_STDOUT_TAG } from '../constants/xml.js'; +import { BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, LOCAL_COMMAND_STDOUT_TAG } from 'src/constants/xml.js'; ... -import { BackgroundAgentSelector } from '../components/tasks/BackgroundAgentSelector.js'; +import { BackgroundAgentSelector } from 'src/components/tasks/BackgroundAgentSelector.js';As per coding guidelines, "Use
src/*path alias in imports; do not use relative paths like../../../."Also applies to: 340-340
🤖 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/screens/REPL.tsx` around lines 247 - 248, Replace relative/new-package-style imports with the project alias form: update the import that brings in BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, LOCAL_COMMAND_STDOUT_TAG (currently from '../constants/xml.js') to use the src/* alias (e.g., import from 'src/constants/xml'), and change the FORK_SUBAGENT_TYPE import (currently from '@claude-code-best/builtin-tools/tools/AgentTool/forkSubagent.js') to the corresponding src alias path (e.g., 'src/builtin-tools/tools/AgentTool/forkSubagent'); keep the same named symbols (BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, LOCAL_COMMAND_STDOUT_TAG, FORK_SUBAGENT_TYPE) so only the module paths are replaced.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@docs/features/background-agent-selector.md`:
- Around line 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.
In `@src/components/PromptInput/PromptInput.tsx`:
- Around line 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.
In `@src/components/tasks/BackgroundAgentSelector.tsx`:
- Line 35: The component BackgroundAgentSelector currently uses the type
React.ReactNode without importing React and uses relative imports; fix by
importing the React type (e.g., import type { ReactNode } from 'react' and
change the signature to return ReactNode, or import * as React) and replace all
relative module paths with the project's src aliases for the referenced
symbols—update imports that bring in useBackgroundAgentTasks, useAppState,
LocalAgentTaskState and any other same-folder imports to use 'src/...' paths
(e.g., import { useBackgroundAgentTasks } from
'src/hooks/useBackgroundAgentTasks.js', import { useAppState } from
'src/state/AppState.js', import type { LocalAgentTaskState } from
'src/tasks/LocalAgentTask/LocalAgentTask.js').
In `@src/hooks/useBackgroundAgentTasks.ts`:
- Around line 10-18: The memoized list of background tasks uses a captured
Date.now() ('now' in useBackgroundAgentTasks) but only depends on 'tasks', so
expirations (evictAfter) won't re-evaluate; update the hook to make the memo
also depend on a time tick (e.g., a small interval-based state like 'now' or
useNow hook) or include a computed time bucket in the dependency array, then use
that 'now' when filtering by evictAfter so tasks are recomputed when they expire
(reference useBackgroundAgentTasks, the 'now' variable, and the 'evictAfter'
filter).
In `@src/screens/REPL.tsx`:
- Around line 5591-5600: The duplicate-prompt check only inspects the first text
block; change it so promptAlreadyRendered is set if any text block in a user
message equals trimmedPrompt (not just the first), and also handle messages
whose content is a plain string by comparing m.message.content ===
trimmedPrompt. Update the branch that uses Array.isArray(m.message?.content) to
use Array.prototype.find (or some) to locate any block where b.type === 'text'
&& typeof b.text === 'string' && b.text.trim() === trimmedPrompt, and in the
non-array path check the raw string content; keep existing references to
isForkBoilerplateTextBlock, boilerplateIndex, and promptAlreadyRendered
unchanged.
---
Outside diff comments:
In `@src/components/messages/AssistantToolUseMessage.tsx`:
- Around line 99-114: The transparent-wrapper branch (isTransparentWrapper)
currently always renders progress via renderToolUseProgressMessage and ignores
the defaultCollapsed flag, so when defaultCollapsed is true (collapsed
background-agent view) progress UI still appears; update the
isTransparentWrapper branch in AssistantToolUseMessage to check defaultCollapsed
(and/or collapsedView equivalent) alongside isQueued/isResolved and return null
when defaultCollapsed is true, ensuring renderToolUseProgressMessage (and its
args like tool, tools, lookups, param.id, progressMessagesForMessage, { verbose,
inProgressToolCallCount, isTranscriptMode }, terminalSize) is only called when
not collapsed.
---
Nitpick comments:
In `@src/components/PromptInput/PromptInput.tsx`:
- Line 29: The import in PromptInput.tsx uses a relative path for the new hook;
change the import of useBackgroundAgentTasks to use the repo alias (src/*)
instead of '../../hooks/...'. Locate the import statement that currently reads
import { useBackgroundAgentTasks } from '../../hooks/useBackgroundAgentTasks.js'
and update it to import from 'src/hooks/useBackgroundAgentTasks' (remove the
relative path and .js extension if project convention omits it).
In `@src/components/tasks/BackgroundAgentSelector.tsx`:
- Around line 2-6: Update the new relative imports in
BackgroundAgentSelector.tsx to use the project path-alias style (src/*) instead
of relative paths: replace the imports for useBackgroundAgentTasks,
useElapsedTime, useAppState, the LocalAgentTaskState type, and formatTokens so
they import from their src/* alias locations; ensure the imported symbol names
(useBackgroundAgentTasks, useElapsedTime, useAppState, LocalAgentTaskState,
formatTokens) remain unchanged so existing references in the file keep working.
In `@src/hooks/useBackgroundAgentTasks.ts`:
- Around line 2-6: Replace the relative import paths with the repository's src/*
path aliases: update the import of useAppState to import from
"src/state/AppState" and update the imports of isLocalAgentTask and
LocalAgentTaskState to import from "src/tasks/LocalAgentTask/LocalAgentTask";
keep the same exported symbols (useAppState, isLocalAgentTask,
LocalAgentTaskState) and file extensions consistent with the codebase.
In `@src/screens/REPL.tsx`:
- Around line 247-248: Replace relative/new-package-style imports with the
project alias form: update the import that brings in BASH_INPUT_TAG,
COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG,
LOCAL_COMMAND_STDOUT_TAG (currently from '../constants/xml.js') to use the src/*
alias (e.g., import from 'src/constants/xml'), and change the FORK_SUBAGENT_TYPE
import (currently from
'@claude-code-best/builtin-tools/tools/AgentTool/forkSubagent.js') to the
corresponding src alias path (e.g.,
'src/builtin-tools/tools/AgentTool/forkSubagent'); keep the same named symbols
(BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG,
LOCAL_COMMAND_STDOUT_TAG, FORK_SUBAGENT_TYPE) so only the module paths are
replaced.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 58221a21-a1dd-4a83-9772-60ac83782dcc
📒 Files selected for processing (11)
docs/features/background-agent-selector.mdsrc/commands/fork/fork.tsxsrc/components/PromptInput/PromptInput.tsxsrc/components/messages/AssistantToolUseMessage.tsxsrc/components/messages/UserForkBoilerplateMessage.tsxsrc/components/messages/UserTextMessage.tsxsrc/components/tasks/BackgroundAgentSelector.tsxsrc/hooks/useBackgroundAgentTasks.tssrc/main.tsxsrc/screens/REPL.tsxsrc/state/AppStateStore.ts
| ``` | ||
| 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 | ||
| ``` |
There was a problem hiding this comment.
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.
| ``` | |
| 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.
| const bgAgentList = useBackgroundAgentTasks(); | ||
| const bgAgentFooterVisible = bgAgentList.length > 0; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| return viewedTask.status === 'running' ? 'shift+↓ to manage · x to stop' : 'shift+↓ to manage · x to clear'; | ||
| } | ||
|
|
||
| export function BackgroundAgentSelector(): React.ReactNode { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether this file relies on React namespace without import,
# and whether tsconfig permits UMD globals.
set -euo pipefail
echo "=== File evidence ==="
sed -n '1,80p' src/components/tasks/BackgroundAgentSelector.tsx | nl -ba | sed -n '1,60p'
echo
echo "=== React namespace usage ==="
rg -n '\bReact\.ReactNode\b' src/components/tasks/BackgroundAgentSelector.tsx
echo
echo "=== React import in file ==="
rg -n "from 'react'|from \"react\"" src/components/tasks/BackgroundAgentSelector.tsx || true
echo
echo "=== allowUmdGlobalAccess in tsconfig files ==="
fd -i '^tsconfig.*\.json$' -x sh -c 'echo "--- $1"; cat "$1" | jq ".compilerOptions.allowUmdGlobalAccess // \"<unset>\""' sh {}Repository: claude-code-best/claude-code
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== File content (BackgroundAgentSelector.tsx) ==="
cat -n src/components/tasks/BackgroundAgentSelector.tsx | head -80
echo ""
echo "=== React.ReactNode usage ==="
rg -n 'React\.ReactNode' src/components/tasks/BackgroundAgentSelector.tsx || echo "No match found"
echo ""
echo "=== React imports in file ==="
rg -n "from ['\"]react['\"]" src/components/tasks/BackgroundAgentSelector.tsx || echo "No React import found"
echo ""
echo "=== Check tsconfig.json for strict and module settings ==="
cat -n tsconfig.json | head -30Repository: claude-code-best/claude-code
Length of output: 4716
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Full tsconfig.json ==="
cat tsconfig.json
echo ""
echo "=== Run TypeScript strict check on this file ==="
bunx tsc --noEmit src/components/tasks/BackgroundAgentSelector.tsx 2>&1 || true
echo ""
echo "=== Check other React component files for pattern ==="
rg -l 'React\.ReactNode' src/components/ | head -5
echo ""
echo "=== Check if any component files import React ==="
rg "import.*from ['\"]react['\"]" src/components/ | head -3 || echo "No React imports found in components"Repository: claude-code-best/claude-code
Length of output: 1894
React.ReactNode requires React import; also replace relative imports with src/* aliases.
Line 35 uses React.ReactNode without importing React. With strict: true and no allowUmdGlobalAccess in tsconfig, this fails TypeScript compilation. Import React explicitly: import type { ReactNode } from 'react' and return ReactNode, or use import * as React from 'react'.
Additionally, lines 2–6 use relative paths (../../hooks, ../../state, etc.) instead of the required src/* path aliases. Update imports to:
import { useBackgroundAgentTasks } from 'src/hooks/useBackgroundAgentTasks.js'import { useAppState } from 'src/state/AppState.js'import type { LocalAgentTaskState } from 'src/tasks/LocalAgentTask/LocalAgentTask.js'- (and similar for remaining imports)
🤖 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/tasks/BackgroundAgentSelector.tsx` at line 35, The component
BackgroundAgentSelector currently uses the type React.ReactNode without
importing React and uses relative imports; fix by importing the React type
(e.g., import type { ReactNode } from 'react' and change the signature to return
ReactNode, or import * as React) and replace all relative module paths with the
project's src aliases for the referenced symbols—update imports that bring in
useBackgroundAgentTasks, useAppState, LocalAgentTaskState and any other
same-folder imports to use 'src/...' paths (e.g., import {
useBackgroundAgentTasks } from 'src/hooks/useBackgroundAgentTasks.js', import {
useAppState } from 'src/state/AppState.js', import type { LocalAgentTaskState }
from 'src/tasks/LocalAgentTask/LocalAgentTask.js').
| 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]) |
There was a problem hiding this comment.
Expired tasks can remain visible until tasks mutates.
Line 11 computes Date.now() inside a useMemo that only depends on tasks (Line 18), so evictAfter expiry won’t trigger recompute on its own.
💡 Suggested fix
-import { useMemo } from 'react'
+import { useEffect, useMemo, useState } from 'react'
@@
export function useBackgroundAgentTasks(): LocalAgentTaskState[] {
const tasks = useAppState(s => s.tasks)
+ const [now, setNow] = useState(() => Date.now())
+
+ useEffect(() => {
+ const timer = setInterval(() => setNow(Date.now()), 1000)
+ return () => clearInterval(timer)
+ }, [])
+
return useMemo(() => {
- const now = Date.now()
return Object.values(tasks)
@@
- }, [tasks])
+ }, [tasks, now])
}🤖 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/hooks/useBackgroundAgentTasks.ts` around lines 10 - 18, The memoized list
of background tasks uses a captured Date.now() ('now' in
useBackgroundAgentTasks) but only depends on 'tasks', so expirations
(evictAfter) won't re-evaluate; update the hook to make the memo also depend on
a time tick (e.g., a small interval-based state like 'now' or useNow hook) or
include a computed time bucket in the dependency array, then use that 'now' when
filtering by evictAfter so tasks are recomputed when they expire (reference
useBackgroundAgentTasks, the 'now' variable, and the 'evictAfter' filter).
| if (m.type === 'user' && Array.isArray(m.message?.content)) { | ||
| const hasBoilerplate = m.message.content.some(isForkBoilerplateTextBlock); | ||
| if (hasBoilerplate) { | ||
| boilerplateIndex = i; | ||
| } else if (!promptAlreadyRendered) { | ||
| const firstText = m.message.content.find(b => b.type === 'text' && typeof b.text === 'string') as | ||
| | { type: 'text'; text: string } | ||
| | undefined; | ||
| if (firstText && firstText.text.trim() === trimmedPrompt) promptAlreadyRendered = true; | ||
| } |
There was a problem hiding this comment.
Broaden the duplicate-prompt check before inserting the synthetic fork bubble.
Line 5596 only looks at the first text block on array-backed user messages. If a retained fork transcript already stores the prompt as plain string content, or in a later text block, this path still inserts the synthetic bubble and the selector shows the prompt twice.
Suggested fix
+function userMessageContainsPrompt(message: MessageType, prompt: string): boolean {
+ if (message.type !== 'user') return false;
+ const content = message.message?.content;
+ if (typeof content === 'string') return content.trim() === prompt;
+ if (!Array.isArray(content)) return false;
+ return content.some(block => block.type === 'text' && typeof block.text === 'string' && block.text.trim() === prompt);
+}
+
...
- } else if (!promptAlreadyRendered) {
- const firstText = m.message.content.find(b => b.type === 'text' && typeof b.text === 'string') as
- | { type: 'text'; text: string }
- | undefined;
- if (firstText && firstText.text.trim() === trimmedPrompt) promptAlreadyRendered = true;
+ } else if (!promptAlreadyRendered && userMessageContainsPrompt(m, trimmedPrompt)) {
+ promptAlreadyRendered = true;
}📝 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.
| if (m.type === 'user' && Array.isArray(m.message?.content)) { | |
| const hasBoilerplate = m.message.content.some(isForkBoilerplateTextBlock); | |
| if (hasBoilerplate) { | |
| boilerplateIndex = i; | |
| } else if (!promptAlreadyRendered) { | |
| const firstText = m.message.content.find(b => b.type === 'text' && typeof b.text === 'string') as | |
| | { type: 'text'; text: string } | |
| | undefined; | |
| if (firstText && firstText.text.trim() === trimmedPrompt) promptAlreadyRendered = true; | |
| } | |
| function userMessageContainsPrompt(message: MessageType, prompt: string): boolean { | |
| if (message.type !== 'user') return false; | |
| const content = message.message?.content; | |
| if (typeof content === 'string') return content.trim() === prompt; | |
| if (!Array.isArray(content)) return false; | |
| return content.some(block => block.type === 'text' && typeof block.text === 'string' && block.text.trim() === prompt); | |
| } | |
| if (m.type === 'user' && Array.isArray(m.message?.content)) { | |
| const hasBoilerplate = m.message.content.some(isForkBoilerplateTextBlock); | |
| if (hasBoilerplate) { | |
| boilerplateIndex = i; | |
| } else if (!promptAlreadyRendered && userMessageContainsPrompt(m, trimmedPrompt)) { | |
| promptAlreadyRendered = true; | |
| } |
🤖 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/screens/REPL.tsx` around lines 5591 - 5600, The duplicate-prompt check
only inspects the first text block; change it so promptAlreadyRendered is set if
any text block in a user message equals trimmedPrompt (not just the first), and
also handle messages whose content is a plain string by comparing
m.message.content === trimmedPrompt. Update the branch that uses
Array.isArray(m.message?.content) to use Array.prototype.find (or some) to
locate any block where b.type === 'text' && typeof b.text === 'string' &&
b.text.trim() === trimmedPrompt, and in the non-array path check the raw string
content; keep existing references to isForkBoilerplateTextBlock,
boilerplateIndex, and promptAlreadyRendered unchanged.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/screens/REPL.tsx (2)
247-253: ⚡ Quick winUse
src/*alias for the newly added imports.The newly introduced imports in this patch are relative-path imports.
Suggested change
import { BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, LOCAL_COMMAND_STDOUT_TAG, -} from '../constants/xml.js'; +} from 'src/constants/xml.js'; ... -import { BackgroundAgentSelector } from '../components/tasks/BackgroundAgentSelector.js'; +import { BackgroundAgentSelector } from 'src/components/tasks/BackgroundAgentSelector.js';As per coding guidelines, "Use
src/*path alias in imports; do not use relative paths like../../../".Also applies to: 346-346
🤖 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/screens/REPL.tsx` around lines 247 - 253, Replace the relative-path imports in REPL.tsx with the project path alias by importing BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, and LOCAL_COMMAND_STDOUT_TAG from "src/constants/xml" instead of the relative '../constants/xml.js' (and update the other occurrence where the same module is imported around the later usage of these tags); ensure any import extensions (.js) match the project's alias conventions.
5602-5605: ⚡ Quick winReplace the forced type cast with a type predicate for better type safety.
The
.find()call on line 5602 uses a forcedasassertion that bypasses TypeScript's type narrowing. Since the predicate properly narrows{ type: string; [key: string]: unknown }to{ type: 'text'; text: string }, use a type predicate instead:Suggested change
- const firstText = m.message.content.find(b => b.type === 'text' && typeof b.text === 'string') as - | { type: 'text'; text: string } - | undefined; + const firstText = m.message.content.find( + (b): b is { type: 'text'; text: string } => b.type === 'text' && typeof b.text === 'string', + ); if (firstText && firstText.text.trim() === trimmedPrompt) promptAlreadyRendered = true;🤖 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/screens/REPL.tsx` around lines 5602 - 5605, The current .find() call assigns to firstText using a forced cast which bypasses TypeScript narrowing; replace that cast by using a type predicate function (e.g., isTextBlock) that asserts b is { type: 'text'; text: string } and pass that predicate to m.message.content.find; then keep the existing check if (firstText && firstText.text.trim() === trimmedPrompt) promptAlreadyRendered = true so TypeScript knows firstText.text is a string without the unsafe as cast (referencing firstText, m.message.content, promptAlreadyRendered, trimmedPrompt).
🤖 Prompt for all review comments with 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.
Nitpick comments:
In `@src/screens/REPL.tsx`:
- Around line 247-253: Replace the relative-path imports in REPL.tsx with the
project path alias by importing BASH_INPUT_TAG, COMMAND_MESSAGE_TAG,
COMMAND_NAME_TAG, FORK_BOILERPLATE_TAG, and LOCAL_COMMAND_STDOUT_TAG from
"src/constants/xml" instead of the relative '../constants/xml.js' (and update
the other occurrence where the same module is imported around the later usage of
these tags); ensure any import extensions (.js) match the project's alias
conventions.
- Around line 5602-5605: The current .find() call assigns to firstText using a
forced cast which bypasses TypeScript narrowing; replace that cast by using a
type predicate function (e.g., isTextBlock) that asserts b is { type: 'text';
text: string } and pass that predicate to m.message.content.find; then keep the
existing check if (firstText && firstText.text.trim() === trimmedPrompt)
promptAlreadyRendered = true so TypeScript knows firstText.text is a string
without the unsafe as cast (referencing firstText, m.message.content,
promptAlreadyRendered, trimmedPrompt).


Summary
参考 Claude Code 官方实现,改进 sub-agent 和 fork agent 的渲染方式。
当前版本 sub-agent 渲染只显示概要,无法查看完整上下文。本 PR 改造后:
详细功能说明见新增的
docs/features/background-agent-selector.md。主要改动
BackgroundAgentSelector组件,承载 agent 列表与详情切换useBackgroundAgentTaskshook,集中管理后台 agent 任务状态REPL.tsx/PromptInput.tsx集成选择器与继续提问入口UserForkBoilerplateMessage/UserTextMessage/AssistantToolUseMessage渲染适配AppStateStore增加对应状态字段docs/features/background-agent-selector.md改动统计:11 files changed, +482 / -27
Test plan
bun run precheck通过(typecheck + lint + test 零错误)Need help on this PR? Tag
@codesmithwith what you need.Summary by CodeRabbit
New Features
Bug Fixes
Documentation