superqode 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
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