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/session.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""Session state management for SuperQode."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, List, Dict, Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SessionContext:
|
|
11
|
+
"""Tracks work context for handoff between agents."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.session_id = f"session_{int(time.time())}"
|
|
15
|
+
self.created_at = datetime.now()
|
|
16
|
+
self.updated_at = datetime.now()
|
|
17
|
+
self.current_role = None
|
|
18
|
+
self.previous_role = None
|
|
19
|
+
self.work_description = ""
|
|
20
|
+
self.files_modified = []
|
|
21
|
+
self.files_created = []
|
|
22
|
+
self.tasks_completed = []
|
|
23
|
+
self.tasks_pending = []
|
|
24
|
+
self.quality_issues = []
|
|
25
|
+
self.handoff_history = []
|
|
26
|
+
self.metadata = {}
|
|
27
|
+
|
|
28
|
+
def update_work_context(
|
|
29
|
+
self,
|
|
30
|
+
description: str,
|
|
31
|
+
files_modified: List[str] = None,
|
|
32
|
+
files_created: List[str] = None,
|
|
33
|
+
tasks_completed: List[str] = None,
|
|
34
|
+
tasks_pending: List[str] = None,
|
|
35
|
+
):
|
|
36
|
+
"""Update the current work context."""
|
|
37
|
+
self.work_description = description
|
|
38
|
+
self.updated_at = datetime.now()
|
|
39
|
+
|
|
40
|
+
if files_modified:
|
|
41
|
+
self.files_modified.extend(files_modified)
|
|
42
|
+
if files_created:
|
|
43
|
+
self.files_created.extend(files_created)
|
|
44
|
+
if tasks_completed:
|
|
45
|
+
self.tasks_completed.extend(tasks_completed)
|
|
46
|
+
if tasks_pending:
|
|
47
|
+
self.tasks_pending.extend(tasks_pending)
|
|
48
|
+
|
|
49
|
+
def add_quality_issue(self, issue: str, severity: str = "medium"):
|
|
50
|
+
"""Add a quality issue found during review."""
|
|
51
|
+
self.quality_issues.append(
|
|
52
|
+
{
|
|
53
|
+
"issue": issue,
|
|
54
|
+
"severity": severity,
|
|
55
|
+
"timestamp": datetime.now().isoformat(),
|
|
56
|
+
"resolved": False,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def resolve_quality_issue(self, index: int):
|
|
61
|
+
"""Mark a quality issue as resolved."""
|
|
62
|
+
if 0 <= index < len(self.quality_issues):
|
|
63
|
+
self.quality_issues[index]["resolved"] = True
|
|
64
|
+
self.quality_issues[index]["resolved_at"] = datetime.now().isoformat()
|
|
65
|
+
|
|
66
|
+
def record_handoff(self, from_role: str, to_role: str, reason: str = ""):
|
|
67
|
+
"""Record a handoff event in history."""
|
|
68
|
+
self.handoff_history.append(
|
|
69
|
+
{
|
|
70
|
+
"timestamp": datetime.now().isoformat(),
|
|
71
|
+
"from_role": from_role,
|
|
72
|
+
"to_role": to_role,
|
|
73
|
+
"reason": reason,
|
|
74
|
+
"work_description": self.work_description,
|
|
75
|
+
"quality_issues_count": len([i for i in self.quality_issues if not i["resolved"]]),
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
self.previous_role = from_role
|
|
79
|
+
self.current_role = to_role
|
|
80
|
+
|
|
81
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
82
|
+
"""Serialize to dictionary for storage."""
|
|
83
|
+
return {
|
|
84
|
+
"session_id": self.session_id,
|
|
85
|
+
"created_at": self.created_at.isoformat(),
|
|
86
|
+
"updated_at": self.updated_at.isoformat(),
|
|
87
|
+
"current_role": self.current_role,
|
|
88
|
+
"previous_role": self.previous_role,
|
|
89
|
+
"work_description": self.work_description,
|
|
90
|
+
"files_modified": self.files_modified,
|
|
91
|
+
"files_created": self.files_created,
|
|
92
|
+
"tasks_completed": self.tasks_completed,
|
|
93
|
+
"tasks_pending": self.tasks_pending,
|
|
94
|
+
"quality_issues": self.quality_issues,
|
|
95
|
+
"handoff_history": self.handoff_history,
|
|
96
|
+
"metadata": self.metadata,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SessionContext":
|
|
101
|
+
"""Deserialize from dictionary."""
|
|
102
|
+
context = cls()
|
|
103
|
+
context.session_id = data.get("session_id", f"session_{int(time.time())}")
|
|
104
|
+
context.created_at = (
|
|
105
|
+
datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now()
|
|
106
|
+
)
|
|
107
|
+
context.updated_at = (
|
|
108
|
+
datetime.fromisoformat(data["updated_at"]) if "updated_at" in data else datetime.now()
|
|
109
|
+
)
|
|
110
|
+
context.current_role = data.get("current_role")
|
|
111
|
+
context.previous_role = data.get("previous_role")
|
|
112
|
+
context.work_description = data.get("work_description", "")
|
|
113
|
+
context.files_modified = data.get("files_modified", [])
|
|
114
|
+
context.files_created = data.get("files_created", [])
|
|
115
|
+
context.tasks_completed = data.get("tasks_completed", [])
|
|
116
|
+
context.tasks_pending = data.get("tasks_pending", [])
|
|
117
|
+
context.quality_issues = data.get("quality_issues", [])
|
|
118
|
+
context.handoff_history = data.get("handoff_history", [])
|
|
119
|
+
context.metadata = data.get("metadata", {})
|
|
120
|
+
return context
|
|
121
|
+
|
|
122
|
+
def save_to_file(self, filepath: Path):
|
|
123
|
+
"""Save context to JSON file."""
|
|
124
|
+
with open(filepath, "w") as f:
|
|
125
|
+
json.dump(self.to_dict(), f, indent=2, default=str)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def load_from_file(cls, filepath: Path) -> Optional["SessionContext"]:
|
|
129
|
+
"""Load context from JSON file."""
|
|
130
|
+
try:
|
|
131
|
+
with open(filepath, "r") as f:
|
|
132
|
+
data = json.load(f)
|
|
133
|
+
return cls.from_dict(data)
|
|
134
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class HandoffWorkflow:
|
|
139
|
+
"""Manages workflow transitions between development and QA roles."""
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
self.context_dir = Path.home() / ".superqode" / "sessions"
|
|
143
|
+
self.context_dir.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
def initiate_handoff(
|
|
146
|
+
self,
|
|
147
|
+
from_role: str,
|
|
148
|
+
to_role: str,
|
|
149
|
+
context: SessionContext,
|
|
150
|
+
reason: str = "",
|
|
151
|
+
additional_context: str = "",
|
|
152
|
+
) -> str:
|
|
153
|
+
"""Initiate a handoff between roles with context preservation."""
|
|
154
|
+
# Record the handoff
|
|
155
|
+
context.record_handoff(from_role, to_role, reason)
|
|
156
|
+
|
|
157
|
+
# Save current context
|
|
158
|
+
context_file = self.context_dir / f"{context.session_id}.json"
|
|
159
|
+
context.save_to_file(context_file)
|
|
160
|
+
|
|
161
|
+
# Generate handoff message
|
|
162
|
+
handoff_message = self._generate_handoff_message(
|
|
163
|
+
from_role, to_role, context, reason, additional_context
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return handoff_message
|
|
167
|
+
|
|
168
|
+
def _generate_handoff_message(
|
|
169
|
+
self,
|
|
170
|
+
from_role: str,
|
|
171
|
+
to_role: str,
|
|
172
|
+
context: SessionContext,
|
|
173
|
+
reason: str,
|
|
174
|
+
additional_context: str,
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Generate a comprehensive handoff message."""
|
|
177
|
+
message_parts = []
|
|
178
|
+
|
|
179
|
+
# Header
|
|
180
|
+
message_parts.append(f"🤝 **Handoff from {from_role} to {to_role}**")
|
|
181
|
+
message_parts.append(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
182
|
+
if reason:
|
|
183
|
+
message_parts.append(f"📝 Reason: {reason}")
|
|
184
|
+
message_parts.append("")
|
|
185
|
+
|
|
186
|
+
# Work description
|
|
187
|
+
if context.work_description:
|
|
188
|
+
message_parts.append("📋 **Work Completed:**")
|
|
189
|
+
message_parts.append(f"{context.work_description}")
|
|
190
|
+
message_parts.append("")
|
|
191
|
+
|
|
192
|
+
# Files changed
|
|
193
|
+
if context.files_modified or context.files_created:
|
|
194
|
+
message_parts.append("📁 **Files Involved:**")
|
|
195
|
+
for file in context.files_created:
|
|
196
|
+
message_parts.append(f" 🆕 {file}")
|
|
197
|
+
for file in context.files_modified:
|
|
198
|
+
message_parts.append(f" ✏️ {file}")
|
|
199
|
+
message_parts.append("")
|
|
200
|
+
|
|
201
|
+
# Tasks
|
|
202
|
+
if context.tasks_completed:
|
|
203
|
+
message_parts.append("✅ **Tasks Completed:**")
|
|
204
|
+
for task in context.tasks_completed:
|
|
205
|
+
message_parts.append(f" • {task}")
|
|
206
|
+
message_parts.append("")
|
|
207
|
+
|
|
208
|
+
if context.tasks_pending:
|
|
209
|
+
message_parts.append("⏳ **Tasks Pending:**")
|
|
210
|
+
for task in context.tasks_pending:
|
|
211
|
+
message_parts.append(f" • {task}")
|
|
212
|
+
message_parts.append("")
|
|
213
|
+
|
|
214
|
+
# Quality issues
|
|
215
|
+
unresolved_issues = [i for i in context.quality_issues if not i["resolved"]]
|
|
216
|
+
if unresolved_issues:
|
|
217
|
+
message_parts.append("⚠️ **Quality Issues Found:**")
|
|
218
|
+
severity_emojis = {"low": "🟢", "medium": "🟡", "high": "🔴", "critical": "💥"}
|
|
219
|
+
for i, issue in enumerate(unresolved_issues):
|
|
220
|
+
emoji = severity_emojis.get(issue["severity"], "🟡")
|
|
221
|
+
message_parts.append(f" {emoji} {issue['issue']}")
|
|
222
|
+
message_parts.append("")
|
|
223
|
+
|
|
224
|
+
# Context for recipient
|
|
225
|
+
role_contexts = {
|
|
226
|
+
"dev.fullstack": "Please review the implementation for code quality, security, and best practices.",
|
|
227
|
+
"qa.api_tester": "Please test the functionality, validate requirements, and identify any issues.",
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if to_role in role_contexts:
|
|
231
|
+
message_parts.append(f"🎯 **Your Role:** {role_contexts[to_role]}")
|
|
232
|
+
|
|
233
|
+
# Additional context
|
|
234
|
+
if additional_context:
|
|
235
|
+
message_parts.append("")
|
|
236
|
+
message_parts.append("📎 **Additional Context:**")
|
|
237
|
+
message_parts.append(additional_context)
|
|
238
|
+
|
|
239
|
+
return "\n".join(message_parts)
|
|
240
|
+
|
|
241
|
+
def get_pending_handoffs(self) -> List[Dict[str, Any]]:
|
|
242
|
+
"""Get list of pending handoffs that need attention."""
|
|
243
|
+
pending = []
|
|
244
|
+
for context_file in self.context_dir.glob("*.json"):
|
|
245
|
+
context = SessionContext.load_from_file(context_file)
|
|
246
|
+
if context:
|
|
247
|
+
# Show handoffs that are not yet approved
|
|
248
|
+
if not context.metadata.get("approved", False):
|
|
249
|
+
pending.append(
|
|
250
|
+
{
|
|
251
|
+
"session_id": context.session_id,
|
|
252
|
+
"current_role": context.current_role,
|
|
253
|
+
"work_description": context.work_description,
|
|
254
|
+
"pending_tasks": len(context.tasks_pending),
|
|
255
|
+
"quality_issues": len(
|
|
256
|
+
[i for i in context.quality_issues if not i["resolved"]]
|
|
257
|
+
),
|
|
258
|
+
"last_updated": context.updated_at,
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
return sorted(pending, key=lambda x: x["last_updated"], reverse=True)
|
|
262
|
+
|
|
263
|
+
def approve_work(self, session_id: str, approval_notes: str = "") -> bool:
|
|
264
|
+
"""Approve work for deployment."""
|
|
265
|
+
context_file = self.context_dir / f"{session_id}.json"
|
|
266
|
+
context = SessionContext.load_from_file(context_file)
|
|
267
|
+
|
|
268
|
+
if not context:
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
# Mark all quality issues as resolved
|
|
272
|
+
for issue in context.quality_issues:
|
|
273
|
+
if not issue["resolved"]:
|
|
274
|
+
issue["resolved"] = True
|
|
275
|
+
issue["resolved_at"] = datetime.now().isoformat()
|
|
276
|
+
issue["approved_by"] = context.current_role
|
|
277
|
+
|
|
278
|
+
# Clear pending tasks
|
|
279
|
+
context.tasks_pending.clear()
|
|
280
|
+
|
|
281
|
+
# Add approval metadata
|
|
282
|
+
context.metadata["approved"] = True
|
|
283
|
+
context.metadata["approved_at"] = datetime.now().isoformat()
|
|
284
|
+
context.metadata["approved_by"] = context.current_role
|
|
285
|
+
context.metadata["approval_notes"] = approval_notes
|
|
286
|
+
|
|
287
|
+
# Save updated context
|
|
288
|
+
context.save_to_file(context_file)
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class SessionState:
|
|
293
|
+
"""Manages the current session state and agent connections."""
|
|
294
|
+
|
|
295
|
+
def __init__(self):
|
|
296
|
+
self.state = "superqode" # "superqode" | "agent_connected" | "role_mode"
|
|
297
|
+
self.connected_agent = None # Agent data when in agent_connected state
|
|
298
|
+
self.agent_role_info = None # Role info when connected via role
|
|
299
|
+
self.current_context = SessionContext() # Current work context
|
|
300
|
+
self.handoff_workflow = HandoffWorkflow() # Handoff management
|
|
301
|
+
self.acp_manager = None # ACP agent manager for real connections
|
|
302
|
+
self.selected_model = None # Selected model for agent
|
|
303
|
+
self.current_mode = "home" # Current mode name (for TUI/non-TUI compatibility)
|
|
304
|
+
|
|
305
|
+
def connect_to_agent(self, agent_data, role_info=None, model=None):
|
|
306
|
+
"""Connect to an agent directly (bypassing roles)"""
|
|
307
|
+
self.state = "agent_connected"
|
|
308
|
+
self.connected_agent = agent_data
|
|
309
|
+
self.agent_role_info = role_info
|
|
310
|
+
self.selected_model = model # Store selected model for direct connections
|
|
311
|
+
|
|
312
|
+
def set_acp_manager(self, manager):
|
|
313
|
+
"""Set the active ACP manager for real-time communication"""
|
|
314
|
+
self.acp_manager = manager
|
|
315
|
+
|
|
316
|
+
def disconnect_acp_manager(self):
|
|
317
|
+
"""Disconnect the ACP manager"""
|
|
318
|
+
if self.acp_manager:
|
|
319
|
+
import asyncio
|
|
320
|
+
|
|
321
|
+
asyncio.run(self.acp_manager.disconnect())
|
|
322
|
+
self.acp_manager = None
|
|
323
|
+
|
|
324
|
+
def disconnect_agent(self):
|
|
325
|
+
"""Disconnect from agent and return to superqode mode"""
|
|
326
|
+
self.state = "superqode"
|
|
327
|
+
self.connected_agent = None
|
|
328
|
+
self.agent_role_info = None
|
|
329
|
+
self.selected_model = None
|
|
330
|
+
self.current_mode = "home"
|
|
331
|
+
|
|
332
|
+
def switch_to_role_mode(self, mode):
|
|
333
|
+
"""Switch to role-based mode"""
|
|
334
|
+
self.state = "role_mode"
|
|
335
|
+
self.current_mode = mode
|
|
336
|
+
|
|
337
|
+
# Check for pending handoffs for this role
|
|
338
|
+
pending = self.get_pending_handoffs()
|
|
339
|
+
role_handoffs = [h for h in pending if h["current_role"] == mode]
|
|
340
|
+
|
|
341
|
+
if role_handoffs:
|
|
342
|
+
# Automatically resume the most recent handoff for this role
|
|
343
|
+
latest_handoff = role_handoffs[0] # Already sorted by updated_at desc
|
|
344
|
+
if self.load_context_from_session(latest_handoff["session_id"]):
|
|
345
|
+
print(f"🤝 Resumed pending handoff: {latest_handoff['work_description'][:50]}...")
|
|
346
|
+
return True
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
def is_connected_to_agent(self):
|
|
350
|
+
"""Check if currently connected to an agent"""
|
|
351
|
+
return self.state == "agent_connected" and self.connected_agent is not None
|
|
352
|
+
|
|
353
|
+
def get_prompt_suffix(self):
|
|
354
|
+
"""Get the prompt suffix based on current state"""
|
|
355
|
+
if self.state == "agent_connected":
|
|
356
|
+
agent_name = (
|
|
357
|
+
self.connected_agent.get("short_name", "Unknown")
|
|
358
|
+
if self.connected_agent
|
|
359
|
+
else "Unknown"
|
|
360
|
+
)
|
|
361
|
+
return f"🔗 {agent_name.upper()}"
|
|
362
|
+
elif self.state == "role_mode":
|
|
363
|
+
return self.current_mode.replace(".", "/").upper()
|
|
364
|
+
else: # superqode
|
|
365
|
+
if self.current_mode == "home":
|
|
366
|
+
return "🏠 HOME"
|
|
367
|
+
else:
|
|
368
|
+
return self.current_mode.replace(".", "/").upper()
|
|
369
|
+
|
|
370
|
+
def get_connection_info(self):
|
|
371
|
+
"""Get detailed connection information for display"""
|
|
372
|
+
if not self.is_connected_to_agent():
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
info = {
|
|
376
|
+
"agent": self.connected_agent.get("name", "Unknown")
|
|
377
|
+
if self.connected_agent
|
|
378
|
+
else "Unknown",
|
|
379
|
+
"short_name": self.connected_agent.get("short_name", "unknown")
|
|
380
|
+
if self.connected_agent
|
|
381
|
+
else "unknown",
|
|
382
|
+
"type": self.connected_agent.get("type", "unknown")
|
|
383
|
+
if self.connected_agent
|
|
384
|
+
else "unknown",
|
|
385
|
+
"description": self.connected_agent.get("description", "")
|
|
386
|
+
if self.connected_agent
|
|
387
|
+
else "",
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# Add role info if connected via role
|
|
391
|
+
if self.agent_role_info:
|
|
392
|
+
info.update(
|
|
393
|
+
{
|
|
394
|
+
"role": self.agent_role_info.get("role", ""),
|
|
395
|
+
"provider": self.agent_role_info.get("provider", ""),
|
|
396
|
+
"model": self.agent_role_info.get("model", ""),
|
|
397
|
+
"job_description": self.agent_role_info.get("job_description", ""),
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return info
|
|
402
|
+
|
|
403
|
+
def update_context(
|
|
404
|
+
self,
|
|
405
|
+
description: str = None,
|
|
406
|
+
files_modified: List[str] = None,
|
|
407
|
+
files_created: List[str] = None,
|
|
408
|
+
tasks_completed: List[str] = None,
|
|
409
|
+
tasks_pending: List[str] = None,
|
|
410
|
+
):
|
|
411
|
+
"""Update the current work context."""
|
|
412
|
+
if description or files_modified or files_created or tasks_completed or tasks_pending:
|
|
413
|
+
self.current_context.update_work_context(
|
|
414
|
+
description or self.current_context.work_description,
|
|
415
|
+
files_modified,
|
|
416
|
+
files_created,
|
|
417
|
+
tasks_completed,
|
|
418
|
+
tasks_pending,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def add_quality_issue(self, issue: str, severity: str = "medium"):
|
|
422
|
+
"""Add a quality issue to the current context."""
|
|
423
|
+
self.current_context.add_quality_issue(issue, severity)
|
|
424
|
+
|
|
425
|
+
def resolve_quality_issue(self, index: int):
|
|
426
|
+
"""Resolve a quality issue by index."""
|
|
427
|
+
self.current_context.resolve_quality_issue(index)
|
|
428
|
+
|
|
429
|
+
def initiate_handoff(self, to_role: str, reason: str = "", additional_context: str = "") -> str:
|
|
430
|
+
"""Initiate a handoff to another role."""
|
|
431
|
+
from_role = self.get_current_role_name()
|
|
432
|
+
|
|
433
|
+
if not from_role:
|
|
434
|
+
return "Error: Not currently in a role mode for handoff"
|
|
435
|
+
|
|
436
|
+
handoff_message = self.handoff_workflow.initiate_handoff(
|
|
437
|
+
from_role, to_role, self.current_context, reason, additional_context
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
# Reset context for new role (but keep session ID)
|
|
441
|
+
old_session_id = self.current_context.session_id
|
|
442
|
+
self.current_context = SessionContext()
|
|
443
|
+
self.current_context.session_id = old_session_id
|
|
444
|
+
self.current_context.previous_role = from_role
|
|
445
|
+
self.current_context.current_role = to_role
|
|
446
|
+
|
|
447
|
+
return handoff_message
|
|
448
|
+
|
|
449
|
+
def approve_work(self, approval_notes: str = "") -> bool:
|
|
450
|
+
"""Approve current work for deployment."""
|
|
451
|
+
return self.handoff_workflow.approve_work(self.current_context.session_id, approval_notes)
|
|
452
|
+
|
|
453
|
+
def get_pending_handoffs(self) -> List[Dict[str, Any]]:
|
|
454
|
+
"""Get list of pending handoffs."""
|
|
455
|
+
return self.handoff_workflow.get_pending_handoffs()
|
|
456
|
+
|
|
457
|
+
def get_current_role_name(self) -> Optional[str]:
|
|
458
|
+
"""Get the current role name for handoffs."""
|
|
459
|
+
if self.state == "role_mode":
|
|
460
|
+
return self.current_mode
|
|
461
|
+
elif self.agent_role_info:
|
|
462
|
+
role = self.agent_role_info.get("role", "")
|
|
463
|
+
mode = self.agent_role_info.get("mode", "")
|
|
464
|
+
if mode and role:
|
|
465
|
+
return f"{mode}.{role}"
|
|
466
|
+
return None
|
|
467
|
+
|
|
468
|
+
def load_context_from_session(self, session_id: str) -> bool:
|
|
469
|
+
"""Load a previous session context."""
|
|
470
|
+
context_file = self.handoff_workflow.context_dir / f"{session_id}.json"
|
|
471
|
+
context = SessionContext.load_from_file(context_file)
|
|
472
|
+
if context:
|
|
473
|
+
self.current_context = context
|
|
474
|
+
return True
|
|
475
|
+
return False
|