@@ -157,28 +157,11 @@ def _extract_file_path(i: dict[str, Any]) -> str:
157157 return _shorten_path (i .get ("file_path" , "" ))
158158
159159
160- _TOOL_ARG_EXTRACTORS : dict [str , Callable [[dict [str , Any ]], str ]] = {
161- "Read" : _extract_file_path ,
162- "Write" : _extract_file_path ,
163- "Edit" : _extract_file_path ,
164- "MultiEdit" : _extract_file_path ,
165- "Glob" : lambda i : i .get ("pattern" , "" ),
166- "Grep" : lambda i : i .get ("pattern" , "" ),
167- "Bash" : lambda i : i .get ("command" , "" ),
168- "Task" : lambda i : _format_params (i , ["description" , "prompt" ]),
169- "WebFetch" : lambda i : i .get ("url" , "" ),
170- "WebSearch" : lambda i : i .get ("query" , "" ),
171- "TodoWrite" : lambda i : f"{ len (i .get ('todos' , []))} todos" ,
172- "Agent" : lambda i : _format_params (i , ["description" , "prompt" ]),
173- "ToolSearch" : lambda i : _format_params (i , ["query" , "max_results" ]),
174- }
175-
176-
177160def _extract_tool_arg (name : str , tool_input : dict [str , Any ]) -> str :
178161 """Return the most relevant argument string for a tool call."""
179- extractor = _TOOL_ARG_EXTRACTORS .get (name )
180- if extractor is not None :
181- return extractor (tool_input )
162+ cfg = _TOOL_REGISTRY .get (name )
163+ if cfg is not None and cfg . extract_arg is not None :
164+ return cfg . extract_arg (tool_input )
182165 return ", " .join (sorted (tool_input .keys ()))
183166
184167
@@ -227,25 +210,48 @@ def _format_run_info(
227210 return " · " .join (parts )
228211
229212
230- # ── Iteration panel ──────────────────────────────────────────────────
231-
232- # (color, category) for each known tool. Color is applied to the tool
233- # name in scroll lines so the activity feed scans visually by intent —
234- # blue=read/search, orange=mutate, green=execute, lavender=web, etc.
235- # Category is the bucket used in the footer's compact tool counter.
236- _TOOL_STYLES : dict [str , tuple [str , str ]] = {
237- "Read" : (_brand .BLUE , "read" ),
238- "Glob" : (_brand .BLUE , "glob" ),
239- "Grep" : (_brand .BLUE , "grep" ),
240- "Edit" : (_brand .ORANGE , "edit" ),
241- "MultiEdit" : (_brand .ORANGE , "edit" ),
242- "Write" : (_brand .ORANGE , "write" ),
243- "Bash" : (_brand .GREEN , "bash" ),
244- "BashOutput" : (_brand .GREEN , "bash" ),
245- "WebFetch" : (_brand .LAVENDER , "web" ),
246- "WebSearch" : (_brand .LAVENDER , "web" ),
247- "Task" : (_brand .VIOLET , "task" ),
248- "TodoWrite" : (_brand .PURPLE , "todo" ),
213+ # ── Tool registry ───────────────────────────────────────────────────
214+ #
215+ # Single source of truth for tool display config in the activity feed.
216+ # Each entry defines the color (applied to the tool name), the category
217+ # bucket (shown in the footer counter), and an optional argument
218+ # extractor (picks the most relevant arg for the scroll line).
219+ #
220+ # Color intent: blue=read/search, orange=mutate, green=execute,
221+ # lavender=web, violet=delegate, purple=meta.
222+
223+
224+ class _ToolConfig :
225+ """Visual and extraction config for a known tool."""
226+
227+ __slots__ = ("color" , "category" , "extract_arg" )
228+
229+ def __init__ (
230+ self ,
231+ color : str ,
232+ category : str ,
233+ extract_arg : Callable [[dict [str , Any ]], str ] | None = None ,
234+ ) -> None :
235+ self .color = color
236+ self .category = category
237+ self .extract_arg = extract_arg
238+
239+
240+ _TOOL_REGISTRY : dict [str , _ToolConfig ] = {
241+ "Read" : _ToolConfig (_brand .BLUE , "read" , _extract_file_path ),
242+ "Glob" : _ToolConfig (_brand .BLUE , "glob" , lambda i : i .get ("pattern" , "" )),
243+ "Grep" : _ToolConfig (_brand .BLUE , "grep" , lambda i : i .get ("pattern" , "" )),
244+ "Edit" : _ToolConfig (_brand .ORANGE , "edit" , _extract_file_path ),
245+ "MultiEdit" : _ToolConfig (_brand .ORANGE , "edit" , _extract_file_path ),
246+ "Write" : _ToolConfig (_brand .ORANGE , "write" , _extract_file_path ),
247+ "Bash" : _ToolConfig (_brand .GREEN , "bash" , lambda i : i .get ("command" , "" )),
248+ "BashOutput" : _ToolConfig (_brand .GREEN , "bash" ),
249+ "WebFetch" : _ToolConfig (_brand .LAVENDER , "web" , lambda i : i .get ("url" , "" )),
250+ "WebSearch" : _ToolConfig (_brand .LAVENDER , "web" , lambda i : i .get ("query" , "" )),
251+ "Task" : _ToolConfig (_brand .VIOLET , "task" , lambda i : _format_params (i , ["description" , "prompt" ])),
252+ "Agent" : _ToolConfig (_brand .VIOLET , "agent" , lambda i : _format_params (i , ["description" , "prompt" ])),
253+ "ToolSearch" : _ToolConfig (_brand .BLUE , "other" , lambda i : _format_params (i , ["query" , "max_results" ])),
254+ "TodoWrite" : _ToolConfig (_brand .PURPLE , "todo" , lambda i : f"{ len (i .get ('todos' , []))} todos" ),
249255}
250256
251257_DEFAULT_TOOL_STYLE : tuple [str , str ] = ("white" , "other" )
@@ -257,7 +263,8 @@ def _format_run_info(
257263
258264
259265def _tool_style_for (name : str ) -> tuple [str , str ]:
260- return _TOOL_STYLES .get (name , _DEFAULT_TOOL_STYLE )
266+ cfg = _TOOL_REGISTRY .get (name )
267+ return (cfg .color , cfg .category ) if cfg is not None else _DEFAULT_TOOL_STYLE
261268
262269
263270class _LivePanelBase :
0 commit comments