superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,875 @@
1
+ """
2
+ TUI Integration - Connect Enhanced Widgets to Main App.
3
+
4
+ This module provides the integration layer to use the new enhanced
5
+ widgets in the main SuperQode app. It handles:
6
+
7
+ 1. Tool call display with rich formatting
8
+ 2. Thinking/reasoning display with streaming
9
+ 3. Response formatting with markdown
10
+ 4. Connection status for ACP/BYOK
11
+ 5. Conversation history navigation
12
+ 6. Enhanced status bar
13
+
14
+ Usage in app_main.py:
15
+ from superqode.tui_integration import TUIEnhancer
16
+
17
+ class SuperQodeApp(App):
18
+ def on_mount(self):
19
+ self.tui = TUIEnhancer(self)
20
+ self.tui.setup()
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import asyncio
26
+ from dataclasses import dataclass, field
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+ from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
30
+ from time import monotonic
31
+
32
+ from rich.text import Text
33
+ from textual.containers import Container
34
+ from textual.widgets import Static
35
+
36
+ if TYPE_CHECKING:
37
+ from textual.app import App
38
+
39
+
40
+ @dataclass
41
+ class AgentSession:
42
+ """Tracks the current agent session state."""
43
+
44
+ # Connection
45
+ connected: bool = False
46
+ connection_type: str = "" # "acp", "byok", "local"
47
+ agent_name: str = ""
48
+ agent_version: str = ""
49
+ model_name: str = ""
50
+ provider: str = ""
51
+ session_id: str = ""
52
+ connected_at: Optional[datetime] = None
53
+
54
+ # Current operation
55
+ is_streaming: bool = False
56
+ is_thinking: bool = False
57
+ current_tool: str = ""
58
+
59
+ # Stats
60
+ message_count: int = 0
61
+ tool_count: int = 0
62
+ prompt_tokens: int = 0
63
+ completion_tokens: int = 0
64
+ total_cost: float = 0.0
65
+
66
+ # Files
67
+ files_read: List[str] = field(default_factory=list)
68
+ files_modified: List[str] = field(default_factory=list)
69
+
70
+
71
+ class TUIEnhancer:
72
+ """
73
+ Enhances the SuperQode TUI with rich widgets.
74
+
75
+ Provides a clean API for the main app to use the new widgets
76
+ for tool calls, thinking, responses, and status.
77
+ """
78
+
79
+ def __init__(self, app: "App"):
80
+ self.app = app
81
+ self.session = AgentSession()
82
+
83
+ # Widget references (set during setup)
84
+ self._tool_panel = None
85
+ self._thinking_panel = None
86
+ self._response_display = None
87
+ self._connection_indicator = None
88
+ self._status_bar = None
89
+ self._conversation_nav = None
90
+
91
+ # Callbacks
92
+ self._on_tool_click: Optional[Callable] = None
93
+ self._on_message_select: Optional[Callable] = None
94
+
95
+ def setup(self) -> None:
96
+ """
97
+ Set up the enhanced TUI components.
98
+
99
+ Call this in the app's on_mount() method.
100
+ """
101
+ # Import widgets lazily
102
+ from superqode.widgets import (
103
+ get_rich_tool_display,
104
+ get_thinking_display,
105
+ get_response_display,
106
+ get_connection_status,
107
+ get_enhanced_status_bar,
108
+ get_conversation_history,
109
+ )
110
+
111
+ # Get widget classes
112
+ (ToolCallPanel, _, ToolCallData, ToolKind, ToolState, CompactToolIndicator, *_) = (
113
+ get_rich_tool_display()
114
+ )
115
+
116
+ (ThinkingPanel, ExtendedThinkingPanel, ThinkingIndicator, *_) = get_thinking_display()
117
+
118
+ (ResponseDisplay, StreamingText, *_) = get_response_display()
119
+
120
+ (
121
+ ConnectionIndicator,
122
+ ConnectionPanel,
123
+ ModelSelector,
124
+ ConnectionInfo,
125
+ ConnectionType,
126
+ ConnectionState,
127
+ TokenUsage,
128
+ ) = get_connection_status()
129
+
130
+ (EnhancedStatusBar, MiniStatusIndicator, StatusBarState, AgentStatus, *_) = (
131
+ get_enhanced_status_bar()
132
+ )
133
+
134
+ (
135
+ ConversationTimeline,
136
+ MessageDetail,
137
+ ConversationNavigator,
138
+ HistoryMessage,
139
+ MessageType,
140
+ ) = get_conversation_history()
141
+
142
+ # Store classes for later use
143
+ self._classes = {
144
+ "ToolCallPanel": ToolCallPanel,
145
+ "ToolCallData": ToolCallData,
146
+ "ToolKind": ToolKind,
147
+ "ToolState": ToolState,
148
+ "ThinkingPanel": ThinkingPanel,
149
+ "ThinkingIndicator": ThinkingIndicator,
150
+ "ResponseDisplay": ResponseDisplay,
151
+ "ConnectionIndicator": ConnectionIndicator,
152
+ "ConnectionInfo": ConnectionInfo,
153
+ "ConnectionType": ConnectionType,
154
+ "ConnectionState": ConnectionState,
155
+ "TokenUsage": TokenUsage,
156
+ "EnhancedStatusBar": EnhancedStatusBar,
157
+ "AgentStatus": AgentStatus,
158
+ "ConversationNavigator": ConversationNavigator,
159
+ "HistoryMessage": HistoryMessage,
160
+ "MessageType": MessageType,
161
+ }
162
+
163
+ # ========================================================================
164
+ # CONNECTION STATUS
165
+ # ========================================================================
166
+
167
+ def connect_agent(
168
+ self,
169
+ agent_name: str,
170
+ model_name: str = "",
171
+ provider: str = "",
172
+ connection_type: str = "byok",
173
+ ) -> None:
174
+ """Record agent connection."""
175
+ self.session.connected = True
176
+ self.session.agent_name = agent_name
177
+ self.session.model_name = model_name
178
+ self.session.provider = provider
179
+ self.session.connection_type = connection_type
180
+ self.session.connected_at = datetime.now()
181
+ self.session.session_id = f"session-{int(datetime.now().timestamp())}"
182
+
183
+ self._update_status_bar()
184
+
185
+ def disconnect_agent(self) -> None:
186
+ """Record agent disconnection."""
187
+ self.session = AgentSession()
188
+ self._update_status_bar()
189
+
190
+ def _update_status_bar(self) -> None:
191
+ """Update the status bar with current session state."""
192
+ if self._status_bar:
193
+ AgentStatus = self._classes.get("AgentStatus")
194
+
195
+ status = AgentStatus.IDLE
196
+ if self.session.is_streaming:
197
+ status = AgentStatus.STREAMING
198
+ elif self.session.is_thinking:
199
+ status = AgentStatus.THINKING
200
+ elif self.session.current_tool:
201
+ status = AgentStatus.TOOL_CALL
202
+
203
+ self._status_bar.update_state(
204
+ connected=self.session.connected,
205
+ connection_type=self.session.connection_type,
206
+ agent_name=self.session.agent_name,
207
+ model_name=self.session.model_name,
208
+ provider=self.session.provider,
209
+ status=status,
210
+ tool_count=self.session.tool_count,
211
+ prompt_tokens=self.session.prompt_tokens,
212
+ completion_tokens=self.session.completion_tokens,
213
+ total_cost=self.session.total_cost,
214
+ message_count=self.session.message_count,
215
+ )
216
+
217
+ # ========================================================================
218
+ # TOOL CALLS
219
+ # ========================================================================
220
+
221
+ def start_tool(
222
+ self,
223
+ tool_id: str,
224
+ tool_name: str,
225
+ tool_kind: str,
226
+ arguments: Dict[str, Any] = None,
227
+ file_path: str = None,
228
+ ) -> None:
229
+ """Record start of a tool call."""
230
+ ToolCallData = self._classes.get("ToolCallData")
231
+ ToolKind = self._classes.get("ToolKind")
232
+ ToolState = self._classes.get("ToolState")
233
+
234
+ if not ToolCallData:
235
+ return
236
+
237
+ # Map tool kind string to enum
238
+ kind_map = {
239
+ "read": ToolKind.FILE_READ,
240
+ "write": ToolKind.FILE_WRITE,
241
+ "edit": ToolKind.FILE_EDIT,
242
+ "delete": ToolKind.FILE_DELETE,
243
+ "shell": ToolKind.SHELL,
244
+ "search": ToolKind.SEARCH,
245
+ "glob": ToolKind.GLOB,
246
+ "lsp": ToolKind.LSP,
247
+ "browser": ToolKind.BROWSER,
248
+ "mcp": ToolKind.MCP,
249
+ }
250
+ kind = kind_map.get(tool_kind.lower(), ToolKind.OTHER)
251
+
252
+ tool = ToolCallData(
253
+ id=tool_id,
254
+ name=tool_name,
255
+ kind=kind,
256
+ state=ToolState.RUNNING,
257
+ start_time=datetime.now(),
258
+ arguments=arguments or {},
259
+ file_path=file_path,
260
+ )
261
+
262
+ self.session.current_tool = tool_name
263
+ self.session.tool_count += 1
264
+
265
+ if self._tool_panel:
266
+ self._tool_panel.add_tool(tool)
267
+
268
+ self._update_status_bar()
269
+
270
+ def complete_tool(
271
+ self,
272
+ tool_id: str,
273
+ result: str = "",
274
+ error: str = "",
275
+ output: str = "",
276
+ exit_code: int = None,
277
+ ) -> None:
278
+ """Record completion of a tool call."""
279
+ self.session.current_tool = ""
280
+
281
+ if self._tool_panel:
282
+ self._tool_panel.complete_tool(tool_id, result=result, error=error)
283
+
284
+ self._update_status_bar()
285
+
286
+ def update_tool_diff(
287
+ self,
288
+ tool_id: str,
289
+ file_path: str,
290
+ old_content: str,
291
+ new_content: str,
292
+ ) -> None:
293
+ """Update a tool call with diff content."""
294
+ if self._tool_panel:
295
+ from superqode.widgets.rich_tool_display import DiffContent, detect_language
296
+
297
+ diff = DiffContent(
298
+ path=file_path,
299
+ old_text=old_content,
300
+ new_text=new_content,
301
+ language=detect_language(file_path),
302
+ )
303
+
304
+ self._tool_panel.update_tool(tool_id, diff=diff)
305
+
306
+ # ========================================================================
307
+ # THINKING / REASONING (with UnifiedThinkingManager support)
308
+ # ========================================================================
309
+
310
+ def start_thinking(self) -> None:
311
+ """Start thinking display."""
312
+ self.session.is_thinking = True
313
+
314
+ if self._thinking_panel:
315
+ self._thinking_panel.start_streaming()
316
+
317
+ self._update_status_bar()
318
+
319
+ def add_thought(self, text: str) -> None:
320
+ """Add a thought to the thinking display."""
321
+ if self._thinking_panel:
322
+ if self.session.is_thinking:
323
+ self._thinking_panel.append_chunk(text)
324
+ else:
325
+ self._thinking_panel.add_thought(text)
326
+
327
+ def complete_thinking(self) -> None:
328
+ """Complete thinking display."""
329
+ self.session.is_thinking = False
330
+
331
+ if self._thinking_panel:
332
+ self._thinking_panel.complete_thought()
333
+
334
+ self._update_status_bar()
335
+
336
+ def get_thinking_manager(self) -> Optional["UnifiedThinkingManager"]:
337
+ """
338
+ Get a UnifiedThinkingManager for routing thinking from all sources.
339
+
340
+ Returns:
341
+ UnifiedThinkingManager if thinking panel is available, None otherwise
342
+ """
343
+ if not self._thinking_panel:
344
+ return None
345
+
346
+ from superqode.widgets.thinking_display import UnifiedThinkingManager
347
+
348
+ return UnifiedThinkingManager(
349
+ panel=self._thinking_panel,
350
+ extended_panel=getattr(self, "_extended_thinking_panel", None),
351
+ indicator=getattr(self, "_thinking_indicator", None),
352
+ )
353
+
354
+ def create_thinking_callbacks(self, connection_type: str = "byok") -> Dict[str, Callable]:
355
+ """
356
+ Create thinking callbacks for different connection types.
357
+
358
+ This method creates the appropriate callbacks for routing thinking
359
+ content to the UI based on the connection type (ACP, BYOK, Local,
360
+ OpenResponses).
361
+
362
+ Args:
363
+ connection_type: Type of connection ("acp", "byok", "local", "openresponses")
364
+
365
+ Returns:
366
+ Dict with callback functions:
367
+ - on_thinking: Callback for thinking text
368
+ - on_thinking_chunk: Callback for streaming chunks
369
+ - on_thinking_complete: Callback for completion
370
+
371
+ Usage:
372
+ callbacks = tui.create_thinking_callbacks("byok")
373
+ agent_loop.on_thinking = callbacks["on_thinking"]
374
+ """
375
+ manager = self.get_thinking_manager()
376
+
377
+ if not manager:
378
+ # Fallback to basic logging if no panel
379
+ async def noop_thinking(text: str) -> None:
380
+ pass
381
+
382
+ return {
383
+ "on_thinking": noop_thinking,
384
+ "on_thinking_chunk": noop_thinking,
385
+ "on_thinking_complete": lambda: None,
386
+ }
387
+
388
+ # Import ThinkingSource
389
+ from superqode.widgets.thinking_display import ThinkingSource
390
+
391
+ # Map connection type to source
392
+ source_map = {
393
+ "acp": ThinkingSource.ACP,
394
+ "byok": ThinkingSource.BYOK,
395
+ "local": ThinkingSource.LOCAL,
396
+ "openresponses": ThinkingSource.OPEN_RESPONSES,
397
+ }
398
+ source = source_map.get(connection_type, ThinkingSource.BYOK)
399
+
400
+ # Start session
401
+ manager.start_session(source)
402
+
403
+ # Create callbacks based on connection type
404
+ if connection_type == "acp":
405
+
406
+ async def on_thinking(text: str) -> None:
407
+ self.session.is_thinking = True
408
+ self._update_status_bar()
409
+ await manager.handle_acp_thought(text)
410
+
411
+ return {
412
+ "on_thinking": on_thinking,
413
+ "on_thinking_chunk": on_thinking,
414
+ "on_thinking_complete": lambda: (
415
+ manager.complete_streaming(),
416
+ setattr(self.session, "is_thinking", False),
417
+ self._update_status_bar(),
418
+ ),
419
+ }
420
+
421
+ elif connection_type == "openresponses":
422
+
423
+ async def on_thinking_event(event: Dict[str, Any]) -> None:
424
+ self.session.is_thinking = True
425
+ self._update_status_bar()
426
+ await manager.handle_openresponses_event(event)
427
+ if event.get("type") == "response.reasoning.done":
428
+ self.session.is_thinking = False
429
+ self._update_status_bar()
430
+
431
+ return {
432
+ "on_thinking": on_thinking_event,
433
+ "on_thinking_chunk": on_thinking_event,
434
+ "on_thinking_complete": lambda: (
435
+ manager.complete_streaming(),
436
+ setattr(self.session, "is_thinking", False),
437
+ self._update_status_bar(),
438
+ ),
439
+ }
440
+
441
+ else: # byok, local
442
+
443
+ async def on_thinking_chunk(chunk: Any) -> None:
444
+ self.session.is_thinking = True
445
+ self._update_status_bar()
446
+ await manager.handle_byok_chunk(chunk)
447
+
448
+ async def on_thinking_text(text: str) -> None:
449
+ """Handle plain text thinking (for models that don't stream)."""
450
+ self.session.is_thinking = True
451
+ self._update_status_bar()
452
+
453
+ # Wrap text in a mock chunk
454
+ class MockChunk:
455
+ thinking_content = text
456
+
457
+ await manager.handle_byok_chunk(MockChunk())
458
+
459
+ return {
460
+ "on_thinking": on_thinking_text,
461
+ "on_thinking_chunk": on_thinking_chunk,
462
+ "on_thinking_complete": lambda: (
463
+ manager.complete_streaming(),
464
+ setattr(self.session, "is_thinking", False),
465
+ self._update_status_bar(),
466
+ ),
467
+ }
468
+
469
+ # ========================================================================
470
+ # RESPONSE
471
+ # ========================================================================
472
+
473
+ def start_response(self, agent_name: str = "", model_name: str = "") -> None:
474
+ """Start streaming response."""
475
+ self.session.is_streaming = True
476
+
477
+ if self._response_display:
478
+ self._response_display.clear()
479
+ self._response_display.agent_name = agent_name or self.session.agent_name
480
+ self._response_display.model_name = model_name or self.session.model_name
481
+
482
+ self._update_status_bar()
483
+
484
+ def append_response(self, text: str) -> None:
485
+ """Append text to streaming response."""
486
+ if self._response_display:
487
+ self._response_display.append_text(text)
488
+
489
+ def complete_response(self, token_count: int = 0) -> None:
490
+ """Complete the response."""
491
+ self.session.is_streaming = False
492
+ self.session.message_count += 1
493
+ self.session.completion_tokens += token_count
494
+
495
+ if self._response_display:
496
+ self._response_display.complete(token_count=token_count)
497
+
498
+ self._update_status_bar()
499
+
500
+ # ========================================================================
501
+ # TOKEN TRACKING
502
+ # ========================================================================
503
+
504
+ def add_tokens(
505
+ self,
506
+ prompt_tokens: int = 0,
507
+ completion_tokens: int = 0,
508
+ cost: float = 0.0,
509
+ ) -> None:
510
+ """Add token usage."""
511
+ self.session.prompt_tokens += prompt_tokens
512
+ self.session.completion_tokens += completion_tokens
513
+ self.session.total_cost += cost
514
+
515
+ self._update_status_bar()
516
+
517
+ # ========================================================================
518
+ # FILE TRACKING
519
+ # ========================================================================
520
+
521
+ def track_file_read(self, path: str) -> None:
522
+ """Track a file read."""
523
+ if path not in self.session.files_read:
524
+ self.session.files_read.append(path)
525
+
526
+ def track_file_modified(self, path: str) -> None:
527
+ """Track a file modification."""
528
+ if path not in self.session.files_modified:
529
+ self.session.files_modified.append(path)
530
+
531
+ # ========================================================================
532
+ # CONVERSATION HISTORY
533
+ # ========================================================================
534
+
535
+ def add_user_message(self, content: str) -> None:
536
+ """Add a user message to history."""
537
+ self.session.message_count += 1
538
+
539
+ if self._conversation_nav:
540
+ self._conversation_nav.set_counts(
541
+ self.session.message_count,
542
+ self.session.message_count - 1,
543
+ )
544
+
545
+ def add_assistant_message(
546
+ self,
547
+ content: str,
548
+ agent_name: str = "",
549
+ model_name: str = "",
550
+ token_count: int = 0,
551
+ duration_ms: float = 0,
552
+ ) -> None:
553
+ """Add an assistant message to history."""
554
+ self.session.message_count += 1
555
+
556
+ if self._conversation_nav:
557
+ self._conversation_nav.set_counts(
558
+ self.session.message_count,
559
+ self.session.message_count - 1,
560
+ )
561
+
562
+ # ========================================================================
563
+ # UTILITIES
564
+ # ========================================================================
565
+
566
+ def get_session_summary(self) -> Dict[str, Any]:
567
+ """Get summary of current session."""
568
+ return {
569
+ "connected": self.session.connected,
570
+ "agent": self.session.agent_name,
571
+ "model": self.session.model_name,
572
+ "provider": self.session.provider,
573
+ "messages": self.session.message_count,
574
+ "tools": self.session.tool_count,
575
+ "files_read": len(self.session.files_read),
576
+ "files_modified": len(self.session.files_modified),
577
+ "tokens": {
578
+ "prompt": self.session.prompt_tokens,
579
+ "completion": self.session.completion_tokens,
580
+ "total": self.session.prompt_tokens + self.session.completion_tokens,
581
+ },
582
+ "cost": self.session.total_cost,
583
+ }
584
+
585
+ def reset_session(self) -> None:
586
+ """Reset the session (keep connection)."""
587
+ connected = self.session.connected
588
+ agent_name = self.session.agent_name
589
+ model_name = self.session.model_name
590
+ provider = self.session.provider
591
+ connection_type = self.session.connection_type
592
+
593
+ self.session = AgentSession()
594
+
595
+ if connected:
596
+ self.session.connected = connected
597
+ self.session.agent_name = agent_name
598
+ self.session.model_name = model_name
599
+ self.session.provider = provider
600
+ self.session.connection_type = connection_type
601
+
602
+ if self._tool_panel:
603
+ self._tool_panel.clear()
604
+
605
+ if self._thinking_panel:
606
+ self._thinking_panel.clear()
607
+
608
+ self._update_status_bar()
609
+
610
+
611
+ # ============================================================================
612
+ # OPENCODE-STYLE UX HELPERS
613
+ # ============================================================================
614
+
615
+
616
+ def format_tool_call_opencode_style(
617
+ tool_name: str,
618
+ tool_kind: str,
619
+ arguments: Dict[str, Any],
620
+ status: str = "running",
621
+ ) -> Text:
622
+ """
623
+ Format a tool call in OpenCode's style.
624
+
625
+ Uses a clean, minimal style with:
626
+ - Tool icon based on kind
627
+ - File path prominently displayed
628
+ - Status indicator
629
+ """
630
+ result = Text()
631
+
632
+ # Icons similar to OpenCode
633
+ icons = {
634
+ "read": "📖",
635
+ "write": "✏️",
636
+ "edit": "🔧",
637
+ "shell": "💻",
638
+ "search": "🔍",
639
+ "glob": "📁",
640
+ }
641
+ icon = icons.get(tool_kind.lower(), "⚡")
642
+
643
+ # Status indicator
644
+ status_icons = {
645
+ "pending": "○",
646
+ "running": "◐",
647
+ "success": "✓",
648
+ "error": "✗",
649
+ }
650
+ status_icon = status_icons.get(status, "○")
651
+
652
+ status_colors = {
653
+ "pending": "#6b7280",
654
+ "running": "#fbbf24",
655
+ "success": "#22c55e",
656
+ "error": "#ef4444",
657
+ }
658
+ status_color = status_colors.get(status, "#6b7280")
659
+
660
+ result.append(f"{status_icon} ", style=f"bold {status_color}")
661
+ result.append(f"{icon} ", style="#a855f7")
662
+ result.append(tool_name, style="bold #e4e4e7")
663
+
664
+ # Show file path if present
665
+ path = arguments.get("path", arguments.get("file_path", arguments.get("filePath", "")))
666
+ if path:
667
+ result.append(f" {path}", style="#6b7280")
668
+
669
+ # Show command for shell
670
+ command = arguments.get("command", "")
671
+ if command:
672
+ cmd_short = command[:50] + "..." if len(command) > 50 else command
673
+ result.append(f" $ {cmd_short}", style="#a1a1aa")
674
+
675
+ return result
676
+
677
+
678
+ def format_thinking_opencode_style(thoughts: List[str]) -> Text:
679
+ """
680
+ Format thinking in OpenCode's style.
681
+
682
+ OpenCode shows thinking in a collapsible section with bullet points.
683
+ """
684
+ result = Text()
685
+
686
+ result.append("💭 ", style="bold #ec4899")
687
+ result.append("Thinking", style="italic #ec4899")
688
+ result.append(f" ({len(thoughts)} thoughts)\n", style="#6b7280")
689
+
690
+ for thought in thoughts[-5:]: # Show last 5
691
+ thought_short = thought[:100] + "..." if len(thought) > 100 else thought
692
+ result.append(f" • {thought_short}\n", style="italic #a1a1aa")
693
+
694
+ return result
695
+
696
+
697
+ def format_response_opencode_style(
698
+ text: str,
699
+ agent_name: str,
700
+ duration: float = 0,
701
+ token_count: int = 0,
702
+ ) -> Text:
703
+ """
704
+ Format response in OpenCode's style.
705
+
706
+ Uses clean typography with agent name header.
707
+ """
708
+ result = Text()
709
+
710
+ # Header
711
+ result.append("─" * 50 + "\n", style="#27272a")
712
+ result.append("🤖 ", style="#a855f7")
713
+ result.append(agent_name, style="bold #a855f7")
714
+
715
+ if duration > 0:
716
+ result.append(f" ({duration:.1f}s)", style="#6b7280")
717
+
718
+ if token_count > 0:
719
+ result.append(f" {token_count} tokens", style="#52525b")
720
+
721
+ result.append("\n─" * 50 + "\n\n", style="#27272a")
722
+
723
+ # Content
724
+ result.append(text, style="#e4e4e7")
725
+
726
+ return result
727
+
728
+
729
+ # ============================================================================
730
+ # WHAT'S MISSING FOR FULL CODING AGENT
731
+ # ============================================================================
732
+
733
+ MISSING_FEATURES = """
734
+ # Features Missing for Full Coding Agent
735
+
736
+ ## 1. CORE AGENT CAPABILITIES
737
+
738
+ ### File Operations
739
+ - [ ] Undo/redo for file changes (checkpoint system)
740
+ - [ ] File change preview before applying
741
+ - [ ] Batch file operations with atomic rollback
742
+ - [ ] File watching with auto-refresh
743
+
744
+ ### Code Intelligence
745
+ - [ ] Go-to-definition integration
746
+ - [ ] Find references
747
+ - [ ] Symbol search across codebase
748
+ - [ ] Inline code completion suggestions
749
+
750
+ ### Terminal
751
+ - [ ] True PTY with full terminal emulation ✓ (implemented)
752
+ - [ ] Multiple terminal sessions
753
+ - [ ] Terminal output streaming to agent
754
+ - [ ] Background task management
755
+
756
+ ## 2. TUI FEATURES
757
+
758
+ ### Display
759
+ - [x] Rich tool call display with diffs ✓
760
+ - [x] Thinking/reasoning display ✓
761
+ - [x] Streaming response formatting ✓
762
+ - [x] Connection status indicator ✓
763
+ - [ ] Split view (code + chat)
764
+ - [ ] Image display in terminal
765
+ - [ ] Inline file preview on hover
766
+
767
+ ### Navigation
768
+ - [x] Conversation history ✓
769
+ - [ ] Message search
770
+ - [ ] Jump to file from mention
771
+ - [ ] Breadcrumb navigation
772
+
773
+ ### Interaction
774
+ - [ ] Keyboard shortcuts for common actions
775
+ - [ ] Mouse support for clicking file paths
776
+ - [ ] Drag-and-drop file support
777
+ - [ ] Copy code blocks with one click
778
+
779
+ ## 3. PROVIDER INTEGRATION
780
+
781
+ ### ACP (Agent Client Protocol)
782
+ - [x] OpenCode connection ✓
783
+ - [x] OpenHands connection ✓
784
+ - [ ] Claude Code connection
785
+ - [ ] Cursor connection
786
+ - [ ] Auto-discovery of ACP agents
787
+
788
+ ### BYOK (Bring Your Own Key)
789
+ - [x] LiteLLM integration ✓
790
+ - [ ] Provider-specific features (streaming, tools)
791
+ - [ ] API key management UI
792
+ - [ ] Rate limit handling
793
+ - [ ] Cost tracking per provider
794
+
795
+ ## 4. SESSION MANAGEMENT
796
+
797
+ ### Persistence
798
+ - [x] Session save/restore ✓
799
+ - [x] Session forking ✓
800
+ - [x] Session sharing ✓
801
+ - [ ] Session templates
802
+ - [ ] Auto-save on crash
803
+
804
+ ### Context
805
+ - [ ] Project context injection
806
+ - [ ] Custom instructions per project
807
+ - [ ] Memory across sessions
808
+ - [ ] Context window management
809
+
810
+ ## 5. WORKFLOW FEATURES
811
+
812
+ ### Git Integration
813
+ - [x] Git-based snapshots ✓
814
+ - [ ] Commit message generation
815
+ - [ ] PR description generation
816
+ - [ ] Diff review mode
817
+ - [ ] Branch management
818
+
819
+ ### Testing
820
+ - [ ] Test runner integration
821
+ - [ ] Coverage visualization
822
+ - [ ] Test generation suggestions
823
+
824
+ ### Documentation
825
+ - [ ] README generation
826
+ - [ ] Docstring generation
827
+ - [ ] API documentation
828
+
829
+ ## 6. SAFETY & PERMISSIONS
830
+
831
+ ### Permissions
832
+ - [x] Rule-based permissions ✓
833
+ - [x] Permission preview ✓
834
+ - [ ] Dangerous command detection
835
+ - [ ] Sandbox mode for untrusted operations
836
+
837
+ ### Audit
838
+ - [ ] Action audit log
839
+ - [ ] Cost tracking
840
+ - [ ] Token usage analytics
841
+
842
+ ## PRIORITY IMPLEMENTATION ORDER:
843
+
844
+ 1. **High Priority** (Most impactful for UX):
845
+ - Split view (code + chat)
846
+ - Keyboard shortcuts
847
+ - File change preview
848
+ - Undo/redo system
849
+
850
+ 2. **Medium Priority** (Competitive features):
851
+ - Code intelligence integration
852
+ - Multiple terminal sessions
853
+ - Provider-specific features
854
+ - Context management
855
+
856
+ 3. **Lower Priority** (Nice to have):
857
+ - Image display
858
+ - Drag-and-drop
859
+ - Test integration
860
+ - Documentation generation
861
+ """
862
+
863
+
864
+ def get_missing_features() -> str:
865
+ """Get the list of missing features."""
866
+ return MISSING_FEATURES
867
+
868
+
869
+ def print_missing_features() -> None:
870
+ """Print the missing features to console."""
871
+ from rich.console import Console
872
+ from rich.markdown import Markdown
873
+
874
+ console = Console()
875
+ console.print(Markdown(MISSING_FEATURES))