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