| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | |
| 7 | "reasonix/internal/event" |
| 8 | "reasonix/internal/jobs" |
| 9 | "reasonix/internal/provider" |
| 10 | ) |
| 11 | |
| 12 | func TestBackgroundTaskToolEventsStayNestedUnderParentCall(t *testing.T) { |
| 13 | finalText := []provider.Chunk{{Type: provider.ChunkText, Text: "done"}, {Type: provider.ChunkDone}} |
| 14 | sub := &scriptedProvider{name: "sub", turns: [][]provider.Chunk{ |
| 15 | {toolCallChunk("c1", "bash", `{"command":"ls"}`), {Type: provider.ChunkDone}}, |
| 16 | finalText, finalText, |
| 17 | }} |
| 18 | task := NewTaskTool(sub, nil, evidenceRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil). |
| 19 | WithTranscripts(NewSubagentStore(t.TempDir()), t.TempDir(), "base-model", "base-effort") |
| 20 | jm := jobs.NewManager(event.Discard) |
| 21 | defer jm.Close() |
| 22 | rec := &recordSink{} |
| 23 | ctx := withCallContext(testTaskContext(), "task-call", rec, nil, false) |
| 24 | ctx = jobs.WithSession(ctx, "parent-session") |
| 25 | ctx = jobs.WithManager(ctx, jm) |
| 26 | |
| 27 | out, err := task.Execute(ctx, []byte(`{"prompt":"list","run_in_background":true}`)) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | res := jm.WaitForSession(context.Background(), "parent-session", []string{extractJobID(out)}, 5) |
| 32 | if len(res) != 1 || res[0].Status != jobs.Done { |
| 33 | t.Fatalf("job = %+v", res) |
| 34 | } |
| 35 | rec.mu.Lock() |
| 36 | defer rec.mu.Unlock() |
| 37 | seen := 0 |
| 38 | for _, e := range rec.evs { |
| 39 | if e.Tool.ID == "" || e.Tool.ID == "task-call" { |
| 40 | continue |
| 41 | } |
| 42 | seen++ |
| 43 | if e.Tool.ParentID != "task-call" { |
| 44 | t.Errorf("event kind %v tool %q id %q has ParentID %q, want task-call", e.Kind, e.Tool.Name, e.Tool.ID, e.Tool.ParentID) |
| 45 | } |
| 46 | } |
| 47 | if seen == 0 { |
| 48 | t.Fatal("no sub-agent tool events observed") |
| 49 | } |
| 50 | } |
| 51 |