@@ -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,20 @@ 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+ } ,
117+ } ,
101118 } ) ;
102119 } , [ projectRoot ] ) ;
103120
@@ -218,6 +235,8 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
218235 setBusy ( true ) ;
219236 setErrorLine ( null ) ;
220237 setRunningProcesses ( null ) ;
238+ setShowProcessStdout ( false ) ;
239+ processStdoutRef . current . clear ( ) ;
221240 try {
222241 await sessionManager . handleUserPrompt ( prompt ) ;
223242 await refreshSkills ( ) ;
@@ -238,6 +257,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
238257 sessionManager . interruptActiveSession ( ) ;
239258 } , [ sessionManager ] ) ;
240259
260+ const handleToggleProcessStdout = useCallback ( ( ) => {
261+ setShowProcessStdout ( true ) ;
262+ } , [ ] ) ;
263+
264+ const handleDismissProcessStdout = useCallback ( ( ) => {
265+ setShowProcessStdout ( false ) ;
266+ } , [ ] ) ;
267+
241268 const handleModelConfigChange = useCallback (
242269 ( selection : ModelConfigSelection ) : string => {
243270 const current = resolveCurrentSettings ( projectRoot ) ;
@@ -442,7 +469,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
442469 < Text color = "red" > Error: { errorLine } </ Text >
443470 </ Box >
444471 ) : null }
445- { view === "session-list" ? (
472+ { showProcessStdout ? (
473+ < ProcessStdoutView
474+ processStdoutRef = { processStdoutRef }
475+ runningProcesses = { runningProcesses }
476+ onDismiss = { handleDismissProcessStdout }
477+ screenWidth = { screenWidth }
478+ />
479+ ) : view === "session-list" ? (
446480 < SessionList
447481 sessions = { sessions }
448482 onSelect = { ( id ) => void handleSelectSession ( id ) }
@@ -464,9 +498,11 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R
464498 promptHistory = { promptHistory }
465499 busy = { busy }
466500 loadingText = { loadingText }
501+ runningProcesses = { runningProcesses }
467502 onSubmit = { handleSubmit }
468503 onModelConfigChange = { handleModelConfigChange }
469504 onInterrupt = { handleInterrupt }
505+ onToggleProcessStdout = { handleToggleProcessStdout }
470506 placeholder = "Type your message..."
471507 />
472508 ) }
0 commit comments