fix(agent): fix data races, add tests, and fix raw protocol detection

- Fix data race on output strings.Builder in codex backend by adding
  mutex and waiting for reader goroutine before reading final output
- Fix data race on onTurnDone by initializing it before reader starts
- Fix bug where notificationProtocol zero value "" never matched
  "unknown", silently dropping all raw v2 notifications from codex
- Add round-robin polling to prevent runtime starvation in poll loop
- Log errors in claude handleControlRequest instead of silently dropping
- Add 35 tests for pkg/agent covering claude parsing, codex JSON-RPC,
  protocol detection, event handling, and helper functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yushen 2026-03-24 14:21:10 +08:00
parent 0d9b687d92
commit 96cfdc2e27
6 changed files with 864 additions and 15 deletions

View file

@ -384,6 +384,7 @@ func (d *daemon) heartbeatLoop(ctx context.Context, runtimeIDs []string) {
}
func (d *daemon) pollLoop(ctx context.Context, runtimeIDs []string) error {
pollOffset := 0
for {
select {
case <-ctx.Done():
@ -392,7 +393,9 @@ func (d *daemon) pollLoop(ctx context.Context, runtimeIDs []string) error {
}
claimed := false
for _, rid := range runtimeIDs {
n := len(runtimeIDs)
for i := 0; i < n; i++ {
rid := runtimeIDs[(pollOffset+i)%n]
task, err := d.client.claimTask(ctx, rid)
if err != nil {
d.logger.Printf("claim task failed for runtime %s: %v", rid, err)
@ -402,11 +405,13 @@ func (d *daemon) pollLoop(ctx context.Context, runtimeIDs []string) error {
d.logger.Printf("poll: got task=%s issue=%s title=%q", task.ID, task.IssueID, task.Context.Issue.Title)
d.handleTask(ctx, *task)
claimed = true
pollOffset = (pollOffset + i + 1) % n
break
}
}
if !claimed {
pollOffset = (pollOffset + 1) % n
if err := sleepWithContext(ctx, d.cfg.PollInterval); err != nil {
return err
}