Skip to content

Commit 95cf87f

Browse files
committed
fix: capture streaming output in all evolution phases
Fixed critical bug where phases only captured output from EventMessageEnd, but the API streams content through EventMessageUpdate events. Changes: - reviewPR: Now accumulates content from EventMessageUpdate streaming - RunPlanPhase: Captures streaming output for plan extraction - runTaskAttempt: Accumulates task output from streaming events - RunCommunicatePhase: Captures journal entry from streaming This fixes the empty review comments and plan generation failures. All phases now properly handle streaming API responses.
1 parent 0725ab1 commit 95cf87f

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

internal/evolution/git.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -212,50 +212,66 @@ func (e *Engine) reviewPR(ctx context.Context, p iteragent.Provider, tools []ite
212212
}
213213

214214
a := e.newAgent(p, tools, systemPrompt, skills)
215-
var reviewOutput string
215+
var reviewBuilder strings.Builder
216+
var finalContent string
216217
for ev := range a.Prompt(ctx, userMsg) {
217218
if e.eventSink != nil {
218219
select {
219220
case e.eventSink <- ev:
220221
default:
221222
}
222223
}
224+
// Accumulate content from streaming updates
225+
if ev.Type == string(iteragent.EventMessageUpdate) {
226+
reviewBuilder.WriteString(ev.Content)
227+
}
223228
if ev.Type == string(iteragent.EventMessageEnd) {
224-
reviewOutput = ev.Content
229+
finalContent = ev.Content
225230
}
226231
}
227232
a.Finish()
228233

229-
low := strings.ToLower(reviewOutput)
230-
passed := strings.Contains(low, "lgtm") || strings.Contains(low, "looks good")
234+
// Use accumulated content or final content, whichever is longer
235+
reviewText := reviewBuilder.String()
236+
if len(finalContent) > len(reviewText) {
237+
reviewText = finalContent
238+
}
239+
240+
// Fallback if still empty
241+
if strings.TrimSpace(reviewText) == "" {
242+
reviewText = "Review completed but no output was generated. Please check the code manually."
243+
}
244+
245+
low := strings.ToLower(reviewText)
246+
passed := strings.Contains(low, "lgtm") || strings.Contains(low, "looks good") || strings.Contains(low, "approved")
231247

232248
if passed {
233249
// Only post comment for final successful review
234-
e.postReviewComment(ctx, reviewOutput, true)
250+
e.postReviewComment(ctx, reviewText, true)
235251
e.logger.Info("PR self-review passed")
236252
return nil
237253
}
238254

239255
// Reviewer found issues — try to auto-fix them (only on first review)
240256
if len(isReReview) > 0 && isReReview[0] {
241257
// Already tried auto-fix, still failed
242-
e.postReviewComment(ctx, reviewOutput, false)
258+
e.postReviewComment(ctx, reviewText, false)
243259
return fmt.Errorf("review blocked merge: issues remain after auto-fix")
244260
}
245261

246262
e.logger.Warn("PR self-review found issues — attempting auto-fix")
247263

248-
fixed, fixErr := e.autoFixIssues(ctx, p, tools, systemPrompt, skills, reviewOutput)
264+
fixed, fixErr := e.autoFixIssues(ctx, p, tools, systemPrompt, skills, reviewText)
249265
if fixErr != nil {
250266
e.logger.Error("auto-fix failed", "err", fixErr)
251267
// Post the original review + auto-fix failure
252-
e.postReviewComment(ctx, reviewOutput+"\n\n---\n**Auto-fix failed:** "+fixErr.Error(), false)
268+
e.postReviewComment(ctx, reviewText+"\n\n---\n**Auto-fix failed:** "+fixErr.Error(), false)
253269
return fmt.Errorf("review blocked merge: %w", fixErr)
254270
}
255271

