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
superqode/history.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperQode History Manager - Command & Session History
|
|
3
|
+
|
|
4
|
+
Manages command history with:
|
|
5
|
+
- JSON-based persistent storage
|
|
6
|
+
- Async file operations
|
|
7
|
+
- Search and filtering
|
|
8
|
+
- Session tracking
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import asyncio
|
|
15
|
+
from dataclasses import dataclass, field, asdict
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional, Dict, Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class HistoryEntry:
|
|
23
|
+
"""A single history entry."""
|
|
24
|
+
|
|
25
|
+
input: str
|
|
26
|
+
timestamp: float
|
|
27
|
+
session_id: Optional[str] = None
|
|
28
|
+
mode: Optional[str] = None
|
|
29
|
+
agent: Optional[str] = None
|
|
30
|
+
success: bool = True
|
|
31
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class SessionInfo:
|
|
36
|
+
"""Information about a session."""
|
|
37
|
+
|
|
38
|
+
session_id: str
|
|
39
|
+
started_at: datetime
|
|
40
|
+
ended_at: Optional[datetime] = None
|
|
41
|
+
mode: Optional[str] = None
|
|
42
|
+
agent: Optional[str] = None
|
|
43
|
+
command_count: int = 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class HistoryManager:
|
|
47
|
+
"""Manages command history with persistence."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, history_file: Optional[Path] = None):
|
|
50
|
+
self.history_file = history_file or Path.home() / ".superqode" / "history.jsonl"
|
|
51
|
+
self._entries: List[HistoryEntry] = []
|
|
52
|
+
self._loaded = False
|
|
53
|
+
self._current_session: Optional[str] = None
|
|
54
|
+
self._position = 0 # For navigation
|
|
55
|
+
|
|
56
|
+
async def load(self) -> bool:
|
|
57
|
+
"""Load history from file."""
|
|
58
|
+
if self._loaded:
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def _read_history() -> List[HistoryEntry]:
|
|
62
|
+
entries = []
|
|
63
|
+
try:
|
|
64
|
+
self.history_file.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
self.history_file.touch(exist_ok=True)
|
|
66
|
+
|
|
67
|
+
with self.history_file.open("r") as f:
|
|
68
|
+
for line in f:
|
|
69
|
+
line = line.strip()
|
|
70
|
+
if line:
|
|
71
|
+
try:
|
|
72
|
+
data = json.loads(line)
|
|
73
|
+
entries.append(HistoryEntry(**data))
|
|
74
|
+
except (json.JSONDecodeError, TypeError):
|
|
75
|
+
continue
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
return entries
|
|
79
|
+
|
|
80
|
+
self._entries = await asyncio.to_thread(_read_history)
|
|
81
|
+
self._loaded = True
|
|
82
|
+
self._position = len(self._entries)
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
async def append(
|
|
86
|
+
self,
|
|
87
|
+
input_text: str,
|
|
88
|
+
mode: Optional[str] = None,
|
|
89
|
+
agent: Optional[str] = None,
|
|
90
|
+
success: bool = True,
|
|
91
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
92
|
+
) -> HistoryEntry:
|
|
93
|
+
"""Append a new entry to history."""
|
|
94
|
+
if not input_text.strip():
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
entry = HistoryEntry(
|
|
98
|
+
input=input_text,
|
|
99
|
+
timestamp=datetime.now().timestamp(),
|
|
100
|
+
session_id=self._current_session,
|
|
101
|
+
mode=mode,
|
|
102
|
+
agent=agent,
|
|
103
|
+
success=success,
|
|
104
|
+
metadata=metadata or {},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._entries.append(entry)
|
|
108
|
+
self._position = len(self._entries)
|
|
109
|
+
|
|
110
|
+
# Write to file
|
|
111
|
+
def _write_entry():
|
|
112
|
+
try:
|
|
113
|
+
with self.history_file.open("a") as f:
|
|
114
|
+
f.write(json.dumps(asdict(entry)) + "\n")
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
await asyncio.to_thread(_write_entry)
|
|
119
|
+
return entry
|
|
120
|
+
|
|
121
|
+
def append_sync(
|
|
122
|
+
self,
|
|
123
|
+
input_text: str,
|
|
124
|
+
mode: Optional[str] = None,
|
|
125
|
+
agent: Optional[str] = None,
|
|
126
|
+
success: bool = True,
|
|
127
|
+
) -> Optional[HistoryEntry]:
|
|
128
|
+
"""Synchronous version of append."""
|
|
129
|
+
if not input_text.strip():
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
entry = HistoryEntry(
|
|
133
|
+
input=input_text,
|
|
134
|
+
timestamp=datetime.now().timestamp(),
|
|
135
|
+
session_id=self._current_session,
|
|
136
|
+
mode=mode,
|
|
137
|
+
agent=agent,
|
|
138
|
+
success=success,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
self._entries.append(entry)
|
|
142
|
+
self._position = len(self._entries)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
self.history_file.parent.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
with self.history_file.open("a") as f:
|
|
147
|
+
f.write(json.dumps(asdict(entry)) + "\n")
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
return entry
|
|
152
|
+
|
|
153
|
+
def get_previous(self) -> Optional[str]:
|
|
154
|
+
"""Get previous history entry (for up arrow)."""
|
|
155
|
+
if not self._entries or self._position <= 0:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
self._position -= 1
|
|
159
|
+
return self._entries[self._position].input
|
|
160
|
+
|
|
161
|
+
def get_next(self) -> Optional[str]:
|
|
162
|
+
"""Get next history entry (for down arrow)."""
|
|
163
|
+
if self._position >= len(self._entries) - 1:
|
|
164
|
+
self._position = len(self._entries)
|
|
165
|
+
return ""
|
|
166
|
+
|
|
167
|
+
self._position += 1
|
|
168
|
+
return self._entries[self._position].input
|
|
169
|
+
|
|
170
|
+
def reset_position(self) -> None:
|
|
171
|
+
"""Reset navigation position to end."""
|
|
172
|
+
self._position = len(self._entries)
|
|
173
|
+
|
|
174
|
+
def search(self, query: str, limit: int = 20) -> List[HistoryEntry]:
|
|
175
|
+
"""Search history entries."""
|
|
176
|
+
query_lower = query.lower()
|
|
177
|
+
results = []
|
|
178
|
+
|
|
179
|
+
for entry in reversed(self._entries):
|
|
180
|
+
if query_lower in entry.input.lower():
|
|
181
|
+
results.append(entry)
|
|
182
|
+
if len(results) >= limit:
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
return results
|
|
186
|
+
|
|
187
|
+
def get_recent(self, count: int = 20) -> List[HistoryEntry]:
|
|
188
|
+
"""Get most recent entries."""
|
|
189
|
+
return list(reversed(self._entries[-count:]))
|
|
190
|
+
|
|
191
|
+
def get_by_mode(self, mode: str, limit: int = 20) -> List[HistoryEntry]:
|
|
192
|
+
"""Get entries for a specific mode."""
|
|
193
|
+
results = []
|
|
194
|
+
for entry in reversed(self._entries):
|
|
195
|
+
if entry.mode == mode:
|
|
196
|
+
results.append(entry)
|
|
197
|
+
if len(results) >= limit:
|
|
198
|
+
break
|
|
199
|
+
return results
|
|
200
|
+
|
|
201
|
+
def get_by_agent(self, agent: str, limit: int = 20) -> List[HistoryEntry]:
|
|
202
|
+
"""Get entries for a specific agent."""
|
|
203
|
+
results = []
|
|
204
|
+
for entry in reversed(self._entries):
|
|
205
|
+
if entry.agent == agent:
|
|
206
|
+
results.append(entry)
|
|
207
|
+
if len(results) >= limit:
|
|
208
|
+
break
|
|
209
|
+
return results
|
|
210
|
+
|
|
211
|
+
def clear(self) -> None:
|
|
212
|
+
"""Clear all history."""
|
|
213
|
+
self._entries.clear()
|
|
214
|
+
self._position = 0
|
|
215
|
+
try:
|
|
216
|
+
self.history_file.unlink(missing_ok=True)
|
|
217
|
+
except Exception:
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
def set_session(self, session_id: str) -> None:
|
|
221
|
+
"""Set the current session ID."""
|
|
222
|
+
self._current_session = session_id
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def size(self) -> int:
|
|
226
|
+
"""Get the number of history entries."""
|
|
227
|
+
return len(self._entries)
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def entries(self) -> List[HistoryEntry]:
|
|
231
|
+
"""Get all entries."""
|
|
232
|
+
return self._entries.copy()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def render_history(
|
|
236
|
+
entries: List[HistoryEntry], console, limit: int = 20, show_metadata: bool = False
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Render history entries."""
|
|
239
|
+
from rich.text import Text
|
|
240
|
+
from rich.panel import Panel
|
|
241
|
+
from rich.box import ROUNDED
|
|
242
|
+
|
|
243
|
+
if not entries:
|
|
244
|
+
console.print(" [dim]No history[/dim]")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
header = Text()
|
|
248
|
+
header.append(" 📜 ", style="bold")
|
|
249
|
+
header.append("Command History", style="bold white")
|
|
250
|
+
header.append(f" ({len(entries)} entries)", style="dim")
|
|
251
|
+
|
|
252
|
+
console.print(Panel(header, border_style="#a855f7", box=ROUNDED, padding=(0, 1)))
|
|
253
|
+
|
|
254
|
+
for i, entry in enumerate(entries[:limit]):
|
|
255
|
+
line = Text()
|
|
256
|
+
|
|
257
|
+
# Timestamp
|
|
258
|
+
dt = datetime.fromtimestamp(entry.timestamp)
|
|
259
|
+
time_str = dt.strftime("%H:%M:%S")
|
|
260
|
+
line.append(f" {time_str} ", style="dim")
|
|
261
|
+
|
|
262
|
+
# Mode/Agent indicator
|
|
263
|
+
if entry.agent:
|
|
264
|
+
line.append(f"[{entry.agent}] ", style="bold cyan")
|
|
265
|
+
elif entry.mode:
|
|
266
|
+
line.append(f"[{entry.mode}] ", style="bold green")
|
|
267
|
+
|
|
268
|
+
# Success indicator
|
|
269
|
+
if not entry.success:
|
|
270
|
+
line.append("✗ ", style="red")
|
|
271
|
+
|
|
272
|
+
# Command
|
|
273
|
+
cmd = entry.input[:60] + "..." if len(entry.input) > 60 else entry.input
|
|
274
|
+
line.append(cmd, style="white")
|
|
275
|
+
|
|
276
|
+
console.print(line)
|
|
277
|
+
|
|
278
|
+
if len(entries) > limit:
|
|
279
|
+
console.print(f" [dim]... and {len(entries) - limit} more[/dim]")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""SuperOpt command-based runner for OSS."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from superopt import AgenticEnvironment, ExecutionTrace, SuperOpt
|
|
11
|
+
from superopt.core.environment import PromptConfig, RetrievalConfig, ToolSchema
|
|
12
|
+
|
|
13
|
+
from superqode.agent.system_prompts import SystemPromptLevel, get_system_prompt
|
|
14
|
+
from superqode.config import load_config
|
|
15
|
+
from superqode.tools.base import ToolRegistry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _pick_job_description(project_root: Path) -> str:
|
|
19
|
+
try:
|
|
20
|
+
config = load_config(project_root)
|
|
21
|
+
except Exception:
|
|
22
|
+
return "You are a QE agent."
|
|
23
|
+
|
|
24
|
+
qe_mode = config.team.modes.get("qe") if config.team else None
|
|
25
|
+
if qe_mode and qe_mode.roles:
|
|
26
|
+
for role in qe_mode.roles.values():
|
|
27
|
+
if role.enabled and role.job_description:
|
|
28
|
+
return role.job_description.strip()
|
|
29
|
+
|
|
30
|
+
if config.default and config.default.job_description:
|
|
31
|
+
return config.default.job_description.strip()
|
|
32
|
+
|
|
33
|
+
return "You are a QE agent."
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _build_environment(project_root: Path) -> AgenticEnvironment:
|
|
37
|
+
system_prompt = get_system_prompt(
|
|
38
|
+
level=SystemPromptLevel.STANDARD,
|
|
39
|
+
working_directory=project_root,
|
|
40
|
+
)
|
|
41
|
+
job_description = _pick_job_description(project_root)
|
|
42
|
+
|
|
43
|
+
prompt_config = PromptConfig(
|
|
44
|
+
system_prompt=system_prompt,
|
|
45
|
+
instruction_policy=job_description,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
registry = ToolRegistry.default()
|
|
49
|
+
tools: Dict[str, ToolSchema] = {}
|
|
50
|
+
for tool in registry.list():
|
|
51
|
+
tools[tool.name] = ToolSchema(
|
|
52
|
+
name=tool.name,
|
|
53
|
+
description=tool.description,
|
|
54
|
+
arguments=tool.parameters,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
retrieval_config = RetrievalConfig()
|
|
58
|
+
|
|
59
|
+
return AgenticEnvironment(
|
|
60
|
+
prompts=prompt_config,
|
|
61
|
+
tools=tools,
|
|
62
|
+
retrieval=retrieval_config,
|
|
63
|
+
memory=[],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _build_trace(result: Dict[str, Any]) -> ExecutionTrace:
|
|
68
|
+
findings = result.get("findings", [])
|
|
69
|
+
critical = [f for f in findings if f.get("severity") == "critical"]
|
|
70
|
+
tests_failed = result.get("tests_failed", 0)
|
|
71
|
+
status = result.get("status", "")
|
|
72
|
+
|
|
73
|
+
success = status == "completed" and tests_failed == 0 and not critical
|
|
74
|
+
failure_message = result.get("verdict") if not success else None
|
|
75
|
+
|
|
76
|
+
trace = ExecutionTrace(
|
|
77
|
+
task_description=f"QE session {result.get('session_id', 'unknown')}",
|
|
78
|
+
success=success,
|
|
79
|
+
failure_message=failure_message,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if tests_failed:
|
|
83
|
+
trace.test_failures = [f"{tests_failed} test(s) failed"]
|
|
84
|
+
|
|
85
|
+
errors = result.get("errors", [])
|
|
86
|
+
if errors:
|
|
87
|
+
trace.runtime_exceptions = list(errors)
|
|
88
|
+
|
|
89
|
+
return trace
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run_superopt(trace_path: Path, output_path: Path, project_root: Path) -> int:
|
|
93
|
+
try:
|
|
94
|
+
result = json.loads(trace_path.read_text())
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
raise SystemExit(f"Failed to read trace: {exc}") from exc
|
|
97
|
+
|
|
98
|
+
environment = _build_environment(project_root)
|
|
99
|
+
trace = _build_trace(result)
|
|
100
|
+
|
|
101
|
+
optimizer = SuperOpt(environment=environment)
|
|
102
|
+
optimizer.step(trace)
|
|
103
|
+
|
|
104
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
output_path.write_text(json.dumps(optimizer.environment.to_dict(), indent=2))
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main() -> None:
|
|
110
|
+
parser = argparse.ArgumentParser(description="Run SuperOpt optimization.")
|
|
111
|
+
parser.add_argument("--trace", required=True, help="Path to QE result JSON")
|
|
112
|
+
parser.add_argument("--out", required=True, help="Output path for environment JSON")
|
|
113
|
+
parser.add_argument("--project-root", default=".", help="Project root")
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
trace_path = Path(args.trace).resolve()
|
|
117
|
+
output_path = Path(args.out).resolve()
|
|
118
|
+
project_root = Path(args.project_root).resolve()
|
|
119
|
+
|
|
120
|
+
raise SystemExit(run_superopt(trace_path, output_path, project_root))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperQode Unified Logging System.
|
|
3
|
+
|
|
4
|
+
Provides consistent logging across all provider modes (ACP, BYOK, Local).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from superqode.logging.unified_log import (
|
|
8
|
+
LogVerbosity,
|
|
9
|
+
LogConfig,
|
|
10
|
+
LogEntry,
|
|
11
|
+
LogKind,
|
|
12
|
+
LogSource,
|
|
13
|
+
UnifiedLogger,
|
|
14
|
+
LogSink,
|
|
15
|
+
)
|
|
16
|
+
from superqode.logging.formatter import UnifiedLogFormatter
|
|
17
|
+
from superqode.logging.sinks import ConversationLogSink, BufferSink, CallbackSink
|
|
18
|
+
from superqode.logging.adapters import (
|
|
19
|
+
BYOKAdapter,
|
|
20
|
+
LocalAdapter,
|
|
21
|
+
ACPAdapter,
|
|
22
|
+
create_adapter,
|
|
23
|
+
)
|
|
24
|
+
from superqode.logging.integration import TUILoggerManager, create_tui_logger
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Core
|
|
28
|
+
"LogVerbosity",
|
|
29
|
+
"LogConfig",
|
|
30
|
+
"LogEntry",
|
|
31
|
+
"LogKind",
|
|
32
|
+
"LogSource",
|
|
33
|
+
"UnifiedLogger",
|
|
34
|
+
"LogSink",
|
|
35
|
+
# Formatting
|
|
36
|
+
"UnifiedLogFormatter",
|
|
37
|
+
# Sinks
|
|
38
|
+
"ConversationLogSink",
|
|
39
|
+
"BufferSink",
|
|
40
|
+
"CallbackSink",
|
|
41
|
+
# Adapters
|
|
42
|
+
"BYOKAdapter",
|
|
43
|
+
"LocalAdapter",
|
|
44
|
+
"ACPAdapter",
|
|
45
|
+
"create_adapter",
|
|
46
|
+
# TUI Integration
|
|
47
|
+
"TUILoggerManager",
|
|
48
|
+
"create_tui_logger",
|
|
49
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider Adapters for SuperQode Unified Logging.
|
|
3
|
+
|
|
4
|
+
These adapters bridge existing callback interfaces to the unified logging system.
|
|
5
|
+
Each adapter converts provider-specific events into LogEntry objects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING
|
|
11
|
+
import asyncio
|
|
12
|
+
|
|
13
|
+
from superqode.logging.unified_log import LogEntry, LogSource, UnifiedLogger
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from superqode.tools.base import ToolResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BYOKAdapter:
|
|
20
|
+
"""
|
|
21
|
+
Adapter for BYOK (LiteLLM Gateway) mode.
|
|
22
|
+
|
|
23
|
+
Bridges on_tool_call, on_tool_result, and on_thinking callbacks
|
|
24
|
+
to the unified logging system.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, logger: UnifiedLogger):
|
|
28
|
+
self.logger = logger
|
|
29
|
+
self._span_ids: dict[str, str] = {} # tool_name -> span_id
|
|
30
|
+
|
|
31
|
+
def on_tool_call(self, name: str, args: dict) -> None:
|
|
32
|
+
"""Handle tool call - emit to unified logger."""
|
|
33
|
+
span_id = self.logger.tool_call(name, args, source="byok")
|
|
34
|
+
self._span_ids[name] = span_id
|
|
35
|
+
|
|
36
|
+
def on_tool_result(self, name: str, result: Any) -> None:
|
|
37
|
+
"""Handle tool result - emit to unified logger."""
|
|
38
|
+
from superqode.tools.base import ToolResult
|
|
39
|
+
|
|
40
|
+
span_id = self._span_ids.pop(name, None)
|
|
41
|
+
|
|
42
|
+
if isinstance(result, ToolResult):
|
|
43
|
+
success = result.success
|
|
44
|
+
output = str(result.output) if result.output else ""
|
|
45
|
+
if not success and result.error:
|
|
46
|
+
output = str(result.error)
|
|
47
|
+
else:
|
|
48
|
+
success = True
|
|
49
|
+
output = str(result) if result else ""
|
|
50
|
+
|
|
51
|
+
self.logger.tool_result(name, output, success, source="byok", span_id=span_id)
|
|
52
|
+
|
|
53
|
+
async def on_thinking_async(self, text: str) -> None:
|
|
54
|
+
"""Handle thinking text - emit to unified logger."""
|
|
55
|
+
if text and text.strip():
|
|
56
|
+
self.logger.thinking(text, source="byok")
|
|
57
|
+
|
|
58
|
+
def on_thinking_sync(self, text: str) -> None:
|
|
59
|
+
"""Synchronous thinking handler."""
|
|
60
|
+
if text and text.strip():
|
|
61
|
+
self.logger.thinking(text, source="byok")
|
|
62
|
+
|
|
63
|
+
def get_callbacks(self) -> dict[str, Callable]:
|
|
64
|
+
"""Get callback functions for use with pure_mode."""
|
|
65
|
+
return {
|
|
66
|
+
"on_tool_call": self.on_tool_call,
|
|
67
|
+
"on_tool_result": self.on_tool_result,
|
|
68
|
+
"on_thinking": self.on_thinking_async,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LocalAdapter:
|
|
73
|
+
"""
|
|
74
|
+
Adapter for Local models (Ollama, etc.).
|
|
75
|
+
|
|
76
|
+
Handles streaming responses with code block detection and formatting.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, logger: UnifiedLogger):
|
|
80
|
+
self.logger = logger
|
|
81
|
+
self._response_buffer = ""
|
|
82
|
+
self._in_code_block = False
|
|
83
|
+
self._code_language = ""
|
|
84
|
+
self._code_buffer = ""
|
|
85
|
+
|
|
86
|
+
def on_tool_call(self, name: str, args: dict) -> None:
|
|
87
|
+
"""Handle tool call."""
|
|
88
|
+
self.logger.tool_call(name, args, source="local")
|
|
89
|
+
|
|
90
|
+
def on_tool_result(self, name: str, result: Any) -> None:
|
|
91
|
+
"""Handle tool result."""
|
|
92
|
+
from superqode.tools.base import ToolResult
|
|
93
|
+
|
|
94
|
+
if isinstance(result, ToolResult):
|
|
95
|
+
self.logger.tool_result(
|
|
96
|
+
name,
|
|
97
|
+
str(result.output) if result.output else "",
|
|
98
|
+
result.success,
|
|
99
|
+
source="local",
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
self.logger.tool_result(name, str(result), True, source="local")
|
|
103
|
+
|
|
104
|
+
async def on_thinking_async(self, text: str) -> None:
|
|
105
|
+
"""Handle thinking text."""
|
|
106
|
+
if text and text.strip():
|
|
107
|
+
self.logger.thinking(text, source="local")
|
|
108
|
+
|
|
109
|
+
def on_response_chunk(self, text: str) -> None:
|
|
110
|
+
"""Handle streaming response chunk with code detection."""
|
|
111
|
+
if not text:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
self._response_buffer += text
|
|
115
|
+
self.logger.response_chunk(text, source="local")
|
|
116
|
+
|
|
117
|
+
def on_response_complete(self) -> str:
|
|
118
|
+
"""Complete response and return full text."""
|
|
119
|
+
response = self._response_buffer
|
|
120
|
+
self._response_buffer = ""
|
|
121
|
+
self.logger.response_complete(source="local")
|
|
122
|
+
return response
|
|
123
|
+
|
|
124
|
+
def get_callbacks(self) -> dict[str, Callable]:
|
|
125
|
+
"""Get callback functions."""
|
|
126
|
+
return {
|
|
127
|
+
"on_tool_call": self.on_tool_call,
|
|
128
|
+
"on_tool_result": self.on_tool_result,
|
|
129
|
+
"on_thinking": self.on_thinking_async,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ACPAdapter:
|
|
134
|
+
"""
|
|
135
|
+
Adapter for ACP (Agent Client Protocol) mode.
|
|
136
|
+
|
|
137
|
+
Bridges ACP session updates to the unified logging system.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, logger: UnifiedLogger):
|
|
141
|
+
self.logger = logger
|
|
142
|
+
self._span_ids: dict[str, str] = {} # tool_call_id -> span_id
|
|
143
|
+
self._message_buffer = ""
|
|
144
|
+
|
|
145
|
+
async def on_message(self, text: str) -> None:
|
|
146
|
+
"""Handle agent message chunks."""
|
|
147
|
+
if text:
|
|
148
|
+
self._message_buffer += text
|
|
149
|
+
self.logger.response_chunk(text, source="acp", agent="Agent")
|
|
150
|
+
|
|
151
|
+
async def on_thinking(self, text: str) -> None:
|
|
152
|
+
"""Handle agent thinking."""
|
|
153
|
+
if text and text.strip():
|
|
154
|
+
# ACP thinking often comes with prefixes like [agent], strip them
|
|
155
|
+
clean_text = text
|
|
156
|
+
if clean_text.startswith("[agent] "):
|
|
157
|
+
clean_text = clean_text[8:]
|
|
158
|
+
elif clean_text.startswith("["):
|
|
159
|
+
# Remove other prefixes like [startup error], [model switch error], etc.
|
|
160
|
+
bracket_end = clean_text.find("] ")
|
|
161
|
+
if bracket_end > 0:
|
|
162
|
+
clean_text = clean_text[bracket_end + 2 :]
|
|
163
|
+
|
|
164
|
+
self.logger.thinking(clean_text, source="acp")
|
|
165
|
+
|
|
166
|
+
async def on_tool_call(self, tool_call: dict) -> None:
|
|
167
|
+
"""Handle ACP tool call."""
|
|
168
|
+
title = tool_call.get("title", "tool")
|
|
169
|
+
raw_input = tool_call.get("rawInput", {})
|
|
170
|
+
tool_call_id = tool_call.get("toolCallId", "")
|
|
171
|
+
|
|
172
|
+
span_id = self.logger.tool_call(title, raw_input, source="acp")
|
|
173
|
+
if tool_call_id:
|
|
174
|
+
self._span_ids[tool_call_id] = span_id
|
|
175
|
+
|
|
176
|
+
async def on_tool_update(self, update: dict) -> None:
|
|
177
|
+
"""Handle ACP tool update."""
|
|
178
|
+
status = update.get("status", "")
|
|
179
|
+
tool_call_id = update.get("toolCallId", "")
|
|
180
|
+
output = update.get("rawOutput") or update.get("output") or update.get("result")
|
|
181
|
+
|
|
182
|
+
span_id = self._span_ids.get(tool_call_id)
|
|
183
|
+
title = update.get("title", "tool")
|
|
184
|
+
|
|
185
|
+
if status in ("completed", "done", "success"):
|
|
186
|
+
self.logger.tool_result(
|
|
187
|
+
title, str(output) if output else "", True, source="acp", span_id=span_id
|
|
188
|
+
)
|
|
189
|
+
elif status in ("error", "failed"):
|
|
190
|
+
self.logger.tool_result(
|
|
191
|
+
title, str(output) if output else "failed", False, source="acp", span_id=span_id
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def on_session_complete(self) -> str:
|
|
195
|
+
"""Complete session and return full message."""
|
|
196
|
+
message = self._message_buffer
|
|
197
|
+
self._message_buffer = ""
|
|
198
|
+
self.logger.response_complete(source="acp", agent="Agent")
|
|
199
|
+
return message
|
|
200
|
+
|
|
201
|
+
def get_callbacks(self) -> dict[str, Callable]:
|
|
202
|
+
"""Get callback functions for use with ACPClient."""
|
|
203
|
+
return {
|
|
204
|
+
"on_message": self.on_message,
|
|
205
|
+
"on_thinking": self.on_thinking,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def create_adapter(
|
|
210
|
+
logger: UnifiedLogger,
|
|
211
|
+
source: LogSource,
|
|
212
|
+
) -> BYOKAdapter | LocalAdapter | ACPAdapter:
|
|
213
|
+
"""Create an adapter for the given source."""
|
|
214
|
+
if source == "acp":
|
|
215
|
+
return ACPAdapter(logger)
|
|
216
|
+
elif source == "local":
|
|
217
|
+
return LocalAdapter(logger)
|
|
218
|
+
else:
|
|
219
|
+
return BYOKAdapter(logger)
|