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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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))
|