@@ -24,27 +24,48 @@ import { render } from "ink";
2424import type { StoredMessage } from "../local/types" ;
2525import type { ID } from "../agent/types" ;
2626import { checkAndMarkFirstRun } from "../cli/lib/first-run" ;
27+ import type { UseChat } from "../react/use-chat" ;
28+ import util from "node:util" ;
29+ import { useLogger } from "../react/use-logger" ;
30+ import { Logger , LoggerContext } from "../react/use-logger" ;
2731
2832const colors = {
2933 run : "#1f86ed" ,
3034 edit : "#e8900e" ,
3135} as const ;
3236
3337export async function startDev ( { directory } : { directory : string } ) {
34- const instance = render (
35- < KeypressProvider >
36- < Root directory = { directory } />
37- </ KeypressProvider > ,
38- {
39- exitOnCtrlC : false ,
40- }
41- ) ;
38+ const instance = render ( < Root directory = { directory } /> , {
39+ exitOnCtrlC : false ,
40+ } ) ;
4241 await instance . waitUntilExit ( ) ;
4342}
4443
4544const Root = ( { directory } : { directory : string } ) => {
45+ const [ logger , setLogger ] = useState < Logger > (
46+ new Logger ( async ( level , ...message ) => {
47+ console [ level ] ( ...message ) ;
48+ } )
49+ ) ;
50+ return (
51+ < KeypressProvider >
52+ < LoggerContext . Provider value = { logger } >
53+ < App directory = { directory } setLogger = { setLogger } />
54+ </ LoggerContext . Provider >
55+ </ KeypressProvider >
56+ ) ;
57+ } ;
58+
59+ const App = ( {
60+ directory,
61+ setLogger,
62+ } : {
63+ directory : string ;
64+ setLogger : ( logger : Logger ) => void ;
65+ } ) => {
4666 const size = useTerminalSize ( ) ;
4767 const [ isFirstRun ] = useState ( ( ) => checkAndMarkFirstRun ( directory ) ) ;
68+ const logger = useLogger ( ) ;
4869
4970 // Use the shared dev mode hook
5071 const dev = useDevMode ( {
@@ -73,10 +94,11 @@ const Root = ({ directory }: { directory: string }) => {
7394 console . log ( chalk . gray ( `⚙ Send webhooks from anywhere: ${ url } ` ) ) ;
7495 } ,
7596 onAgentLog : ( log ) => {
76- const logColor = log . level === "error" ? "red" : "white" ;
77- const logPrefix =
78- log . level === "error" ? "⚙ [Agent Error]" : "⚙ [Agent Log]" ;
79- console . log ( `${ chalk [ logColor ] ( logPrefix ) } ${ chalk . gray ( log . message ) } ` ) ;
97+ if ( log . level === "error" ) {
98+ logger . error ( "agent" , log . message ) ;
99+ } else {
100+ logger . log ( "agent" , log . message ) ;
101+ }
80102 } ,
81103 onDevhookRequest : ( request ) => {
82104 console . log (
@@ -102,6 +124,17 @@ const Root = ({ directory }: { directory: string }) => {
102124 }
103125 } ,
104126 } ) ;
127+ useEffect ( ( ) => {
128+ setLogger (
129+ new Logger ( ( level , source , ...message ) => {
130+ return dev . chat . queueLogMessage ( {
131+ message : util . format ( ...message ) ,
132+ level,
133+ source,
134+ } ) ;
135+ } )
136+ ) ;
137+ } , [ dev . chat . queueLogMessage , setLogger ] ) ;
105138
106139 const { exit } = useApp ( ) ;
107140 const [ exitArmed , setExitArmed ] = useState ( false ) ;
@@ -324,6 +357,20 @@ const Root = ({ directory }: { directory: string }) => {
324357 maxWidth = { size . columns - 2 }
325358 />
326359 ) : null }
360+ { dev . chat . queuedLogs . map ( ( log ) => (
361+ < Message
362+ key = { log . id }
363+ message = { log }
364+ nextMessage = { undefined }
365+ previousMessage = {
366+ dev . chat . streamingMessage ||
367+ ( dev . chat . messages . length > 0
368+ ? dev . chat . messages . at ( dev . chat . messages . length - 1 )
369+ : undefined )
370+ }
371+ maxWidth = { size . columns - 2 }
372+ />
373+ ) ) }
327374 { dev . showWaitingPlaceholder ? (
328375 < AssistantWaitingPlaceholder maxWidth = { size . columns - 2 } />
329376 ) : null }
@@ -632,12 +679,52 @@ const MessageComponent = ({
632679 maxWidth ?: number ;
633680 streaming ?: boolean ;
634681} ) => {
682+ // Check if this is a log message
683+ const isLogMessage =
684+ message . metadata &&
685+ typeof message . metadata === "object" &&
686+ "__blink_log" in message . metadata &&
687+ message . metadata . __blink_log === true ;
688+
635689 let prefix : React . ReactNode ;
636690 let contentColor : string ;
637691 // Only add margin if there is a previous message.
638692 // Otherwise, we end up with two blank lines under the banner.
639693 let marginTop : number = previousMessage ? 1 : 0 ;
640694
695+ if ( isLogMessage ) {
696+ // Format log messages with special styling
697+ const logLevel = ( message . metadata as any ) . level ;
698+ const logSource = ( message . metadata as any ) . source ;
699+ const isError = logLevel === "error" ;
700+
701+ prefix = null ;
702+ contentColor = isError ? "red" : "gray" ;
703+
704+ // Render log messages with a different structure
705+ const content : React . ReactNode = (
706+ < Box gap = { 1 } flexDirection = "column" width = { maxWidth } >
707+ { message . parts
708+ . map ( ( part , index ) => {
709+ if ( part . type === "text" ) {
710+ return < Text color = "gray" > { part . text } </ Text > ;
711+ }
712+ return null ;
713+ } )
714+ . filter ( Boolean ) }
715+ </ Box >
716+ ) ;
717+
718+ return (
719+ < Box flexDirection = "row" gap = { 1 } >
720+ < Text color = { isError ? "red" : undefined } bold >
721+ [{ logSource } ]
722+ </ Text >
723+ { content }
724+ </ Box >
725+ ) ;
726+ }
727+
641728 switch ( message . role ) {
642729 case "system" :
643730 prefix = < Text > t </ Text > ;
0 commit comments