256272
if !fixed {
257273
e.logger.Warn("auto-fix could not resolve all issues — blocking merge")
258-
e.postReviewComment(ctx, reviewOutput+"\n\n---\n**Auto-fix:** Could not resolve all issues automatically.", false)
274+
e.postReviewComment(ctx, reviewText+"\n\n---\n**Auto-fix:** Could not resolve all issues automatically.", false)
259275
return fmt.Errorf("review blocked merge: auto-fix could not resolve all issues")
260276
}
261277

internal/evolution/phases.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,24 @@ func (e *Engine) RunPlanPhase(ctx context.Context, p iteragent.Provider, issues
2626
systemPrompt := buildSystemPrompt(e.repoPath, string(identity))
2727
a := e.newAgent(p, e.tools, systemPrompt, e.skills)
2828

29+
var contentBuilder strings.Builder
2930
var lastContent string
3031
for ev := range a.Prompt(ctx, userMessage) {
32+
if ev.Type == string(iteragent.EventMessageUpdate) {
33+
contentBuilder.WriteString(ev.Content)
34+
}
3135
if ev.Type == string(iteragent.EventMessageEnd) {
3236
lastContent = ev.Content
3337
}
3438
}
3539
a.Finish()
3640

41+
// Use accumulated content or final content, whichever is longer
42+
accumulatedContent := contentBuilder.String()
43+
if len(accumulatedContent) > len(lastContent) {
44+
lastContent = accumulatedContent
45+
}
46+
3747
// Extract plan from agent output if it didn't write the file via tool call.
3848
planPath := filepath.Join(e.repoPath, "SESSION_PLAN.md")
3949
if _, err := os.Stat(planPath); os.IsNotExist(err) && lastContent != "" {
@@ -353,9 +363,13 @@ func (e *Engine) runTaskAttempt(ctx context.Context, p iteragent.Provider, task
353363
userMsg += "\n\nAfter implementing, run: go build ./... && go test ./...\nThen commit your changes using a conventional commit message (e.g. feat: ..., fix: ..., chore: ..., refactor: ..., test: ..., docs: ...)."
354364

355365
a := e.newAgent(p, tools, systemPrompt, skills)
366+
var outputBuilder strings.Builder
356367
var taskOutput string
357368
var taskErr error
358369
for ev := range a.Prompt(ctx, userMsg) {
370+
if ev.Type == string(iteragent.EventMessageUpdate) {
371+
outputBuilder.WriteString(ev.Content)
372+
}
359373
if ev.Type == string(iteragent.EventMessageEnd) {
360374
taskOutput = ev.Content
361375
}
@@ -365,6 +379,12 @@ func (e *Engine) runTaskAttempt(ctx context.Context, p iteragent.Provider, task
365379
}
366380
a.Finish()
367381

382+
// Use accumulated content or final content, whichever is longer
383+
accumulatedOutput := outputBuilder.String()
384+
if len(accumulatedOutput) > len(taskOutput) {
385+
taskOutput = accumulatedOutput
386+
}
387+
368388
if taskErr != nil {
369389
e.logger.Warn("task error", "number", task.Number, "err", taskErr)
370390
_ = e.revert(ctx)

internal/evolution/phases_communicate.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,24 @@ Rules:
8383
- If nothing happened, write "Evolution session completed." as the body`, recentCommits, day, day)
8484

8585
a := e.newAgent(p, nil, minimalPrompt, nil)
86+
var entryBuilder strings.Builder
8687
var journalEntry string
8788
for ev := range a.Prompt(journalCtx, journalMsg) {
89+
if ev.Type == string(iteragent.EventMessageUpdate) {
90+
entryBuilder.WriteString(ev.Content)
91+
}
8892
if ev.Type == string(iteragent.EventMessageEnd) {
8993
journalEntry = strings.TrimSpace(ev.Content)
9094
}
9195
}
9296
a.Finish()
9397

98+
// Use accumulated content or final content, whichever is longer
99+
accumulatedEntry := strings.TrimSpace(entryBuilder.String())
100+
if len(accumulatedEntry) > len(journalEntry) {
101+
journalEntry = accumulatedEntry
102+
}
103+
94104
e.persistJournalEntry(journalEntry, day)
95105
}
96106

0 commit comments

Comments
 (0)