@@ -50,6 +50,35 @@ def test_normalize_task_items_accepts_list_of_json_strings():
5050 assert items [1 ]["task" ] == "Second"
5151
5252
53+ def test_normalize_json_array_joins_char_split_json_list ():
54+ """Ollama sometimes sends tasks as a list of single-character strings."""
55+ chars = list (
56+ '[{"task": "Explore the codebase", "done": false, "current": true},'
57+ '{"task": "Draft roadmap", "done": false}]'
58+ )
59+ items = normalize_json_array (chars , param_name = "tasks" )
60+ assert len (items ) == 2
61+ assert items [0 ]["task" ] == "Explore the codebase"
62+ assert items [1 ]["task" ] == "Draft roadmap"
63+
64+
65+ def test_normalize_json_array_unwraps_single_element_json_string_list ():
66+ wrapped = [
67+ '[{"task": "Only task", "done": false}]' ,
68+ ]
69+ items = normalize_json_array (wrapped , param_name = "tasks" )
70+ assert len (items ) == 1
71+ assert items [0 ]["task" ] == "Only task"
72+
73+
74+ def test_normalize_task_items_from_char_split_list ():
75+ chars = list (json .dumps ([{"task" : "Ship tests" , "done" : True }]))
76+ items = normalize_task_items (chars )
77+ assert len (items ) == 1
78+ assert items [0 ]["task" ] == "Ship tests"
79+ assert items [0 ]["done" ] is True
80+
81+
5382def test_normalize_task_items_does_not_split_characters ():
5483 tasks_json = json .dumps ([{"task" : "Only one task" , "done" : False }])
5584 items = normalize_task_items (tasks_json )
@@ -103,6 +132,32 @@ def test_format_output_accepts_tasks_as_json_string():
103132 coder .io .tool_error .assert_not_called ()
104133
105134
135+ def test_format_output_accepts_char_split_tasks_list ():
136+ """Reproduces BrightVision bug: tasks array is one JSON character per element."""
137+ coder = DummyCoder ()
138+ tasks_json = (
139+ '[{"task": "Explore the codebase", "done": false, "current": true},'
140+ '{"task": "Draft roadmap items", "done": false}]'
141+ )
142+ args = json .dumps ({"tasks" : list (tasks_json )})
143+ tool_response = SimpleNamespace (function = SimpleNamespace (name = "UpdateTodoList" , arguments = args ))
144+
145+ update_todo_list .Tool .format_output (
146+ coder ,
147+ mcp_server = SimpleNamespace (name = "test" ),
148+ tool_response = tool_response ,
149+ )
150+
151+ output_text = "\n " .join (call .args [0 ] for call in coder .io .tool_output .call_args_list )
152+ assert "Explore the codebase" in output_text
153+ assert "Draft roadmap items" in output_text
154+ assert output_text .count ("○ " ) == 1
155+ assert "→ Explore the codebase" in output_text
156+ assert "○ Draft roadmap items" in output_text
157+ assert all (len (line .strip ()) > 3 for line in output_text .splitlines () if line .startswith ("○ " ))
158+ coder .io .tool_error .assert_not_called ()
159+
160+
106161def test_format_output_reports_invalid_tool_arguments_json ():
107162 coder = DummyCoder ()
108163 tool_response = SimpleNamespace (
0 commit comments