voidx 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. voidx/__init__.py +3 -0
  2. voidx/agent/__init__.py +0 -0
  3. voidx/agent/agents.py +439 -0
  4. voidx/agent/attachments.py +235 -0
  5. voidx/agent/graph.py +463 -0
  6. voidx/agent/graph_components/__init__.py +1 -0
  7. voidx/agent/graph_components/compaction.py +268 -0
  8. voidx/agent/graph_components/permissions.py +139 -0
  9. voidx/agent/graph_components/run_loop.py +532 -0
  10. voidx/agent/graph_components/runtime.py +14 -0
  11. voidx/agent/graph_components/streaming.py +351 -0
  12. voidx/agent/graph_components/subagent.py +278 -0
  13. voidx/agent/graph_components/tool_execution.py +208 -0
  14. voidx/agent/runtime_context.py +368 -0
  15. voidx/agent/slash.py +466 -0
  16. voidx/agent/slash_components/__init__.py +1 -0
  17. voidx/agent/slash_components/code_ide.py +68 -0
  18. voidx/agent/slash_components/lsp.py +105 -0
  19. voidx/agent/slash_components/mcp.py +332 -0
  20. voidx/agent/slash_components/model.py +419 -0
  21. voidx/agent/slash_components/runtime.py +55 -0
  22. voidx/agent/slash_components/skills.py +94 -0
  23. voidx/agent/state.py +32 -0
  24. voidx/agent/task_state.py +278 -0
  25. voidx/agent/tool_filters.py +27 -0
  26. voidx/config.py +707 -0
  27. voidx/llm/__init__.py +0 -0
  28. voidx/llm/catalog.py +188 -0
  29. voidx/llm/compaction.py +267 -0
  30. voidx/llm/context.py +43 -0
  31. voidx/llm/instruction.py +220 -0
  32. voidx/llm/provider.py +312 -0
  33. voidx/llm/usage.py +341 -0
  34. voidx/lsp/__init__.py +30 -0
  35. voidx/lsp/client.py +259 -0
  36. voidx/lsp/config.py +172 -0
  37. voidx/lsp/detector.py +512 -0
  38. voidx/lsp/errors.py +19 -0
  39. voidx/lsp/manager.py +280 -0
  40. voidx/lsp/schema.py +179 -0
  41. voidx/lsp/service.py +103 -0
  42. voidx/main.py +154 -0
  43. voidx/mcp/__init__.py +33 -0
  44. voidx/mcp/client.py +458 -0
  45. voidx/mcp/manager.py +267 -0
  46. voidx/mcp/schema.py +112 -0
  47. voidx/mcp/tool.py +122 -0
  48. voidx/mcp_servers/__init__.py +1 -0
  49. voidx/mcp_servers/web.py +104 -0
  50. voidx/memory/__init__.py +0 -0
  51. voidx/memory/context_frames.py +188 -0
  52. voidx/memory/model_profiles.py +98 -0
  53. voidx/memory/runtime_state.py +240 -0
  54. voidx/memory/session.py +272 -0
  55. voidx/memory/store.py +245 -0
  56. voidx/memory/transcript.py +137 -0
  57. voidx/permission/__init__.py +28 -0
  58. voidx/permission/engine.py +430 -0
  59. voidx/permission/evaluate.py +114 -0
  60. voidx/permission/sandbox.py +280 -0
  61. voidx/permission/schema.py +24 -0
  62. voidx/permission/service.py +314 -0
  63. voidx/permission/wildcard.py +34 -0
  64. voidx/skills/__init__.py +18 -0
  65. voidx/skills/bundled/superpowers/receiving-code-review/SKILL.md +30 -0
  66. voidx/skills/bundled/superpowers/requesting-code-review/SKILL.md +27 -0
  67. voidx/skills/bundled/superpowers/systematic-debugging/SKILL.md +36 -0
  68. voidx/skills/bundled/superpowers/test-driven-development/SKILL.md +33 -0
  69. voidx/skills/bundled/superpowers/verification-before-completion/SKILL.md +31 -0
  70. voidx/skills/bundled/superpowers/writing-plans/SKILL.md +27 -0
  71. voidx/skills/policy.py +97 -0
  72. voidx/skills/registry.py +162 -0
  73. voidx/skills/schema.py +47 -0
  74. voidx/skills/service.py +199 -0
  75. voidx/tools/__init__.py +0 -0
  76. voidx/tools/agent.py +81 -0
  77. voidx/tools/base.py +86 -0
  78. voidx/tools/bash.py +105 -0
  79. voidx/tools/file_ops.py +193 -0
  80. voidx/tools/lsp.py +155 -0
  81. voidx/tools/registry.py +104 -0
  82. voidx/tools/repomap.py +238 -0
  83. voidx/tools/search.py +162 -0
  84. voidx/tools/task_status.py +57 -0
  85. voidx/tools/task_tracker.py +81 -0
  86. voidx/tools/todo.py +82 -0
  87. voidx/tools/web_content.py +357 -0
  88. voidx/tools/web_mcp.py +107 -0
  89. voidx/tools/webfetch.py +155 -0
  90. voidx/tools/websearch.py +276 -0
  91. voidx/ui/__init__.py +0 -0
  92. voidx/ui/app.py +1033 -0
  93. voidx/ui/app_components/__init__.py +1 -0
  94. voidx/ui/app_components/clipboard_image.py +245 -0
  95. voidx/ui/app_components/commands.py +18 -0
  96. voidx/ui/app_components/controls.py +29 -0
  97. voidx/ui/app_components/file_picker.py +115 -0
  98. voidx/ui/app_components/formatting.py +187 -0
  99. voidx/ui/app_components/git_changes.py +51 -0
  100. voidx/ui/app_components/rendering.py +1169 -0
  101. voidx/ui/browse.py +160 -0
  102. voidx/ui/capture.py +169 -0
  103. voidx/ui/code_ide.py +251 -0
  104. voidx/ui/commands.py +83 -0
  105. voidx/ui/console.py +381 -0
  106. voidx/ui/console_components/__init__.py +1 -0
  107. voidx/ui/console_components/formatting.py +96 -0
  108. voidx/ui/console_components/streaming.py +253 -0
  109. voidx/ui/diff.py +331 -0
  110. voidx/ui/dock.py +372 -0
  111. voidx/ui/dock_components/__init__.py +1 -0
  112. voidx/ui/dock_components/formatting.py +123 -0
  113. voidx/ui/dock_components/nodes.py +401 -0
  114. voidx/ui/dock_components/state.py +51 -0
  115. voidx/ui/event_components/__init__.py +1 -0
  116. voidx/ui/event_components/schema.py +249 -0
  117. voidx/ui/events.py +341 -0
  118. voidx/ui/session_changes.py +163 -0
  119. voidx/ui/startup.py +161 -0
  120. voidx/ui/transcript.py +148 -0
  121. voidx/ui/tree.py +316 -0
  122. voidx-1.0.0.dist-info/METADATA +59 -0
  123. voidx-1.0.0.dist-info/RECORD +126 -0
  124. voidx-1.0.0.dist-info/WHEEL +5 -0
  125. voidx-1.0.0.dist-info/entry_points.txt +2 -0
  126. voidx-1.0.0.dist-info/top_level.txt +1 -0
