@@ -30,6 +30,7 @@ import { findExpandedThinkingId } from "./thinkingState";
3030import { WelcomeScreen } from "./WelcomeScreen" ;
3131import { AskUserQuestionPrompt } from "./AskUserQuestionPrompt" ;
3232import { McpStatusList } from "./McpStatusList" ;
33+ import { ProcessStdoutView } from "./ProcessStdoutView" ;
3334import {
3435 findPendingAskUserQuestion ,
3536 formatAskUserQuestionAnswers ,
@@ -69,6 +70,8 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
6970 const [ resolvedSettings , setResolvedSettings ] = useState ( ( ) => resolveCurrentSettings ( projectRoot ) ) ;
7071 const [ nowTick , setNowTick ] = useState ( 0 ) ;
7172 const [ mcpStatuses , setMcpStatuses ] = useState < ReturnType < typeof sessionManager . getMcpStatus > > ( [ ] ) ;
73+ const [ showProcessStdout , setShowProcessStdout ] = useState ( false ) ;
74+ const processStdoutRef = useRef < Map < number , string > > ( new Map ( ) ) ;
7275
7376 const messagesRef = useRef < SessionMessage [ ] > ( [ ] ) ;
7477 messagesRef . current = messages ;
@@ -98,6 +101,19 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
98101 // 当 MCP 状态变更时,如果当前正在查看 MCP 状态页面,则更新显示
99102 setMcpStatuses ( sessionManager . getMcpStatus ( ) ) ;
100103 } ,
104+ onProcessStdout : ( pid , chunk ) => {
105+ const buf = processStdoutRef . current ;
106+ const current = buf . get ( pid ) ?? "" ;
107+ // Cap at 1 MB per process to avoid unbounded memory growth
108+ // on noisy or long-running commands like `yes` or verbose builds.
109+ const MAX_STDOUT_BUFFER = 1_000_000 ;
110+ if ( current . length >= MAX_STDOUT_BUFFER ) {
111+ return ;
112+ }
113+ const text = typeof chunk === "string" ? chunk : String ( chunk ) ;
114+ const available = MAX_STDOUT_BUFFER - current . length ;
115+ buf . set ( pid , current + text . slice ( 0 , available ) ) ;
116+ } ,
101117 } ) ;
102118 } , [ projectRoot ] ) ;
103119
@@ -218,6 +234,8 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
218234 setBusy ( true ) ;
219235 setErrorLine ( null ) ;
220236 setRunningProcesses ( null ) ;
237+ setShowProcessStdout ( false ) ;
238+ processStdoutRef . current . clear ( ) ;
221239 try {
222240 await sessionManager . handleUserPrompt ( prompt ) ;
223241 await refreshSkills ( ) ;
@@ -238,6 +256,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
238256 sessionManager . interruptActiveSession ( ) ;
239257 } , [ sessionManager ] ) ;
240258
259+ const handleToggleProcessStdout = useCallback ( ( ) => {
260+ setShowProcessStdout ( true ) ;
261+ } , [ ] ) ;
262+
263+ const handleDismissProcessStdout = useCallback ( ( ) => {
264+ setShowProcessStdout ( false ) ;
265+ } , [ ] ) ;
266+
241267 const handleModelConfigChange = useCallback (
242268 ( selection : ModelConfigSelection ) : string => {
243269 const current = resolveCurrentSettings ( projectRoot ) ;
@@ -442,7 +468,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
442468 < Text color = "red" > Error: { errorLine } </ Text >
443469 </ Box >
444470 ) : null }
445- { view === "session-list" ? (
471+ { showProcessStdout ? (
472+ < ProcessStdoutView
473+ processStdoutRef = { processStdoutRef }
474+ runningProcesses = { runningProcesses }
475+ onDismiss = { handleDismissProcessStdout }
476+ screenWidth = { screenWidth }
477+ />
478+ ) : view === "session-list" ? (
446479 < SessionList
447480 sessions = { sessions }
448481 onSelect = { ( id ) => void handleSelectSession ( id ) }
@@ -464,9 +497,11 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
464497 promptHistory = { promptHistory }
465498 busy = { busy }
466499 loadingText = { loadingText }
500+ runningProcesses = { runningProcesses }
467501 onSubmit = { handleSubmit }
468502 onModelConfigChange = { handleModelConfigChange }
469503 onInterrupt = { handleInterrupt }
504+ onToggleProcessStdout = { handleToggleProcessStdout }
470505 placeholder = "Type your message..."
471506 />
472507 ) }
0 commit comments