superqode 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ # Schema defined in agent_schema.py
2
+ # https://github.com/stakpak/stakpak
3
+
4
+ identity = "stakpak.dev"
5
+ name = "Stakpak"
6
+ short_name = "stakpak"
7
+ url = "https://github.com/stakpak/stakpak"
8
+ protocol = "acp"
9
+ author_name = "Stakpak"
10
+ author_url = "https://github.com/stakpak"
11
+ publisher_name = "SuperQode Team"
12
+ publisher_url = "https://github.com/SuperagenticAI/superqode"
13
+ type = "coding"
14
+ description = "An ACP-compatible agent focused on providing comprehensive code assistance and collaboration features."
15
+ tags = ["code-assistance", "collaboration", "acp"]
16
+ run_command."*" = "stakpak --acp"
17
+
18
+ help = '''
19
+ # Stakpak
20
+
21
+ **Comprehensive Code Assistance Agent**
22
+
23
+ Stakpak is an ACP-compatible agent focused on providing comprehensive code assistance and collaboration features for development teams.
24
+
25
+ ## Key Features
26
+
27
+ - **Code Assistance**: Comprehensive code analysis and suggestions
28
+ - **Collaboration**: Team collaboration features
29
+ - **ACP Compatible**: Full Agent Client Protocol support
30
+ - **Extensible**: Plugin system for custom functionality
31
+
32
+ ## Installation
33
+
34
+ Install Stakpak via pip:
35
+
36
+ ```bash
37
+ pip install stakpak
38
+ ```
39
+
40
+ **Requirements:**
41
+ - Python 3.8 or higher
42
+ - pip
43
+
44
+ ---
45
+
46
+ **GitHub**: https://github.com/stakpak/stakpak
47
+ '''
48
+
49
+ [actions."*".install]
50
+ command = "pip install stakpak"
51
+ description = "Install Stakpak"
@@ -0,0 +1,51 @@
1
+ # Schema defined in agent_schema.py
2
+ # https://github.com/vtcode/vtcode
3
+
4
+ identity = "vtcode.dev"
5
+ name = "VT Code"
6
+ short_name = "vtcode"
7
+ url = "https://github.com/vtcode/vtcode"
8
+ protocol = "acp"
9
+ author_name = "VT Code"
10
+ author_url = "https://github.com/vtcode"
11
+ publisher_name = "SuperQode Team"
12
+ publisher_url = "https://github.com/SuperagenticAI/superqode"
13
+ type = "coding"
14
+ description = "A versatile coding agent implementing ACP for seamless integration with compatible development environments."
15
+ tags = ["versatile", "acp", "integration"]
16
+ run_command."*" = "vtcode --acp"
17
+
18
+ help = '''
19
+ # VT Code
20
+
21
+ **Versatile Coding Agent**
22
+
23
+ VT Code is a versatile coding agent implementing ACP for seamless integration with compatible development environments.
24
+
25
+ ## Key Features
26
+
27
+ - **Versatile**: Works with multiple development environments
28
+ - **ACP Integration**: Full Agent Client Protocol support
29
+ - **Seamless Integration**: Easy integration with existing workflows
30
+ - **Cross-Platform**: Works on multiple operating systems
31
+
32
+ ## Installation
33
+
34
+ Install VT Code ACP adapter via npm:
35
+
36
+ ```bash
37
+ npm install -g vtcode-acp
38
+ ```
39
+
40
+ **Requirements:**
41
+ - Node.js (v16 or higher)
42
+ - npm
43
+
44
+ ---
45
+
46
+ **GitHub**: https://github.com/vtcode/vtcode
47
+ '''
48
+
49
+ [actions."*".install]
50
+ command = "npm install -g vtcode-acp"
51
+ description = "Install VT Code ACP adapter"
@@ -0,0 +1,266 @@
1
+ """Agent discovery system for SuperQode ACP integration."""
2
+
3
+ import asyncio
4
+ from importlib.resources import files
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from .schema import Agent
10
+
11
+ try:
12
+ import tomllib
13
+ except ImportError:
14
+ # Python < 3.12
15
+ import tomli as tomllib
16
+
17
+
18
+ class AgentReadError(Exception):
19
+ """Problem reading the agents."""
20
+
21
+
22
+ async def read_agents(include_registry: bool = False) -> dict[str, "Agent"]:
23
+ """Read agent information from agents/data directory with enhanced error handling.
24
+
25
+ Args:
26
+ include_registry: If True, merge with registry agents. Default False for backward compatibility.
27
+
28
+ Raises:
29
+ AgentReadError: If the files could not be read.
30
+
31
+ Returns:
32
+ A mapping of identity on to Agent dict.
33
+ """
34
+
35
+ def read_agents_sync() -> tuple[list["Agent"], list[str]]:
36
+ """Read agent information synchronously with error tracking.
37
+
38
+ Stored in agents/data directory.
39
+
40
+ Returns:
41
+ Tuple of (agents_list, warnings_list)
42
+ """
43
+ agents: list["Agent"] = []
44
+ warnings: list[str] = []
45
+
46
+ # Define search paths
47
+ search_paths = []
48
+
49
+ # Add filesystem path (primary source)
50
+ fs_data_dir = Path(__file__).parent / "data"
51
+ if fs_data_dir.exists():
52
+ search_paths.append(fs_data_dir)
53
+
54
+ # Try package data as secondary source
55
+ try:
56
+ package_data_path = files("superqode.agents.data")
57
+ # Convert to string path for Path constructor
58
+ package_data_path = Path(str(package_data_path))
59
+ if package_data_path.exists() and package_data_path not in search_paths:
60
+ search_paths.append(package_data_path)
61
+ except (ImportError, AttributeError, TypeError):
62
+ pass # Package data not available
63
+
64
+ # Also check for user-defined agent directories
65
+ user_agent_dir = Path.home() / ".superqode" / "agents"
66
+ if user_agent_dir.exists():
67
+ search_paths.append(user_agent_dir)
68
+
69
+ if not search_paths:
70
+ warnings.append("No agent data directories found")
71
+ return agents, warnings
72
+
73
+ # Read agents from all paths
74
+ for search_path in search_paths:
75
+ try:
76
+ for file in search_path.iterdir():
77
+ if file.name.endswith(".toml") and file.is_file():
78
+ try:
79
+ agent: "Agent" = tomllib.load(file.open("rb"))
80
+ if agent.get("active", True):
81
+ # Validate required fields
82
+ required_fields = ["identity", "name", "short_name", "protocol"]
83
+ missing_fields = [
84
+ field for field in required_fields if field not in agent
85
+ ]
86
+ if missing_fields:
87
+ warnings.append(
88
+ f"Agent {file.name}: missing required fields {missing_fields}"
89
+ )
90
+ continue
91
+
92
+ agents.append(agent)
93
+ else:
94
+ warnings.append(f"Agent {agent.get('name', file.name)} is disabled")
95
+ except tomllib.TOMLKitError as e:
96
+ warnings.append(f"Failed to parse {file.name}: {e}")
97
+ except Exception as e:
98
+ warnings.append(f"Error reading {file.name}: {e}")
99
+ except Exception as e:
100
+ warnings.append(f"Error reading directory {search_path}: {e}")
101
+
102
+ return agents, warnings
103
+
104
+ agents, warnings = await asyncio.to_thread(read_agents_sync)
105
+
106
+ # Log warnings if any
107
+ if warnings:
108
+ import sys
109
+
110
+ console = sys.modules.get("rich.console", None)
111
+ if console:
112
+ from rich.console import Console
113
+
114
+ console = Console()
115
+ for warning in warnings:
116
+ console.print(f"[yellow]Warning: {warning}[/yellow]")
117
+
118
+ agent_map = {agent["identity"]: agent for agent in agents}
119
+
120
+ # Merge with registry if requested
121
+ if include_registry:
122
+ # Import here to avoid circular dependency
123
+ from .acp_registry import get_all_registry_agents
124
+
125
+ # Get registry agents and merge
126
+ registry_agents = get_all_registry_agents()
127
+
128
+ # Convert registry agents to Agent format and merge
129
+ for identity, metadata in registry_agents.items():
130
+ # Skip if already in local agents
131
+ if identity in agent_map:
132
+ continue
133
+
134
+ # Convert registry metadata to Agent format
135
+ agent: "Agent" = {
136
+ "identity": metadata["identity"],
137
+ "name": metadata["name"],
138
+ "short_name": metadata["short_name"],
139
+ "url": metadata["url"],
140
+ "protocol": "acp",
141
+ "author_name": metadata["author_name"],
142
+ "author_url": metadata["author_url"],
143
+ "publisher_name": "SuperQode Team",
144
+ "publisher_url": "https://github.com/SuperagenticAI/superqode",
145
+ "type": "coding",
146
+ "description": metadata["description"],
147
+ "tags": [],
148
+ "help": f"# {metadata['name']}\n\n{metadata['description']}\n\n## Installation\n\n{metadata['installation_instructions']}\n\nRun: `{metadata['installation_command']}`",
149
+ "run_command": {"*": metadata["run_command"]},
150
+ "actions": {
151
+ "*": {
152
+ "install": {
153
+ "command": metadata["installation_command"],
154
+ "description": f"Install {metadata['name']}",
155
+ }
156
+ }
157
+ },
158
+ }
159
+
160
+ agent_map[identity] = agent
161
+
162
+ if not agent_map:
163
+ raise AgentReadError("No valid agents found in any data directory")
164
+
165
+ return agent_map
166
+
167
+
168
+ async def get_agent_by_identity_async(
169
+ identity: str, include_registry: bool = True
170
+ ) -> "Agent | None":
171
+ """Get a specific agent by identity (async version).
172
+
173
+ Args:
174
+ identity: The agent identity to look for.
175
+ include_registry: If True, also check registry. Default True.
176
+
177
+ Returns:
178
+ The agent dict if found, None otherwise.
179
+ """
180
+ agent_map = await read_agents(include_registry=include_registry)
181
+ return agent_map.get(identity)
182
+
183
+
184
+ async def get_agent_by_short_name_async(
185
+ short_name: str, include_registry: bool = True
186
+ ) -> "Agent | None":
187
+ """Get a specific agent by short name (async version).
188
+
189
+ Args:
190
+ short_name: The agent short name to look for.
191
+ include_registry: If True, also check registry. Default True.
192
+
193
+ Returns:
194
+ The agent dict if found, None otherwise.
195
+ """
196
+ agents = await read_agents(include_registry=include_registry)
197
+
198
+ for agent in agents.values():
199
+ if agent.get("short_name", "").lower() == short_name.lower():
200
+ return agent
201
+
202
+ return None
203
+
204
+
205
+ def get_agent_by_identity(identity: str) -> "Agent | None":
206
+ """Get a specific agent by identity.
207
+
208
+ Args:
209
+ identity: The agent identity to look for.
210
+
211
+ Returns:
212
+ The agent dict if found, None otherwise.
213
+ """
214
+ import asyncio
215
+
216
+ try:
217
+ # Try to get the current event loop
218
+ loop = asyncio.get_running_loop()
219
+ # If we're in an async context, we need to handle this differently
220
+ # For now, create a new event loop
221
+ new_loop = asyncio.new_event_loop()
222
+ asyncio.set_event_loop(new_loop)
223
+ try:
224
+ agent_map = new_loop.run_until_complete(read_agents())
225
+ return agent_map.get(identity)
226
+ finally:
227
+ new_loop.close()
228
+ except RuntimeError:
229
+ # No running loop, safe to use asyncio.run
230
+ agent_map = asyncio.run(read_agents())
231
+ return agent_map.get(identity)
232
+
233
+
234
+ def get_agent_by_short_name(short_name: str) -> "Agent | None":
235
+ """Get a specific agent by short name.
236
+
237
+ Args:
238
+ short_name: The agent short name to look for.
239
+
240
+ Returns:
241
+ The agent dict if found, None otherwise.
242
+ """
243
+ import asyncio
244
+
245
+ try:
246
+ # Try to get the current event loop
247
+ loop = asyncio.get_running_loop()
248
+ # If we're in an async context, we need to handle this differently
249
+ # For now, create a new event loop
250
+ new_loop = asyncio.new_event_loop()
251
+ asyncio.set_event_loop(new_loop)
252
+ try:
253
+ agents = new_loop.run_until_complete(read_agents())
254
+ for agent in agents.values():
255
+ if agent.get("short_name", "").lower() == short_name.lower():
256
+ return agent
257
+ return None
258
+ finally:
259
+ new_loop.close()
260
+ except RuntimeError:
261
+ # No running loop, safe to use asyncio.run
262
+ agents = asyncio.run(read_agents())
263
+ for agent in agents.values():
264
+ if agent.get("short_name", "").lower() == short_name.lower():
265
+ return agent
266
+ return None
@@ -0,0 +1,160 @@
1
+ """Agent messaging utilities for direct subprocess communication."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Tuple, Optional, Dict, Any
6
+
7
+
8
+ def map_model_to_opencode(model_name: str) -> str:
9
+ """Map user-friendly model names to OpenCode model identifiers.
10
+
11
+ OpenCode expects just the model name (e.g., "glm-4.7-free"),
12
+ NOT the provider/model format.
13
+ """
14
+ # Map user-friendly names to OpenCode model names
15
+ model_mapping = {
16
+ "glm-4.7": "glm-4.7-free",
17
+ "grok-code": "grok-code",
18
+ "gpt-5-nano": "gpt-5-nano",
19
+ "big-pickle": "big-pickle",
20
+ "minimax-m2.1": "minimax-m2.1-free",
21
+ # Strip provider prefix if present
22
+ "opencode/glm-4.7-free": "glm-4.7-free",
23
+ "opencode/grok-code": "grok-code",
24
+ "opencode/gpt-5-nano": "gpt-5-nano",
25
+ "opencode/big-pickle": "big-pickle",
26
+ "opencode/minimax-m2.1-free": "minimax-m2.1-free",
27
+ }
28
+
29
+ mapped = model_mapping.get(model_name, model_name)
30
+
31
+ # Ensure the result has the opencode/ prefix
32
+ if not mapped.startswith("opencode/"):
33
+ mapped = f"opencode/{mapped}"
34
+
35
+ return mapped
36
+
37
+
38
+ def send_message_to_agent(
39
+ agent: Dict[str, Any], message: str, model: Optional[str] = None, cwd: Optional[Path] = None
40
+ ) -> Tuple[bool, str, str]:
41
+ """
42
+ Send a message to an agent using subprocess.
43
+
44
+ Args:
45
+ agent: Agent data dictionary with 'short_name' and other metadata
46
+ message: The message to send to the agent
47
+ model: Optional model name to use (will be mapped for OpenCode)
48
+ cwd: Optional working directory for subprocess
49
+
50
+ Returns:
51
+ Tuple of (success: bool, stdout: str, stderr: str)
52
+ """
53
+ agent_name = agent.get("short_name", "unknown")
54
+
55
+ if agent_name == "opencode":
56
+ return _send_to_opencode(message, model, cwd)
57
+ else:
58
+ # For other agents, return not implemented
59
+ error_msg = f"Direct messaging not yet implemented for agent: {agent_name}"
60
+ return (False, "", error_msg)
61
+
62
+
63
+ def _send_to_opencode(
64
+ message: str, model: Optional[str] = None, cwd: Optional[Path] = None
65
+ ) -> Tuple[bool, str, str]:
66
+ """
67
+ Send a message to OpenCode using subprocess.
68
+
69
+ Args:
70
+ message: The message to send
71
+ model: Optional model name (will be mapped to OpenCode format)
72
+ cwd: Optional working directory
73
+
74
+ Returns:
75
+ Tuple of (success: bool, stdout: str, stderr: str)
76
+ """
77
+ # Build the command
78
+ cmd = ["opencode", "run", message]
79
+
80
+ # Note: Not specifying model for better performance and accuracy
81
+ # OpenCode tends to read config files when model is specified
82
+
83
+ # Execute
84
+ try:
85
+ import subprocess as sp
86
+
87
+ result = sp.run(cmd, capture_output=True, text=True, cwd=str(cwd) if cwd else None)
88
+
89
+ success = result.returncode == 0
90
+ return (success, result.stdout, result.stderr)
91
+
92
+ except FileNotFoundError:
93
+ error_msg = "OpenCode not found. Make sure it's installed and in your PATH."
94
+ return (False, "", error_msg)
95
+ except Exception as e:
96
+ error_msg = f"Error running OpenCode: {e}"
97
+ return (False, "", error_msg)
98
+
99
+
100
+ def send_to_role_agent(
101
+ agent_name: str, model: Optional[str], message: str, cwd: Optional[Path] = None
102
+ ) -> Tuple[bool, str, str]:
103
+ """
104
+ Send a message to an agent configured in a role.
105
+
106
+ This is a convenience function for role-based mode where we already know
107
+ the agent name and model from the role configuration.
108
+
109
+ Args:
110
+ agent_name: Name of the agent (e.g., "opencode")
111
+ model: Model to use (e.g., "glm-4.7", "grok-code")
112
+ message: The message to send
113
+ cwd: Optional working directory
114
+
115
+ Returns:
116
+ Tuple of (success: bool, stdout: str, stderr: str)
117
+ """
118
+ # Create a minimal agent dict
119
+ agent = {"short_name": agent_name}
120
+ return send_message_to_agent(agent, message, model, cwd)
121
+
122
+
123
+ # Import PersonaContext for type hints (lazy import to avoid circular deps)
124
+ from typing import TYPE_CHECKING
125
+
126
+ if TYPE_CHECKING:
127
+ from superqode.agents.persona import PersonaContext
128
+
129
+
130
+ def wrap_message_with_persona(message: str, persona_context: Optional["PersonaContext"]) -> str:
131
+ """Wrap a user message with persona context.
132
+
133
+ This function prepends the persona system prompt to the user's message,
134
+ allowing the AI model to respond with the appropriate role context.
135
+
136
+ Args:
137
+ message: The original user message
138
+ persona_context: Optional persona context to prepend. If None or
139
+ invalid, returns the original message unchanged.
140
+
141
+ Returns:
142
+ The wrapped message with persona context, or original if no context.
143
+ The format is: {system_prompt}{user_message}
144
+ """
145
+ # Handle None or invalid context gracefully
146
+ if persona_context is None:
147
+ return message
148
+
149
+ # Check if persona_context has required attributes
150
+ try:
151
+ system_prompt = persona_context.system_prompt
152
+ if not system_prompt:
153
+ return message
154
+ except (AttributeError, TypeError):
155
+ # Invalid context object, return original message
156
+ return message
157
+
158
+ # Combine persona context with user message
159
+ # The system_prompt already ends with "---\n" separator
160
+ return f"{system_prompt}{message}"
@@ -0,0 +1,166 @@
1
+ """Persona injection for role-based AI interactions.
2
+
3
+ This module provides functionality to inject role-based persona context
4
+ into messages sent to AI coding agents, enabling the model to respond
5
+ with the appropriate expertise and personality for the configured role.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Optional
10
+
11
+ # Import ResolvedRole type for type hints
12
+ from superqode.config.schema import ResolvedRole
13
+
14
+
15
+ @dataclass
16
+ class PersonaContext:
17
+ """Holds the constructed persona context for a role.
18
+
19
+ Attributes:
20
+ role_name: The full role identifier (e.g., "DEV.FULLSTACK")
21
+ role_description: Brief description of the role
22
+ job_description: Detailed job description from YAML config
23
+ system_prompt: The constructed system prompt for injection
24
+ is_valid: True if job_description was present and non-empty
25
+ """
26
+
27
+ role_name: str
28
+ role_description: str
29
+ job_description: str
30
+ system_prompt: str
31
+ is_valid: bool
32
+
33
+
34
+ def validate_job_description(job_description: Optional[str]) -> bool:
35
+ """Validate that a job_description is suitable for persona injection.
36
+
37
+ Args:
38
+ job_description: The job description string to validate
39
+
40
+ Returns:
41
+ True if job_description is a non-empty string (not None,
42
+ not empty string, not whitespace-only)
43
+ """
44
+ if job_description is None:
45
+ return False
46
+ if not isinstance(job_description, str):
47
+ return False
48
+ return len(job_description.strip()) > 0
49
+
50
+
51
+ def truncate_job_description(job_description: str, max_length: int = 60) -> str:
52
+ """Truncate a job description for display purposes.
53
+
54
+ Args:
55
+ job_description: The job description to truncate
56
+ max_length: Maximum length before truncation (default 60)
57
+
58
+ Returns:
59
+ The original string if under max_length, otherwise truncated
60
+ with "..." suffix
61
+ """
62
+ if not job_description:
63
+ return ""
64
+
65
+ # Get first line only for preview
66
+ first_line = job_description.split("\n")[0].strip()
67
+
68
+ if len(first_line) <= max_length:
69
+ return first_line
70
+
71
+ return first_line[:max_length] + "..."
72
+
73
+
74
+ class PersonaInjector:
75
+ """Constructs persona context from role configuration.
76
+
77
+ This class is responsible for building the system prompt that will
78
+ be injected into messages sent to AI coding agents.
79
+ """
80
+
81
+ # Template for full persona prompt (when job_description is available)
82
+ FULL_PROMPT_TEMPLATE = """[SYSTEM INSTRUCTION - MANDATORY PERSONA]
83
+ You ARE a {role_name} on a software development team. This is your identity.
84
+
85
+ Role: {role_description}
86
+
87
+ Your responsibilities and expertise:
88
+ {job_description}
89
+
90
+ CRITICAL RULES:
91
+ 1. When asked "who are you" or about your identity, describe yourself as this role - NOT as an AI model.
92
+ 2. DO NOT announce your role or identity during tasks. Just do the work silently and professionally.
93
+ 3. NEVER say things like "As a {role_name}..." or "I am a {role_name}..." while working.
94
+ 4. Focus on completing tasks efficiently without self-referential statements.
95
+ [END SYSTEM INSTRUCTION]
96
+
97
+ """
98
+
99
+ # Template for minimal persona prompt (when job_description is missing)
100
+ MINIMAL_PROMPT_TEMPLATE = """[SYSTEM INSTRUCTION - MANDATORY PERSONA]
101
+ You ARE a {role_name} ({role_description}) on a software development team. This is your identity.
102
+
103
+ CRITICAL RULES:
104
+ 1. When asked "who are you", describe yourself as this role - NOT as an AI model.
105
+ 2. DO NOT announce your role during tasks. Just do the work silently.
106
+ 3. NEVER say "As a {role_name}..." or "I am a {role_name}..." while working.
107
+ [END SYSTEM INSTRUCTION]
108
+
109
+ """
110
+
111
+ def build_persona(self, mode: str, role: str, resolved_role: ResolvedRole) -> PersonaContext:
112
+ """Build persona context from resolved role configuration.
113
+
114
+ Args:
115
+ mode: The mode name (e.g., "dev", "qe")
116
+ role: The role name (e.g., "fullstack", "api_tester")
117
+ resolved_role: The ResolvedRole object from config loader
118
+
119
+ Returns:
120
+ PersonaContext with constructed system prompt
121
+ """
122
+ role_name = f"{mode.upper()}.{role.upper()}"
123
+ role_description = resolved_role.description or ""
124
+ job_description = resolved_role.job_description or ""
125
+
126
+ is_valid = validate_job_description(job_description)
127
+
128
+ system_prompt = self.format_system_prompt(
129
+ role_name=role_name,
130
+ role_description=role_description,
131
+ job_description=job_description,
132
+ is_valid=is_valid,
133
+ )
134
+
135
+ return PersonaContext(
136
+ role_name=role_name,
137
+ role_description=role_description,
138
+ job_description=job_description,
139
+ system_prompt=system_prompt,
140
+ is_valid=is_valid,
141
+ )
142
+
143
+ def format_system_prompt(
144
+ self, role_name: str, role_description: str, job_description: str, is_valid: bool
145
+ ) -> str:
146
+ """Format the system prompt for injection.
147
+
148
+ Args:
149
+ role_name: The full role identifier
150
+ role_description: Brief description of the role
151
+ job_description: Detailed job description
152
+ is_valid: Whether job_description is valid
153
+
154
+ Returns:
155
+ A formatted string suitable for prepending to user messages
156
+ """
157
+ if is_valid:
158
+ return self.FULL_PROMPT_TEMPLATE.format(
159
+ role_name=role_name,
160
+ role_description=role_description,
161
+ job_description=job_description.strip(),
162
+ )
163
+ else:
164
+ return self.MINIMAL_PROMPT_TEMPLATE.format(
165
+ role_name=role_name, role_description=role_description
166
+ )