voidx/ui/console.py ADDED
@@ -0,0 +1,381 @@
1
+ """Rich console — smooth streaming, status indicators, Claude Code style."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import contextmanager
6
+ from contextvars import ContextVar
7
+ from typing import Callable, Iterator
8
+
9
+ from rich.console import Console
10
+ from rich.markdown import Markdown
11
+ from rich.panel import Panel
12
+ from rich.text import Text
13
+
14
+ from voidx.ui.console_components.formatting import (
15
+ _capture_ansi,
16
+ _done_spin,
17
+ _event_tool_id,
18
+ _fmt_args,
19
+ _fmt_args_short,
20
+ _next_spin,
21
+ _pop_event_tool_id,
22
+ _title,
23
+ fmt_args,
24
+ )
25
+ from voidx.ui.dock import dock
26
+ from voidx.ui.events import (
27
+ AnsiAppended,
28
+ DiffAppended,
29
+ ErrorAppended,
30
+ MarkdownAppended,
31
+ StatusUpdated,
32
+ ToolFinished,
33
+ ToolResultAppended,
34
+ ToolStarted,
35
+ ui_events,
36
+ )
37
+ from voidx.ui.console_components.streaming import StreamingRenderer
38
+
39
+ CommandOutputSink = Callable[[str], None]
40
+ CommandOutputWidth = int | Callable[[], int]
41
+
42
+ _command_output_sink: ContextVar[CommandOutputSink | None] = ContextVar(
43
+ "command_output_sink",
44
+ default=None,
45
+ )
46
+ _command_output_width: ContextVar[CommandOutputWidth] = ContextVar(
47
+ "command_output_width",
48
+ default=80,
49
+ )
50
+
51
+
52
+ def _capture_width() -> int:
53
+ width = _command_output_width.get()
54
+ if callable(width):
55
+ try:
56
+ return max(int(width()), 20)
57
+ except Exception:
58
+ return 80
59
+ return max(int(width), 20)
60
+
61
+
62
+ class VoidConsole:
63
+ """Thin wrapper with voidx-specific rendering primitives."""
64
+
65
+ _TOOL_GERUND: dict[str, str] = {
66
+ "read": "reading", "write": "writing", "edit": "editing",
67
+ "glob": "finding", "grep": "searching", "bash": "running",
68
+ "agent": "delegating", "webfetch": "fetching", "websearch": "searching",
69
+ "todo": "updating", "task_status": "checking", "repo_map": "mapping",
70
+ "lsp_diagnostics": "checking", "lsp_symbols": "indexing",
71
+ "lsp_definition": "locating", "lsp_references": "finding",
72
+ "lsp_format": "formatting",
73
+ }
74
+
75
+ _AGENT_GERUND: dict[str, str] = {
76
+ "orchestrator": "thinking",
77
+ "explore": "exploring",
78
+ "plan": "planning",
79
+ "implement": "implementing",
80
+ "review": "reviewing",
81
+ }
82
+
83
+ def __init__(self) -> None:
84
+ self._console = Console()
85
+ self._debug = True
86
+ self._pending_tools: dict[str, list[dict[str, object]]] = {}
87
+ self._event_tool_ids: dict[str, list[str]] = {}
88
+
89
+ @property
90
+ def console(self) -> Console:
91
+ return self._console
92
+
93
+ @property
94
+ def width(self) -> int:
95
+ return self._console.width
96
+
97
+ @property
98
+ def debug(self) -> bool:
99
+ return self._debug
100
+
101
+ def set_debug(self, value: bool) -> None:
102
+ self._debug = value
103
+
104
+ @contextmanager
105
+ def capture_command_output(
106
+ self,
107
+ sink: CommandOutputSink,
108
+ *,
109
+ width: CommandOutputWidth = 80,
110
+ ) -> Iterator[None]:
111
+ sink_token = _command_output_sink.set(sink)
112
+ width_token = _command_output_width.set(width)
113
+ try:
114
+ yield
115
+ finally:
116
+ _command_output_width.reset(width_token)
117
+ _command_output_sink.reset(sink_token)
118
+
119
+ def _emit_command_output(self, render: Callable[[Console], None]) -> bool:
120
+ sink = _command_output_sink.get()
121
+ if sink is None:
122
+ return False
123
+ text = _capture_ansi(_capture_width(), render)
124
+ if text:
125
+ sink(text)
126
+ return True
127
+
128
+ def print(self, *args, **kwargs) -> None:
129
+ if self._emit_command_output(lambda console: console.print(*args, **kwargs)):
130
+ return
131
+ if dock.active and ui_events.is_running:
132
+ text = _capture_ansi(
133
+ self._console.width,
134
+ lambda console: console.print(*args, **kwargs),
135
+ )
136
+ if text:
137
+ ui_events.emit_nowait(AnsiAppended(text=text))
138
+ return
139
+ if dock.active and dock.print(*args, **kwargs):
140
+ return
141
+ self._console.print(*args, **kwargs)
142
+
143
+ def markdown(self, content: str) -> None:
144
+ if self._emit_command_output(lambda console: console.print(Markdown(content))):
145
+ return
146
+ if dock.active and ui_events.is_running:
147
+ ui_events.emit_nowait(MarkdownAppended(content=content))
148
+ return
149
+ if dock.active and dock.capture(lambda console: console.print(Markdown(content))):
150
+ return
151
+ self._console.print(Markdown(content))
152
+
153
+ def thinking(self, text: str) -> None:
154
+ if self._emit_command_output(lambda console: console.print(Text(text, style="dim italic"))):
155
+ return
156
+ if dock.active and ui_events.is_running:
157
+ captured = _capture_ansi(
158
+ self._console.width,
159
+ lambda console: console.print(Text(text, style="dim italic")),
160
+ )
161
+ if captured:
162
+ ui_events.emit_nowait(AnsiAppended(text=captured))
163
+ return
164
+ if dock.active and dock.capture(lambda console: console.print(Text(text, style="dim italic"))):
165
+ return
166
+ self._console.print(Text(text, style="dim italic"))
167
+
168
+ def tool_call(self, tool_name: str, args: dict[str, object]) -> None:
169
+ if not self._debug:
170
+ self._pending_tools.setdefault(tool_name, []).append(args)
171
+ return
172
+ gerund = _title(self._TOOL_GERUND.get(tool_name, tool_name + "ing"))
173
+ if dock.active and ui_events.is_running:
174
+ event_id = _event_tool_id(tool_name)
175
+ self._event_tool_ids.setdefault(tool_name, []).append(event_id)
176
+ ui_events.emit_nowait(ToolStarted(
177
+ tool_call_id=event_id,
178
+ tool_name=tool_name,
179
+ label=gerund,
180
+ args=_fmt_args(args),
181
+ raw_args=args,
182
+ ))
183
+ return
184
+ if dock.active:
185
+ dock.start_tool(gerund, _fmt_args(args), tool_name=tool_name, raw_args=args)
186
+ return
187
+ self.print(f" {_next_spin()} [bold]{gerund}[/bold]({_fmt_args(args)})")
188
+
189
+ def tool_done(self, tool_name: str, elapsed: float, ok: bool = True) -> None:
190
+ icon = _done_spin() if ok else "[red]●[/red]"
191
+ style = "green" if ok else "red"
192
+ label = _title(tool_name)
193
+ if not self._debug:
194
+ pending = self._pending_tools.get(tool_name, [])
195
+ args = pending.pop(0) if pending else {}
196
+ if not pending:
197
+ self._pending_tools.pop(tool_name, None)
198
+ elapsed_part = f" [dim]({elapsed:.1f}s)[/dim]" if elapsed >= 2 else ""
199
+ if dock.active and ui_events.is_running:
200
+ event_id = _event_tool_id(tool_name)
201
+ detail = _fmt_args_short(tool_name, args)
202
+ ui_events.emit_nowait(ToolStarted(
203
+ tool_call_id=event_id,
204
+ tool_name=tool_name,
205
+ label=label,
206
+ args=detail,
207
+ raw_args=args,
208
+ ))
209
+ ui_events.emit_nowait(ToolFinished(
210
+ tool_call_id=event_id,
211
+ label=label,
212
+ elapsed=elapsed,
213
+ ok=ok,
214
+ ))
215
+ return
216
+ if dock.active:
217
+ detail = _fmt_args_short(tool_name, args)
218
+ dock.start_tool(label, detail, tool_name=tool_name, raw_args=args)
219
+ dock.finish_tool(label, elapsed, ok)
220
+ return
221
+ self.print(
222
+ f" {icon} [{style}]{label}[/] [dim]{_fmt_args_short(tool_name, args)}[/]{elapsed_part}"
223
+ )
224
+ return
225
+ if dock.active and ui_events.is_running:
226
+ event_id = _pop_event_tool_id(self._event_tool_ids, tool_name)
227
+ if event_id:
228
+ ui_events.emit_nowait(ToolFinished(
229
+ tool_call_id=event_id,
230
+ label=label,
231
+ elapsed=elapsed,
232
+ ok=ok,
233
+ ))
234
+ return
235
+ if dock.active:
236
+ dock.finish_tool(label, elapsed, ok)
237
+ return
238
+ self.print(f" {icon} [{style}]{label}[/{style}] [dim]({elapsed:.1f}s)[/dim]")
239
+
240
+ def tool_result(self, text: str) -> None:
241
+ if dock.active and ui_events.is_running:
242
+ ui_events.emit_nowait(ToolResultAppended(text=text))
243
+ return
244
+ if dock.active:
245
+ dock.append_tool_result(text)
246
+ return
247
+ self.print(text)
248
+
249
+ def error(self, message: str) -> None:
250
+ if self._emit_command_output(
251
+ lambda console: console.print(Panel(message, border_style="red", title="error"))
252
+ ):
253
+ return
254
+ if dock.active and ui_events.is_running:
255
+ ui_events.emit_nowait(ErrorAppended(message=message))
256
+ return
257
+ if dock.active:
258
+ dock.append_error(message)
259
+ return
260
+ self._console.print(Panel(message, border_style="red", title="error"))
261
+
262
+ def warn(self, message: str) -> None:
263
+ self.print(f"[yellow]! {message}[/yellow]")
264
+
265
+ def sep(self) -> None:
266
+ w = self._console.width or 80
267
+ self.print("─" * w, style="dim")
268
+
269
+ def step_header(self, n: int, max_n: int, agent: str = "") -> None:
270
+ gerund = _title(self._AGENT_GERUND.get(agent, agent))
271
+ label = f"Agent step {n}/{max_n}" if agent == "orchestrator" else f"{gerund} {n}/{max_n}"
272
+ if dock.active and ui_events.is_running:
273
+ ui_events.emit_nowait(StatusUpdated(
274
+ status_id="agent:-1:progress",
275
+ label=label,
276
+ stage="agent_step",
277
+ ))
278
+ return
279
+ if not self._debug:
280
+ return
281
+ if dock.active:
282
+ return
283
+ self.print(f" {_next_spin()} [dim]{gerund} ({n}/{max_n})[/dim]")
284
+
285
+ def diff(self, diff_text: str, title: str = "") -> None:
286
+ from voidx.ui.diff import render_diff
287
+ if self._emit_command_output(lambda console: render_diff(console, diff_text, title)):
288
+ return
289
+ if dock.active and ui_events.is_running:
290
+ ui_events.emit_nowait(DiffAppended(diff_text=diff_text, title=title))
291
+ return
292
+ if dock.active and dock.capture(lambda console: render_diff(console, diff_text, title)):
293
+ return
294
+ render_diff(self._console, diff_text, title)
295
+
296
+
297
+ class TreeAwareConsole:
298
+ """Dual-write console: prints to Rich AND records to an OutputTree node.
299
+
300
+ Used by the main orchestrator agent for real-time output + tree building.
301
+ Sub-agents use CaptureConsole instead.
302
+ """
303
+
304
+ def __init__(self, rich_console: Console, tree, turn_node):
305
+ self._console = rich_console
306
+ self._tree = tree
307
+ self._turn = turn_node
308
+ self._current_tool = None
309
+
310
+ @property
311
+ def console(self) -> Console:
312
+ return self._console
313
+
314
+ def step_header(self, n: int, max_n: int, agent: str = "") -> None:
315
+ gerund = _title(VoidConsole._AGENT_GERUND.get(agent, agent))
316
+ self._console.print(f" {_next_spin()} [dim]{gerund} ({n}/{max_n})[/dim]")
317
+
318
+ def tool_call(self, tool_name: str, args: dict[str, object]) -> None:
319
+ gerund = _title(VoidConsole._TOOL_GERUND.get(tool_name, tool_name + "ing"))
320
+ dot = _next_spin()
321
+ self._console.print(
322
+ f" {dot} [bold]{gerund}[/bold]({fmt_args(args)})"
323
+ )
324
+ self._current_tool = self._tree.new_node(
325
+ parent=self._turn,
326
+ node_type="tool_call",
327
+ header=f"{dot} [bold]{gerund}[/bold]({fmt_args(args)})",
328
+ status="running",
329
+ collapsed=True,
330
+ )
331
+
332
+ def tool_done(self, tool_name: str, elapsed: float, ok: bool = True) -> None:
333
+ icon = _done_spin() if ok else "[red]●[/red]"
334
+ style = "green" if ok else "red"
335
+ label = _title(tool_name)
336
+ self._console.print(
337
+ f" {icon} [{style}]{label}[/{style}] [dim]({elapsed:.1f}s)[/dim]"
338
+ )
339
+ if self._current_tool:
340
+ self._current_tool.header += f" [{style}]{label} ({elapsed:.1f}s)[/{style}]"
341
+ self._current_tool.elapsed = elapsed
342
+ self._current_tool.status = "done" if ok else "error"
343
+ self._tree.mark_dirty()
344
+
345
+ def tool_result(self, text: str) -> None:
346
+ self._console.print(text)
347
+ if self._current_tool:
348
+ self._tree.new_node(
349
+ parent=self._current_tool,
350
+ node_type="tool_result",
351
+ body_lines=text.split("\n"),
352
+ collapsed=False,
353
+ )
354
+
355
+ def diff(self, diff_text: str, title: str = "") -> None:
356
+ from voidx.ui.diff import render_diff
357
+ render_diff(self._console, diff_text, title)
358
+ if self._current_tool:
359
+ lines = diff_text.split("\n")
360
+ if title:
361
+ lines.insert(0, f"[bold]{title}[/bold]")
362
+ self._tree.new_node(
363
+ parent=self._current_tool,
364
+ node_type="tool_result",
365
+ body_lines=lines,
366
+ collapsed=False,
367
+ )
368
+
369
+ def print(self, *args, **kwargs) -> None:
370
+ self._console.print(*args, **kwargs)
371
+
372
+ def warn(self, message: str) -> None:
373
+ self._console.print(f"[yellow]! {message}[/yellow]")
374
+
375
+ def error(self, message: str) -> None:
376
+ from rich.panel import Panel
377
+ self._console.print(Panel(message, border_style="red", title="error"))
378
+
379
+ def sep(self) -> None:
380
+ w = self._console.width or 80
381
+ self._console.print("─" * w, style="dim")
@@ -0,0 +1 @@
1
+ """Implementation parts for console rendering."""
@@ -0,0 +1,96 @@
1
+ """Shared console formatting helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from io import StringIO
7
+
8
+ from rich.console import Console
9
+
10
+ _SPIN_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
11
+ _spin_idx = 0
12
+ _ORANGE = "#EBCB8B" # Nord yellow-ish
13
+
14
+
15
+ def _next_spin() -> str:
16
+ global _spin_idx
17
+ _spin_idx = (_spin_idx + 1) % len(_SPIN_FRAMES)
18
+ return f"[{_ORANGE}]{_SPIN_FRAMES[_spin_idx]}[/]"
19
+
20
+
21
+ def _done_spin() -> str:
22
+ return "[#A3BE8C]●[/#A3BE8C]"
23
+
24
+
25
+ def _title(text: str) -> str:
26
+ return text[:1].upper() + text[1:] if text else text
27
+
28
+
29
+ def _escape_rich(s: str) -> str:
30
+ return s.replace("[", "\\[").replace("]", "\\]")
31
+
32
+
33
+ def _capture_ansi(width: int, render) -> str:
34
+ buffer = StringIO()
35
+ console = Console(
36
+ file=buffer,
37
+ force_terminal=True,
38
+ color_system="truecolor",
39
+ width=width or 80,
40
+ )
41
+ render(console)
42
+ return buffer.getvalue().rstrip("\n")
43
+
44
+
45
+ def _event_tool_id(tool_name: str) -> str:
46
+ return f"console:{tool_name}:{time.time_ns()}"
47
+
48
+
49
+ def _pop_event_tool_id(pending: dict[str, list[str]], tool_name: str) -> str:
50
+ ids = pending.get(tool_name, [])
51
+ event_id = ids.pop(0) if ids else ""
52
+ if not ids:
53
+ pending.pop(tool_name, None)
54
+ return event_id
55
+
56
+
57
+ def _fmt_args(args: dict[str, object]) -> str:
58
+ """Format tool args Claude Code style: key="value" inside parentheses."""
59
+ parts = []
60
+ for k, v in args.items():
61
+ s = str(v)
62
+ if len(s) > 60:
63
+ s = s[:57] + "..."
64
+ escaped = _escape_rich(s)
65
+ if isinstance(v, str):
66
+ parts.append(f'{k}="[cyan]{escaped}[/cyan]"')
67
+ else:
68
+ parts.append(f"{k}=[cyan]{escaped}[/cyan]")
69
+ return ", ".join(parts)
70
+
71
+
72
+ def _fmt_args_short(tool_name: str, args: dict[str, object]) -> str:
73
+ if tool_name in {"read", "write", "edit"}:
74
+ value = args.get("file_path")
75
+ return _escape_rich(str(value)) if value else ""
76
+ if tool_name == "glob":
77
+ value = args.get("pattern")
78
+ return _escape_rich(str(value)) if value else ""
79
+ if tool_name == "grep":
80
+ value = args.get("pattern")
81
+ include = args.get("include")
82
+ suffix = f" in {_escape_rich(str(include))}" if include else ""
83
+ return f"{_escape_rich(str(value))}{suffix}" if value else ""
84
+ if tool_name == "bash":
85
+ value = str(args.get("command", ""))
86
+ shortened = value[:77] + "..." if len(value) > 80 else value
87
+ return _escape_rich(shortened)
88
+ if tool_name == "agent":
89
+ return _escape_rich(str(args.get("agent") or ""))
90
+ if tool_name in {"webfetch", "websearch"}:
91
+ value = args.get("url") or args.get("query")
92
+ return _escape_rich(str(value)) if value else ""
93
+ return ""
94
+
95
+
96
+ fmt_args = _fmt_args