@@ -58,6 +58,8 @@ abstract class PatchApp(
5858 private var lastParsedErrors: ParsedErrors ? = null
5959
6060 private val previousParsedErrorsRecords = mutableListOf<ParsedErrorRecord >()
61+ data class FixAttempt (val error : String , val patch : String , val timestamp : Long = System .currentTimeMillis())
62+ private val fixHistory = mutableMapOf<String , MutableList <FixAttempt >>()
6163
6264 abstract fun codeFiles (): Set <Path >
6365
@@ -134,10 +136,9 @@ abstract class PatchApp(
134136 override val inputCnt = 1
135137 override val stickyInput = false
136138 override fun newSession (user : User , session : Session ): SocketManager {
137- val socketManager = super .newSession(user, session)
139+ val ui = super .newSession(user, session)
138140 log.info(" Creating new session for user: ${user?.id ? : " anonymous" } " )
139141 var retries: Int = - 1
140- val ui = socketManager
141142 val task = ui.newTask()
142143 var retryOnOffButton: StringBuilder ? = null
143144 val disableButton = task.hrefLink(" Disable Auto-Retry" ) {
@@ -159,7 +160,7 @@ abstract class PatchApp(
159160 }
160161 log.debug(" Initialized retries to $retries " )
161162 }
162- val newTask = ui.newTask( false )
163+ val newTask = task.linkedTask( " Run Command ${ if (retries < settings.maxRetries) " (Retry ${settings.maxRetries - retries} / $settings .maxRetries) " else " " } " )
163164 Thread {
164165 log.info(" Starting run thread" )
165166 val model = model.getChildClient(task)
@@ -174,7 +175,7 @@ abstract class PatchApp(
174175 newTask.placeholder
175176 }
176177 log.info(" Session setup complete" )
177- return socketManager
178+ return ui
178179 }
179180
180181 abstract fun projectSummary (): String
@@ -307,7 +308,6 @@ abstract class PatchApp(
307308 task = fixTask,
308309 plan = plan,
309310 settings = settings,
310- changed = mutableSetOf (),
311311 progressHeader = progressHeader,
312312 model = model
313313 )
@@ -330,12 +330,10 @@ abstract class PatchApp(
330330 task : SessionTask ,
331331 plan : ParsedResponse <ParsedErrors >,
332332 settings : Settings ,
333- changed : MutableSet <Path >,
334333 progressHeader : StringBuilder ? ,
335334 model : ChatInterface
336335 ) {
337336 log.info(" Starting fixAllErrors" )
338- val tabs = TabbedDisplay (task)
339337 val errors = plan.obj.errors ? : emptyList()
340338 val hasErrors = errors.any { it.isWarning != true }
341339 log.info(" Found ${errors.size} errors, hasErrors=$hasErrors " )
@@ -351,24 +349,30 @@ abstract class PatchApp(
351349 .map { (msg, errors) ->
352350 log.info(" Processing error group: $msg with ${errors.size} instances" )
353351 task.ui.pool.submit {
354- val task = task.ui.newTask(false ).apply { tabs[msg ? : " Error" ] = placeholder }
352+ val subSession = task.linkedTask(" Fix: ${msg?.take(50 ) ? : " Error" } ..." )
353+ val statusBuffer = subSession.add(" Status: Initializing..." )!!
355354 errors.forEach { error ->
356355 log.info(" Processing individual error: ${error.message} " )
357- task.header(" Processing error: $msg " , 3 )
358- task.add(
356+ statusBuffer.set(" Status: Analyzing error details..." )
357+ subSession.update()
358+
359+ subSession.header(" Processing error: $msg " , 3 )
360+ subSession.add(
359361 renderMarkdown(
360362 " ```json\n ${JsonUtil .toJson(error)} \n ```" ,
361363 tabs = false ,
362- ui = task .ui
364+ ui = subSession .ui
363365 )
364366 )
365- task .verbose(
367+ subSession .verbose(
366368 renderMarkdown(
367369 " [Extra Details] Error processed at: ${Instant .now()} " ,
368370 tabs = false ,
369- ui = task .ui
371+ ui = subSession .ui
370372 )
371373 )
374+ statusBuffer.set(" Status: Searching for relevant files..." )
375+ subSession.update()
372376
373377 val searchResults = error.research?.searchQueries?.flatMap { query ->
374378 log.debug(" Executing search query: pattern=${query.pattern} , glob=${query.fileGlob} " )
@@ -383,22 +387,25 @@ abstract class PatchApp(
383387 }?.toSet() ? : emptySet()
384388 log.info(" Search found ${searchResults.size} relevant files" )
385389 if (searchResults.isNotEmpty()) {
386- task .verbose(
390+ subSession .verbose(
387391 renderMarkdown(
388392 " Search results:\n\n ${searchResults.joinToString(" \n " ) { " * `$it `" }} " ,
389393 tabs = false ,
390- ui = task .ui
394+ ui = subSession .ui
391395 )
392396 )
393397 }
398+ statusBuffer.set(" Status: Generating fix..." )
399+ subSession.update()
394400 fix(
395401 error,
396402 searchResults.toList().map { it.toFile().absolutePath },
397403 settings.autoFix,
398- changed,
399- task,
404+ subSession,
400405 model
401406 )
407+ statusBuffer.set(" Status: Complete" )
408+ subSession.update()
402409 }
403410 }
404411 }.toTypedArray().onEach { it.get() }
@@ -472,7 +479,6 @@ abstract class PatchApp(
472479 error : ParsedError ,
473480 additionalFiles : List <String >? = null,
474481 autoFix : Boolean ,
475- changed : MutableSet <Path >,
476482 task : SessionTask ,
477483 model : ChatInterface ,
478484 ) {
@@ -508,6 +514,15 @@ abstract class PatchApp(
508514
509515 val summary = codeSummary(prunedPaths.distinct(), error)
510516 log.info(" Generated code summary (${summary.length} chars)" )
517+ val historyContext = prunedPaths.mapNotNull { path ->
518+ val history = fixHistory[path.toString()]
519+ if (! history.isNullOrEmpty()) {
520+ " ### History for `$path `\n " + history.joinToString(" \n " ) {
521+ " - [${SimpleDateFormat (" HH:mm:ss" ).format(it.timestamp)} ] Attempted fix for '${it.error} '"
522+ } + " \n\n Previous patches applied to this file:\n ```diff\n " + history.joinToString(" \n\n " ) { it.patch } + " \n ```"
523+ } else null
524+ }.joinToString(" \n\n " )
525+
511526 val fixResponse = ChatAgent (
512527 prompt = """
513528 You are a helpful AI that helps people with coding.
@@ -522,31 +537,31 @@ abstract class PatchApp(
522537 listOf (
523538 " $promptPrefix \n\n Focus on and Fix the Error:\n ${error.message ? : " " } \n " +
524539 (if (error.details?.isNotBlank() == true ) " Details:\n ${error.details} \n " else " " ) +
525- (if (settings.additionalInstructions.isNotBlank()) " Additional Instructions:\n ${settings.additionalInstructions} \n " else " " )
540+ (if (settings.additionalInstructions.isNotBlank()) " Additional Instructions:\n ${settings.additionalInstructions} \n " else " " ) +
541+ (if (historyContext.isNotBlank()) " \n\n Previous Debugging Attempts (Learn from these):\n $historyContext " else " " )
526542 ),
527543
528544 ).lines().joinToString(" \n " ) {
529545 it.replace(Regex (""" /\* Error.*?\*/""" ), " " )
530546 }
531547 log.info(" Received fix response (${fixResponse.length} chars)" )
548+ // Record history
549+ prunedPaths.forEach { path ->
550+ fixHistory.getOrPut(path.toString()) { mutableListOf () }.add(
551+ FixAttempt (error.message ? : " Unknown" , fixResponse)
552+ )
553+ }
554+
532555 val markdown = AddApplyFileDiffLinks .instrumentFileDiffs(
533556 self = task.ui,
534557 root = root.toPath(),
535558 response = fixResponse,
536559 shouldAutoApply = { path ->
537- if (autoFix && ! changed.contains(path) ) {
560+ if (autoFix) {
538561 log.info(" Auto-applying fix to: $path " )
539- changed.add(path)
540562 true
541563 } else {
542- log.debug(
543- " Not auto-applying fix to: {} (autoFix={}, already changed={})" ,
544- path,
545- autoFix,
546- changed.contains(
547- path
548- )
549- )
564+ log.debug(" Not auto-applying fix to: {} (autoFix={})" , path, autoFix)
550565 false
551566 }
552567 },
0 commit comments