mindroot 9.3.0__py3-none-any.whl → 9.6.0__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.
- mindroot/coreplugins/admin/__init__.py +3 -1
- mindroot/coreplugins/admin/agent_router.py +250 -7
- mindroot/coreplugins/admin/asset_manager.py +164 -0
- mindroot/coreplugins/admin/command_router.py +236 -1
- mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
- mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
- mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
- mindroot/coreplugins/admin/mcp_routes.py +216 -0
- mindroot/coreplugins/admin/mod.py +62 -0
- mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
- mindroot/coreplugins/admin/persona_handler.py +15 -6
- mindroot/coreplugins/admin/persona_router.py +158 -2
- mindroot/coreplugins/admin/plugin_manager.py +105 -9
- mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
- mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
- mindroot/coreplugins/admin/plugin_routes.py +114 -0
- mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
- mindroot/coreplugins/admin/router.py +116 -15
- mindroot/coreplugins/admin/service_models.py +1 -1
- mindroot/coreplugins/admin/settings_router.py +1 -0
- mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
- mindroot/coreplugins/admin/static/css/dark.css +1 -0
- mindroot/coreplugins/admin/static/css/default.css +4 -0
- mindroot/coreplugins/admin/static/js/about-info.js +367 -0
- mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
- mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
- mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
- mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
- mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
- mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
- mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
- mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
- mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
- mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
- mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
- mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
- mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
- mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
- mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
- mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
- mindroot/coreplugins/admin/static/logo.png +0 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
- mindroot/coreplugins/agent/Assistant/agent.json +27 -11
- mindroot/coreplugins/agent/agent.py +2 -2
- mindroot/coreplugins/agent/command_parser.py +25 -10
- mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
- mindroot/coreplugins/chat/__init__.py +4 -1
- mindroot/coreplugins/chat/router.py +132 -20
- mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
- mindroot/coreplugins/chat/services.py +31 -1
- mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
- mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
- mindroot/coreplugins/chat/static/css/dark.css +24 -3
- mindroot/coreplugins/chat/static/css/default.css +24 -3
- mindroot/coreplugins/chat/static/css/main.css +1 -0
- mindroot/coreplugins/chat/static/js/action.js +137 -60
- mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
- mindroot/coreplugins/chat/static/js/chat.js +59 -16
- mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
- mindroot/coreplugins/chat/static/js/chatform.js +2 -2
- mindroot/coreplugins/chat/static/site.webmanifest +1 -1
- mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
- mindroot/coreplugins/chat/widget_manager.py +139 -0
- mindroot/coreplugins/chat/widget_routes.py +287 -0
- mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
- mindroot/coreplugins/email/__init__.py +2 -0
- mindroot/coreplugins/email/email_provider.py +2 -2
- mindroot/coreplugins/email/mod.py +100 -0
- mindroot/coreplugins/email/services.py +5 -3
- mindroot/coreplugins/email/smtp_handler.py +9 -3
- mindroot/coreplugins/email/test_email_service.py +75 -0
- mindroot/coreplugins/env_manager/mod.py +61 -25
- mindroot/coreplugins/home/router.py +37 -2
- mindroot/coreplugins/home/static/imgs/logo.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
- mindroot/coreplugins/home/templates/home.jinja2 +15 -6
- mindroot/coreplugins/index/indices/default/index.json +39 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -2
- mindroot/coreplugins/jwt_auth/mod.py +40 -17
- mindroot/coreplugins/l8n/__init__.py +6 -0
- mindroot/coreplugins/l8n/debug_loader.py +85 -0
- mindroot/coreplugins/l8n/debug_middleware.py +74 -0
- mindroot/coreplugins/l8n/l8n_constants.py +19 -0
- mindroot/coreplugins/l8n/language_detection.py +183 -0
- mindroot/coreplugins/l8n/middleware.py +151 -0
- mindroot/coreplugins/l8n/mod.py +277 -0
- mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
- mindroot/coreplugins/l8n/test_enhanced.py +298 -0
- mindroot/coreplugins/l8n/test_l8n.py +95 -0
- mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
- mindroot/coreplugins/l8n/test_middleware.py +272 -0
- mindroot/coreplugins/l8n/utils.py +232 -0
- mindroot/coreplugins/mcp_/__init__.py +14 -0
- mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
- mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
- mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
- mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
- mindroot/coreplugins/mcp_/mod.py +367 -0
- mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
- mindroot/coreplugins/mcp_/server_installer.py +79 -0
- mindroot/coreplugins/mcp_/setup.py +26 -0
- mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
- mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
- mindroot/coreplugins/persona/mod.py +12 -7
- mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
- mindroot/coreplugins/subscriptions/__init__.py +1 -0
- mindroot/coreplugins/subscriptions/mod.py +14 -3
- mindroot/coreplugins/subscriptions/router.py +3 -0
- mindroot/coreplugins/user_service/__init__.py +1 -2
- mindroot/coreplugins/user_service/admin_init.py +1 -0
- mindroot/coreplugins/user_service/email_service.py +72 -17
- mindroot/coreplugins/user_service/mod.py +10 -2
- mindroot/coreplugins/user_service/router.py +2 -0
- mindroot/lib/auth/api_key.py +28 -0
- mindroot/lib/cli/plugins.py +94 -0
- mindroot/lib/plugins/default_plugin_manifest.json +20 -0
- mindroot/lib/plugins/installation.py +5 -5
- mindroot/lib/plugins/l8n_static_handler.py +225 -0
- mindroot/lib/plugins/loader.py +33 -3
- mindroot/lib/plugins/loader_with_l8n.py +281 -0
- mindroot/lib/plugins/manifest.py +236 -24
- mindroot/lib/providers/commands.py +3 -1
- mindroot/lib/route_decorators.py +5 -5
- mindroot/lib/templates.py +183 -11
- mindroot/lib/utils/merge_arrays.py +1 -1
- mindroot/migrate.py +39 -20
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +42 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
- mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
- mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
- mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
- mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
- mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
- mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
- mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/index/default.json +0 -76
- mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
- mindroot/coreplugins/user_service/hooks.py +0 -23
- /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/WHEEL +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test script to verify email service functionality.
|
|
4
|
+
Run this to test if email sending works with your configuration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
sys.path.append('/files/mindroot/src')
|
|
11
|
+
|
|
12
|
+
from mindroot.coreplugins.email.mod import init_email_provider, send_email
|
|
13
|
+
|
|
14
|
+
async def test_email_service():
|
|
15
|
+
"""Test the email service with current environment configuration"""
|
|
16
|
+
print("Testing email service...")
|
|
17
|
+
|
|
18
|
+
# Check environment variables
|
|
19
|
+
smtp_email = os.getenv('SMTP_EMAIL')
|
|
20
|
+
smtp_password = os.getenv('SMTP_PASSWORD')
|
|
21
|
+
|
|
22
|
+
if not smtp_email or not smtp_password:
|
|
23
|
+
print("❌ Missing SMTP_EMAIL or SMTP_PASSWORD environment variables")
|
|
24
|
+
print("Please set these environment variables:")
|
|
25
|
+
print(" export SMTP_EMAIL='your-email@gmail.com'")
|
|
26
|
+
print(" export SMTP_PASSWORD='your-app-password'")
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
print(f"📧 Using SMTP email: {smtp_email}")
|
|
30
|
+
|
|
31
|
+
# Initialize email provider
|
|
32
|
+
print("Initializing email provider...")
|
|
33
|
+
success = await init_email_provider()
|
|
34
|
+
|
|
35
|
+
if not success:
|
|
36
|
+
print("❌ Failed to initialize email provider")
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
print("✅ Email provider initialized successfully")
|
|
40
|
+
|
|
41
|
+
# Test sending email
|
|
42
|
+
test_email = input(f"Enter test email address (or press Enter to use {smtp_email}): ").strip()
|
|
43
|
+
if not test_email:
|
|
44
|
+
test_email = smtp_email
|
|
45
|
+
|
|
46
|
+
print(f"Sending test email to {test_email}...")
|
|
47
|
+
|
|
48
|
+
# Test HTML email
|
|
49
|
+
html_body = """
|
|
50
|
+
<html>
|
|
51
|
+
<body>
|
|
52
|
+
<h1>MindRoot Email Service Test</h1>
|
|
53
|
+
<p>This is a test email from MindRoot.</p>
|
|
54
|
+
<p><strong>HTML formatting works!</strong></p>
|
|
55
|
+
<p>If you can see this styled content, HTML emails are working correctly.</p>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
result = await send_email(
|
|
61
|
+
to=test_email,
|
|
62
|
+
subject="MindRoot Email Service Test",
|
|
63
|
+
body=html_body # HTML will be auto-detected
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if result.get('success'):
|
|
67
|
+
print("✅ Test email sent successfully!")
|
|
68
|
+
print(f"Message ID: {result.get('message_id')}")
|
|
69
|
+
return True
|
|
70
|
+
else:
|
|
71
|
+
print(f"❌ Failed to send test email: {result.get('error')}")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
asyncio.run(test_email_service())
|
|
@@ -22,13 +22,12 @@ def should_skip_directory(directory):
|
|
|
22
22
|
skip_dirs = [
|
|
23
23
|
'__pycache__',
|
|
24
24
|
'node_modules',
|
|
25
|
-
'static/js',
|
|
25
|
+
'static/js',
|
|
26
26
|
'static/css',
|
|
27
27
|
'venv',
|
|
28
28
|
'env',
|
|
29
29
|
'.env',
|
|
30
30
|
'virtualenv',
|
|
31
|
-
'site-packages',
|
|
32
31
|
'dist-packages',
|
|
33
32
|
'.git',
|
|
34
33
|
'.idea',
|
|
@@ -38,6 +37,14 @@ def should_skip_directory(directory):
|
|
|
38
37
|
'egg-info'
|
|
39
38
|
]
|
|
40
39
|
|
|
40
|
+
# Special handling for site-packages - only skip if it's not mindroot related
|
|
41
|
+
if 'site-packages' in directory:
|
|
42
|
+
# Allow mindroot core plugins in site-packages
|
|
43
|
+
if 'mindroot/coreplugins' in directory or 'mindroot/lib' in directory:
|
|
44
|
+
return False
|
|
45
|
+
# Skip other site-packages content
|
|
46
|
+
return True
|
|
47
|
+
|
|
41
48
|
# Check if any part of the path contains a directory to skip
|
|
42
49
|
path_parts = Path(directory).parts
|
|
43
50
|
for skip_dir in skip_dirs:
|
|
@@ -66,7 +73,7 @@ def scan_directory_for_env_vars(directory):
|
|
|
66
73
|
# Use grep to find os.environ references - much faster than parsing each file
|
|
67
74
|
cmd = [
|
|
68
75
|
'grep', '-r',
|
|
69
|
-
'-E', r"os\.environ(\.get\(|\[)",
|
|
76
|
+
'-E', r"(os\.environ(\.get\(|\[)|os\.getenv\(|getenv\()",
|
|
70
77
|
'--include=*.py',
|
|
71
78
|
'--exclude-dir=venv',
|
|
72
79
|
'--exclude-dir=env',
|
|
@@ -85,14 +92,24 @@ def scan_directory_for_env_vars(directory):
|
|
|
85
92
|
return env_vars
|
|
86
93
|
|
|
87
94
|
# Extract variable names using regex
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
patterns = [
|
|
96
|
+
r"os\.environ\.get\(['\"]([A-Za-z0-9_]+)['\"]", # os.environ.get('VAR_NAME')
|
|
97
|
+
r"os\.environ\[['\"]([A-Za-z0-9_]+)['\"]\]", # os.environ['VAR_NAME']
|
|
98
|
+
r"os\.getenv\(['\"]([A-Za-z0-9_]+)['\"]", # os.getenv('VAR_NAME')
|
|
99
|
+
r"(?<!os\.)getenv\(['\"]([A-Za-z0-9_]+)['\"]", # getenv('VAR_NAME') without os. prefix
|
|
100
|
+
]
|
|
90
101
|
|
|
91
102
|
for line in result.stdout.splitlines():
|
|
92
|
-
|
|
103
|
+
# Skip lines that are comments containing example patterns
|
|
104
|
+
if '# os.environ.get(' in line or '# os.environ[' in line or '# os.getenv(' in line:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
for pattern in patterns:
|
|
93
108
|
for match in re.finditer(pattern, line):
|
|
94
109
|
var_name = match.group(1)
|
|
95
|
-
|
|
110
|
+
# Filter out obvious false positives
|
|
111
|
+
if var_name not in ['VAR_NAME', 'VARIABLE_NAME', 'ENV_VAR']:
|
|
112
|
+
env_vars.add(var_name)
|
|
96
113
|
|
|
97
114
|
except Exception as e:
|
|
98
115
|
print(f"Error scanning directory {directory}: {e}")
|
|
@@ -103,28 +120,38 @@ def scan_directory_for_env_vars(directory):
|
|
|
103
120
|
async def scan_env_vars(params=None, context=None):
|
|
104
121
|
"""Scan all enabled plugins for environment variable references.
|
|
105
122
|
|
|
123
|
+
Debug logs added to help troubleshoot path resolution and scanning.
|
|
124
|
+
|
|
106
125
|
Returns:
|
|
107
126
|
dict: Dictionary with plugin names as keys and environment variable info as values
|
|
108
127
|
"""
|
|
109
128
|
results = {}
|
|
110
129
|
all_env_vars = set()
|
|
111
130
|
|
|
131
|
+
print("[ENV_MANAGER DEBUG] Starting scan_env_vars")
|
|
132
|
+
print(f"[ENV_MANAGER DEBUG] Current working directory: {os.getcwd()}")
|
|
133
|
+
|
|
112
134
|
# Get all enabled plugins
|
|
113
135
|
enabled_plugins = list_enabled()
|
|
136
|
+
print(f"[ENV_MANAGER DEBUG] Found {len(enabled_plugins)} enabled plugins: {[name for name, cat in enabled_plugins]}")
|
|
114
137
|
|
|
115
138
|
for plugin_name, category in enabled_plugins:
|
|
116
139
|
plugin_path = get_plugin_path(plugin_name)
|
|
140
|
+
print(f"[ENV_MANAGER DEBUG] Plugin {plugin_name}: path = {plugin_path}")
|
|
117
141
|
if plugin_path:
|
|
118
142
|
# If plugin_path is a file, get its directory
|
|
119
143
|
if os.path.isfile(plugin_path):
|
|
120
144
|
plugin_path = os.path.dirname(plugin_path)
|
|
145
|
+
print(f"[ENV_MANAGER DEBUG] Plugin {plugin_name}: converted to directory = {plugin_path}")
|
|
121
146
|
|
|
122
147
|
# Skip scanning if this is a directory we should ignore
|
|
123
148
|
if should_skip_directory(plugin_path):
|
|
149
|
+
print(f"[ENV_MANAGER DEBUG] Plugin {plugin_name}: skipping directory {plugin_path}")
|
|
124
150
|
continue
|
|
125
151
|
|
|
126
152
|
# Scan the plugin directory for environment variable references
|
|
127
153
|
env_vars = scan_directory_for_env_vars(plugin_path)
|
|
154
|
+
print(f"[ENV_MANAGER DEBUG] Plugin {plugin_name}: found {len(env_vars)} env vars: {sorted(env_vars)}")
|
|
128
155
|
|
|
129
156
|
if env_vars:
|
|
130
157
|
results[plugin_name] = {
|
|
@@ -133,30 +160,35 @@ async def scan_env_vars(params=None, context=None):
|
|
|
133
160
|
'env_vars': list(env_vars)
|
|
134
161
|
}
|
|
135
162
|
all_env_vars.update(env_vars)
|
|
163
|
+
else:
|
|
164
|
+
print(f"[ENV_MANAGER DEBUG] Plugin {plugin_name}: no path found")
|
|
136
165
|
|
|
137
|
-
# Also scan the
|
|
138
|
-
core_env_vars = set()
|
|
166
|
+
# Also scan the lib directory (but not coreplugins since individual plugins are already scanned)
|
|
139
167
|
lib_path = os.path.dirname(lib.__file__)
|
|
140
|
-
mindroot_path = os.path.dirname(lib_path)
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
print(f"[ENV_MANAGER DEBUG] lib.__file__ = {lib.__file__}")
|
|
170
|
+
print(f"[ENV_MANAGER DEBUG] lib_path = {lib_path}")
|
|
171
|
+
|
|
172
|
+
# Scan lib directory for additional core variables
|
|
143
173
|
if os.path.isdir(lib_path):
|
|
174
|
+
print(f"[ENV_MANAGER DEBUG] Scanning lib directory: {lib_path}")
|
|
144
175
|
lib_vars = scan_directory_for_env_vars(lib_path)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
176
|
+
print(f"[ENV_MANAGER DEBUG] Lib vars found: {sorted(lib_vars)}")
|
|
177
|
+
|
|
178
|
+
if lib_vars:
|
|
179
|
+
results['lib'] = {
|
|
180
|
+
'plugin_name': 'lib',
|
|
181
|
+
'category': 'core',
|
|
182
|
+
'env_vars': sorted(list(lib_vars))
|
|
183
|
+
}
|
|
184
|
+
all_env_vars.update(lib_vars)
|
|
185
|
+
print(f"[ENV_MANAGER DEBUG] Added lib section to results")
|
|
186
|
+
else:
|
|
187
|
+
print(f"[ENV_MANAGER DEBUG] Lib directory not found: {lib_path}")
|
|
152
188
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
'category': 'core',
|
|
157
|
-
'env_vars': sorted(list(core_env_vars))
|
|
158
|
-
}
|
|
159
|
-
all_env_vars.update(core_env_vars)
|
|
189
|
+
# Note: We don't scan the entire coreplugins directory separately because
|
|
190
|
+
# individual core plugins are already being scanned above in the enabled_plugins loop.
|
|
191
|
+
# This prevents duplicate entries that would show up as "Multiple Plugins".
|
|
160
192
|
|
|
161
193
|
# Get current environment variables
|
|
162
194
|
current_env = {}
|
|
@@ -174,6 +206,10 @@ async def scan_env_vars(params=None, context=None):
|
|
|
174
206
|
# Add current environment variables to results
|
|
175
207
|
results['current_env'] = current_env
|
|
176
208
|
|
|
209
|
+
print(f"[ENV_MANAGER DEBUG] Results structure: {[(k, len(v.get('env_vars', [])) if isinstance(v, dict) and 'env_vars' in v else 'N/A') for k, v in results.items()]}")
|
|
210
|
+
|
|
211
|
+
print(f"[ENV_MANAGER DEBUG] Final results keys: {list(results.keys())}")
|
|
212
|
+
print(f"[ENV_MANAGER DEBUG] Total unique env vars: {len(all_env_vars)}")
|
|
177
213
|
return results
|
|
178
214
|
|
|
179
215
|
|
|
@@ -5,6 +5,8 @@ from lib.templates import render
|
|
|
5
5
|
from lib.route_decorators import add_public_static
|
|
6
6
|
import os
|
|
7
7
|
import glob
|
|
8
|
+
import json
|
|
9
|
+
from lib.providers.services import service_manager
|
|
8
10
|
from datetime import datetime
|
|
9
11
|
import time
|
|
10
12
|
|
|
@@ -18,7 +20,40 @@ add_public_static('/home/static/')
|
|
|
18
20
|
@router.get("/", response_class=HTMLResponse)
|
|
19
21
|
async def home(request: Request):
|
|
20
22
|
# Get all agent directories
|
|
21
|
-
agent_dirs = [
|
|
23
|
+
agent_dirs = []
|
|
24
|
+
|
|
25
|
+
# Check local agents
|
|
26
|
+
if os.path.exists("data/agents/local"):
|
|
27
|
+
agent_dirs.extend([agent for agent in os.listdir("data/agents/local") if os.path.isdir(os.path.join("data/agents/local", agent))])
|
|
28
|
+
|
|
29
|
+
# Check shared agents
|
|
30
|
+
if os.path.exists("data/agents/shared"):
|
|
31
|
+
shared_agents = [agent for agent in os.listdir("data/agents/shared") if os.path.isdir(os.path.join("data/agents/shared", agent))]
|
|
32
|
+
agent_dirs.extend(shared_agents)
|
|
33
|
+
|
|
34
|
+
# Get agent data with persona information
|
|
35
|
+
agents_with_personas = []
|
|
36
|
+
for agent_name in agent_dirs:
|
|
37
|
+
try:
|
|
38
|
+
agent_data = await service_manager.get_agent_data(agent_name)
|
|
39
|
+
# Get the original persona reference from the agent.json file directly
|
|
40
|
+
# to preserve registry paths like "registry/owner/name"
|
|
41
|
+
agent_file_path = f"data/agents/local/{agent_name}/agent.json"
|
|
42
|
+
if not os.path.exists(agent_file_path):
|
|
43
|
+
agent_file_path = f"data/agents/shared/{agent_name}/agent.json"
|
|
44
|
+
|
|
45
|
+
with open(agent_file_path, 'r') as f:
|
|
46
|
+
raw_agent_data = json.load(f)
|
|
47
|
+
persona_path = raw_agent_data.get('persona', agent_name)
|
|
48
|
+
|
|
49
|
+
agents_with_personas.append({
|
|
50
|
+
'name': agent_name,
|
|
51
|
+
'persona': persona_path,
|
|
52
|
+
'agent_data': agent_data # Include full agent data for template
|
|
53
|
+
})
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# Fallback if agent data can't be loaded
|
|
56
|
+
agents_with_personas.append({'name': agent_name, 'persona': agent_name})
|
|
22
57
|
|
|
23
58
|
# Try to sort agents by last access time
|
|
24
59
|
agent_access_times = []
|
|
@@ -50,5 +85,5 @@ async def home(request: Request):
|
|
|
50
85
|
agents = [agent for agent, _ in agent_access_times]
|
|
51
86
|
|
|
52
87
|
user = request.state.user
|
|
53
|
-
html = await render('home', {"user": user, "request": request, "agents": agents })
|
|
88
|
+
html = await render('home', {"user": user, "request": request, "agents": agents, "agents_with_personas": agents_with_personas })
|
|
54
89
|
return HTMLResponse(html)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<link rel="stylesheet" href="/home/static/css/default.css">
|
|
17
17
|
<link rel="stylesheet" href="/home/static/css/home.css">
|
|
18
18
|
<link rel="stylesheet" href="/home/static/css/enhanced.css">
|
|
19
|
-
<link rel="icon" href="/
|
|
19
|
+
<link rel="icon" href="/imgs/logo.png">
|
|
20
20
|
{% endblock %}
|
|
21
21
|
|
|
22
22
|
{% block head_css_end %}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
{% block body_main %}
|
|
44
44
|
<main class="agent-container">
|
|
45
|
-
<img src="/
|
|
45
|
+
<img src="/imgs/logo.png" alt="MindRoot Logo" class="logo">
|
|
46
46
|
|
|
47
47
|
<h1>MindRoot Agent Host</h1>
|
|
48
48
|
|
|
@@ -51,12 +51,21 @@
|
|
|
51
51
|
<h2>Click an Agent to Start a Chat Session:</h2>
|
|
52
52
|
|
|
53
53
|
<div class="agent-list" role="list">
|
|
54
|
-
{% for
|
|
55
|
-
<a href="/agent/{{
|
|
56
|
-
|
|
54
|
+
{% for agent_persona in agents_with_personas %}
|
|
55
|
+
<a href="/agent/{{ agent_persona.name }}" class="agent-link" role="listitem" target="_blank">
|
|
56
|
+
{% set persona_path = agent_persona.persona %}
|
|
57
|
+
{% set has_faceref = false %}
|
|
58
|
+
{% if agent_persona.agent_data and agent_persona.agent_data.persona and agent_persona.agent_data.persona.faceref %}
|
|
59
|
+
{% set has_faceref = true %}
|
|
60
|
+
{% endif %}
|
|
61
|
+
{% if has_faceref %}
|
|
62
|
+
<img src="/chat/personas/{{ persona_path }}/faceref.png" alt="{{ agent_persona.name }} avatar" class="agent-avatar" onerror="this.src='/chat/personas/{{ persona_path }}/avatar.png'; this.onerror=function(){this.src='/chat/static/assistant.png'}">
|
|
63
|
+
{% else %}
|
|
64
|
+
<img src="/chat/personas/{{ persona_path }}/avatar.png" alt="{{ agent_persona.name }} avatar" class="agent-avatar" onerror="this.src='/chat/static/assistant.png'">
|
|
65
|
+
{% endif %}
|
|
57
66
|
<div class="agent-info">
|
|
58
67
|
<span class="agent-status" aria-hidden="true"></span>
|
|
59
|
-
<span class="agent-name">{{
|
|
68
|
+
<span class="agent-name">{{ agent_persona.name | replace('_', ' ') }}</span>
|
|
60
69
|
</div>
|
|
61
70
|
</a>
|
|
62
71
|
{% endfor %}
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"swap_face"
|
|
165
165
|
],
|
|
166
166
|
"dependencies": [],
|
|
167
|
-
"
|
|
167
|
+
"github_url": "runvnc/ah_swapface"
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"name": "History",
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
"stream_chat"
|
|
187
187
|
],
|
|
188
188
|
"dependencies": [],
|
|
189
|
-
"
|
|
189
|
+
"github_url": "runvnc/ah_openrouter"
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
"name": "RunPod SD",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"select_image_model"
|
|
202
202
|
],
|
|
203
203
|
"dependencies": [],
|
|
204
|
-
"
|
|
204
|
+
"github_url": "runvnc/ah_runpod_sd"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"name": "Browser Use",
|
|
@@ -229,14 +229,14 @@
|
|
|
229
229
|
],
|
|
230
230
|
|
|
231
231
|
"dependencies": [],
|
|
232
|
-
"
|
|
232
|
+
"github_url": "runvnc/mr_gemini"
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
"name": "Session Data",
|
|
236
236
|
"version": "1.0.0",
|
|
237
237
|
"description": "",
|
|
238
238
|
"source": "github",
|
|
239
|
-
"
|
|
239
|
+
"github_url": "runvnc/ah_session_data",
|
|
240
240
|
"commands": [
|
|
241
241
|
"session_data_update",
|
|
242
242
|
"session_data_del",
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
"version": "1.0.0",
|
|
253
253
|
"description": "",
|
|
254
254
|
"source": "github",
|
|
255
|
-
"
|
|
255
|
+
"github_url": "runvnc/mr_hud",
|
|
256
256
|
"commands": [],
|
|
257
257
|
"services": [],
|
|
258
258
|
"dependencies": [],
|
|
@@ -408,5 +408,38 @@
|
|
|
408
408
|
"flags": [],
|
|
409
409
|
"instructions": "You are an advanced AI software engineer with expertise in the MindRoot agent framework and related technologies.\n\n# MindRoot Plugin System\n\n## Overview\nThe MindRoot plugin system is a modular architecture that combines Python backend functionality with web components for the frontend. It provides a flexible way to extend the system's capabilities through commands, services, and web UI components.\n\n## Plugin Structure\n\nFor frontend component details, see below.\n\nplugin_name/\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 plugin_name/\n\u2502 \u251c\u2500\u2500 templates/ # Main page templates\n\u2502 \u251c\u2500\u2500 static/ # Static assets (auto-mounted if directory exists)\n\u2502 \u251c\u2500\u2500 inject/ # Templates to inject into existing blocks\n\u2502 \u251c\u2500\u2500 override/ # Templates to replace existing blocks\n\u2502 \u251c\u2500\u2500 mod.py # Commands and services\n\u2502 \u251c\u2500\u2500 router.py # FastAPI routes (auto-mounted if present)\n\u2502 \u2514\u2500\u2500 __init__.py # Plugin initialization\n\u251c\u2500\u2500 plugin_info.json # Plugin metadata and configuration\n\u251c\u2500\u2500 pyproject.toml # Build system requirements\n\u251c\u2500\u2500 setup.py # Package installation\n\u2514\u2500\u2500 README.md # Documentation\n```\n\n## Key Components\n\n### 1. Plugin Configuration (plugin_info.json)\n```json\n{\n \"name\": \"Plugin Name\",\n \"version\": \"1.0.0\",\n \"description\": \"Plugin description\",\n \"services\": [\"service_name\"],\n \"commands\": [\"command_name\"]\n}\n```\n\n### 2. Plugin Initialization (__init__.py)\n```python\n# This import is currently required for the plugin to load properly\n# Will be improved in future versions\nfrom .mod import *\n```\n\n### 3. Backend (Python)\n\n#### Command Registration\n```python\nfrom lib.providers.commands import command\n\n@command()\nasync def my_command(params, context=None):\n \"\"\"Command implementation\"\"\"\n pass\n```\n\n#### Service Registration\n```python\nfrom lib.providers.services import service\n\n@service()\nasync def my_service(params, context=None):\n \"\"\"Service implementation\"\"\"\n pass\n```\n\n#### Route Handlers (Optional)\n```python\n# router.py - will be automatically mounted if present\nfrom fastapi import APIRouter, Request\nfrom fastapi.responses import HTMLResponse\nfrom lib.templates import render\n\nrouter = APIRouter()\n\n@router.get(\"/endpoint\")\nasync def handler(request: Request):\n # Templates must be in templates/[page_name].jinja2\n user = request.state.user.username\n html = await render('page_name', {\"context\": \"data\", \"user\": user })\n return HTMLResponse(html)\n```\n\n### 4. Template System\n\nThe main chat template (mindroot/coreplugins/chat/templates/chat.jinja2) provides these blocks for plugin customization:\n\n#### Head Section Blocks\n```jinja2\n{% block head_meta %} {# Meta tags, charset, viewport #}\n{% block title %} {# Page title #}\n{% block head_styles %} {# CSS includes (must include <link> or <style> tag) #}\n{% block head_scripts %} {# JavaScript includes (must include <script> tags ) #}\n{% block head_favicon %} {# Favicon definitions #}\n{% block head_extra %} {# Additional head content #}\n```\n\n#### Body Section Blocks\n```jinja2\n{% block body_init %} {# Initial JavaScript setup #}\n{% block pre_content %} {# Left sidebar content #}\n{% block insert %} {# Additional content area #}\n{% block content %} {# Main chat interface #}\n{% block body_extra %} {# Additional body content #}\n```\n\n#### Template Injection Example\n```jinja2\n{# inject/chat.jinja2 - Simple button injection example #}\n{% block pre_content %}\n <div class=\"my-plugin-section\">\n <a href=\"/my-plugin/action\">\n <button class=\"plugin-btn\">Plugin Action</button>\n </a>\n </div>\n{% endblock %}\n```\n\n#### Template Override Example\n```jinja2\n{# override/chat.jinja2 - Will replace entire pre_content block #}\n\n{% block pre_content %}\n <div class=\"custom-sidebar\">\n <h2>Custom Sidebar</h2>\n <nav>\n <ul>\n <li><a href=\"#\">Menu Item 1</a></li>\n <li><a href=\"#\">Menu Item 2</a></li>\n </ul>\n </nav>\n </div>\n{% endblock %}\n```\n\n# Frontend Integration Guide\n\nThe chat system is built on web components using the Lit library. The source code is available in the [mindroot repository](https://github.com/runvnc/mindroot).\n\n### Key Source Files\n\n\n- [base.js](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/chat/static/js/base.js) - Base component with theme support\n\n## Chat frontend\n\nTo view these files use a fetch web page or curl command.\n\n- [chat.js](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/chat/static/js/chat.js) - Main chat component and SSE handling\n- [action.js](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/chat/static/js/action.js) - Command result display\n- [chatmessage.js](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/chat/static/js/chatmessage.js) - Message component\n- [chatform.js](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/chat/static/js/chatform.js) - Input handling\n\n## Admin\n\n- [Main admin components](https://github.com/runvnc/mindroot/blob/main/src/mindroot/coreplugins/admin/static/js/) \n\nExample for plugging in a new admin section:\n\nNote that non-core plugins will need to follow the normal stucture with src/ and plugin_info.json etc.\nIf under coreplugins/ then that is not needed but root dir pyproject and MANIFEST.in need to be updated\nwith requirements and files like under static/\n\nUnder `static/js`:\n\n```javascript\n\nimport { LitElement, html, css } from '/admin/static/js/lit-core.min.js';\nimport { BaseEl } from '/admin/static/js/base.js';\n\nclass ApiKeyManager extends BaseEl {\n static properties = {\n apiKeys: { type: Array },\n loading: { type: Boolean },\n selectedUser: { type: String }\n }\n\n static styles = css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .api-key-manager {\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 1200px;\n margin: 0 auto;\n gap: 20px;\n }\n\n .section {\n background: rgb(10, 10, 25);\n border-radius: 8px;\n padding: 1rem;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .key-list {\n width: 100%;\n border-collapse: collapse;\n margin-top: 1rem;\n }\n\n .key-list th,\n .key-list td {\n padding: 0.75rem;\n text-align: left;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .key-list th {\n background: rgba(0, 0, 0, 0.2);\n font-weight: 500;\n }\n\n .actions {\n display: flex;\n gap: 10px;\n align-items: center;\n }\n\n button {\n background: #2a2a40;\n color: #fff;\n border: 1px solid rgba(255, 255, 255, 0.1);\n padding: 0.5rem 1rem;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n button:hover {\n background: #3a3a50;\n }\n\n button.delete {\n background: #402a2a;\n }\n\n button.delete:hover {\n background: #503a3a;\n }\n\n .user-select {\n background: #2a2a40;\n color: #fff;\n border: 1px solid rgba(255, 255, 255, 0.1);\n padding: 0.5rem;\n border-radius: 4px;\n margin-right: 10px;\n }\n\n .description-input {\n background: #2a2a40;\n color: #fff;\n border: 1px solid rgba(255, 255, 255, 0.1);\n padding: 0.5rem;\n border-radius: 4px;\n margin-right: 10px;\n width: 200px;\n }\n\n .key-value {\n font-family: monospace;\n background: rgba(0, 0, 0, 0.2);\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n }\n `;\n\n constructor() {\n super();\n this.apiKeys = [];\n this.loading = false;\n this.selectedUser = '';\n this.fetchInitialData();\n }\n\n async fetchInitialData() {\n this.loading = true;\n await Promise.all([\n this.fetchApiKeys(),\n ]);\n this.loading = false;\n }\n\n async fetchApiKeys() {\n try {\n const response = await fetch('/api_keys/list');\n const result = await response.json();\n if (result.success) {\n this.apiKeys = result.data;\n }\n } catch (error) {\n console.error('Error fetching API keys:', error);\n }\n }\n\n async handleCreateKey() {\n const description = this.shadowRoot.querySelector('.description-input').value;\n const username = this.shadowRoot.querySelector('.user-select').value;\n\n try {\n const response = await fetch('/api_keys/create', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n username,\n description\n })\n });\n\n const result = await response.json();\n if (result.success) {\n await this.fetchApiKeys();\n this.shadowRoot.querySelector('.description-input').value = '';\n }\n } catch (error) {\n console.error('Error creating API key:', error);\n }\n }\n\n async handleDeleteKey(apiKey) {\n if (!confirm('Are you sure you want to delete this API key?')) return;\n\n try {\n const response = await fetch(`/api_keys/delete/${apiKey}`, {\n method: 'DELETE'\n });\n\n const result = await response.json();\n if (result.success) {\n await this.fetchApiKeys();\n }\n } catch (error) {\n console.error('Error deleting API key:', error);\n }\n }\n\n render() {\n return html`\n <div class=\"api-key-manager\">\n <div class=\"section\">\n <div class=\"actions\">\n <input type=\"text\"\n class=\"user-select\"\n placeholder=\"user\"\n >\n <input \n type=\"text\" \n class=\"description-input\" \n placeholder=\"Key description\"\n >\n <button @click=${this.handleCreateKey}>Create New API Key</button>\n </div>\n\n <table class=\"key-list\">\n <thead>\n <tr>\n <th>API Key</th>\n <th>Username</th>\n <th>Description</th>\n <th>Created At</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n ${this.apiKeys.map(key => html`\n <tr>\n <td><span class=\"key-value\">${key.key}</span></td>\n <td>${key.username}</td>\n <td>${key.description}</td>\n <td>${new Date(key.created_at).toLocaleString()}</td>\n <td>\n <button \n class=\"delete\" \n @click=${() => this.handleDeleteKey(key.key)}\n >Delete</button>\n </td>\n </tr>\n `)}\n </tbody>\n </table>\n </div>\n </div>\n `;\n }\n}\n\ncustomElements.define('api-key-manager', ApiKeyManager);\n```\nUnder `inject/`\n```jinja2\n{% block head %}\n{% block head %}\n<script type=\"module\" src=\"/api_keys/static/js/api-key-manager.js\"></script>\n{% endblock %}\n\n{% block content %}\n<details>\n <summary><span class=\"material-icons\">vpn_key</span>MindRoot API Keys</summary>\n <div class=\"details-content\">\n <api-key-manager theme=\"dark\" scope=\"local\"></api-key-manager>\n </div>\n</details>\n{% endblock %}\n```\n\n### Base Component Class\n\nAll custom components should extend the `BaseEl` class. The BaseEl class provides:\n\n- Automatic theme handling (`this.theme`)\n- Convenient DOM querying (`this.getEl(selector)`)\n- Custom event dispatch helper (`this.dispatch(name, detail)`)\n- Automatic style injection through the render method\n\nExample component:\n\n```javascript\nimport { BaseEl } from '/chat/static/js/base.js';\n\nclass MyComponent extends BaseEl {\n static properties = {\n // Your component properties\n };\n\n constructor() {\n super();\n // Your initialization\n }\n\n // Override _render instead of render\n _render() {\n return html`\n <div>Your component content</div>\n `;\n }\n}\n```\n\n### Command Handler System\n\nThe chat system uses a global registry for command handlers. Handlers receive events with different phases:\n\n- 'partial' - Incremental updates during command execution\n- 'running' - Command is actively executing\n- 'result' - Final command result\n\nExample command handler:\n\n```javascript\nwindow.registerCommandHandler('my_command', (data) => {\n console.log('Handler for', data.command);\n \n switch(data.event) {\n case 'partial':\n // Handle incremental updates\n return handlePartial(data.params);\n \n case 'running':\n // Show progress indication\n return showProgress();\n \n case 'result':\n // Process final result\n return processResult(data.args);\n }\n});\n```\n\n### Component Integration Example\n\nHere's a complete example of a custom component that handles command results:\n\n```javascript\nimport { BaseEl } from '/chat/static/js/base.js';\nimport { html, css } from '/chat/static/js/lit-core.min.js';\n\nclass MyResultViewer extends BaseEl {\n static properties = {\n data: { type: Object }\n };\n\n static styles = css`\n :host {\n display: block;\n background: var(--component-bg, var(--background-color));\n color: var(--component-text, var(--text-color));\n padding: 1rem;\n }\n `;\n\n constructor() {\n super();\n this.data = {};\n }\n\n _render() {\n return html`\n <div class=\"result-viewer\">\n <h3>${this.data.title}</h3>\n <pre>${JSON.stringify(this.data.content, null, 2)}</pre>\n </div>\n `;\n }\n}\n\ncustomElements.define('my-result-viewer', MyResultViewer);\n\n// Register command handler\nwindow.registerCommandHandler('show_result', (data) => {\n if (data.event === 'result') {\n return html`<my-result-viewer .data=${data.args}></my-result-viewer>`;\n }\n return null;\n});\n```\n\n### Styling Guidelines\n\nComponents should use CSS custom properties for theming to maintain consistency with the core system:\n\n```css\n:host {\n /* Use theme variables with fallbacks */\n background: var(--component-bg, var(--background-color));\n color: var(--component-text, var(--text-color));\n padding: var(--component-padding, 1rem);\n}\n```\n\nCommon theme variables:\n- `--background-color` - Main background\n- `--text-color` - Main text color\n- `--link-color` - Link text color\n- `--code-bg` - Code block background\n- `--component-bg` - Component background (can override --background-color)\n- `--component-text` - Component text (can override --text-color)\n\nComponents should:\n- Use CSS custom properties for themeable values\n- Provide fallbacks to core theme variables\n- Follow existing component patterns\n- Support both light and dark themes\n\n### Best Practices\n\n1. **Component Design**\n- Extend BaseEl for consistent theming and functionality\n- Override _render() instead of render()\n- Use properties for reactive data\n- Follow web component lifecycle methods\n\n2. **Command Handling**\n- Register handlers early in component initialization\n- Handle all event types (partial, running, result)\n- Provide appropriate loading indicators\n- Clean up resources when component is disconnected\n\n3. **Event System**\n- Use this.dispatch() for custom events\n- Bubble events appropriately (bubbles: true)\n- Include relevant detail data\n- Listen for events at appropriate level\n\n4. **Performance**\n- Throttle frequent updates\n- Use efficient rendering patterns\n- Clean up event listeners and intervals\n- Handle large data sets appropriately\n\n5. **Integration**\n- Follow existing component patterns\n- Use theme variables consistently\n- Support both desktop and mobile layouts\n- Test with different themes and configurations\n\n### Frontend Plugin Integration Points\n\nPlugins can integrate with the frontend in several ways:\n\n1. **Custom Components**\n- Create new web components extending BaseEl\n- Add to chat interface or custom pages\n- Interact with command system\n- Provide specialized visualizations\n\n2. **Command Handlers**\n- Register handlers for plugin commands\n- Process command events (partial/running/result)\n- Update UI in response to commands\n- Handle command parameters and results\n\n3. **Template Injection**\n- Add content to existing template blocks\n- Inject custom styles and scripts\n- Extend core UI functionality\n- Add new UI sections\n\n4. **Static Assets**\n- JavaScript modules and components\n- CSS styles and themes\n- Images and media\n- Third-party dependencies\n\nAll static assets should be placed in the plugin's static/ directory and will be automatically mounted at /[plugin_name]/static.\n\n### Development Tips\n\n1. **Getting Started**\n- Study the core component implementations in the source files\n- Use the browser dev tools to inspect component structure\n- Test components in isolation before integration\n- Start with simple components and build up complexity\n\n2. **Debugging**\n- Use console.log() in command handlers and component methods\n- Inspect component properties and state in dev tools\n- Watch for event propagation issues\n- Check for proper cleanup in disconnectedCallback()\n\n3. **Common Issues**\n- Not extending BaseEl (missing theme support)\n- Overriding render() instead of _render()\n- Forgetting to handle all command event types\n- Not cleaning up event listeners\n- Missing theme variable fallbacks\n\n4. **Testing**\n- Test with different themes\n- Verify mobile responsiveness\n- Check memory usage with long sessions\n- Validate command handler behavior\n- Test component lifecycle methods\n\n### SSE (Server-Sent Events) Integration\n\nMindRoot uses SSE for real-time updates from the AI. The chat component establishes an SSE connection and listens for events:\n\n- 'partial_command' - Incremental command output\n- 'running_command' - Command execution status\n- 'command_result' - Final command results\n- 'image' - Image generation results\n- 'finished_chat' - Chat completion\n\nPlugins can utilize this system by:\n1. Sending events from backend commands\n2. Handling events in frontend components\n3. Using the existing chat message display system\n4. Adding custom event types if needed\n\n### AI Integration Points\n\nComponents can interact with the AI system in several ways:\n\n1. **Command Results**\n- Display command outputs in custom formats\n- Show progress for long-running operations\n- Handle specialized data types\n- Provide interactive UI for results\n\n2. **Message Display**\n- Customize how AI responses appear\n- Add interactive elements to messages\n- Handle special content types\n- Provide context-specific visualizations\n\n3. **Input Handling**\n- Add custom input methods\n- Pre-process user input\n- Provide specialized interfaces\n- Handle file uploads or other data\n\n4. **Context Management**\n- Access session context\n- Store component-specific state\n- Share data between components\n- Maintain conversation history\n\n## Tool Commands\n\nCommands are Python functions that can be called by the AI agent. These must be:\n1. Decorated with @command()\n2. Listed in plugin_info.json\n3. Enabled for specific agents in the /admin interface\n\nExample command:\n\n```python\nfrom lib.providers.commands import command\n\n@command()\nasync def read(fname, context=None):\n \"\"\"Read text from a file.\n You must specify the full path to the file.\n \n Example:\n { \"read\": { \"fname\": \"/path/to/file1.txt\" } }\n \"\"\"\n with open(fname, 'r') as f:\n text = f.read()\n return text\n```\n\nKey points about commands:\n\n- Must be async functions\n- Individual parameters must be specified in the signature, not a generic 'params'\n- Should include detailed docstrings with examples\n- Can access context parameter for session data\n- Should handle errors gracefully\n- Can return data that the AI can use\n- Must be enabled per-agent in admin interface\n\n\n## Reminder: Tool Command Parameters\n\nThe individual parameters in your @command function signature must be specifically\ndefined. You may NOT use a single generic `params` or similar. This style\nwill NOT work with the system.\n\nValid:\n```python\n@command()\nasync def do_something(first_arg: str, second_thing: string, context=None):\n ...\n```\n\nThe following is completely invalid:\n```python\nasync def do_something(params, context=None):\n\n\n\n## setup.py and plugin install\n\nIMPORTANT: **setup.py must handle install/inclusion of any files in subdirs, e.g. `static/`, `templates/`, `inject/`**\n\nExample:\n\n```shell\n...\n package_data={\n \"mr_pkg1\": [\n \"static/js/*.js\",\n \"static/*.js\"\n \"inject/*.jinja2\",\n \"override/*.jinja2\"\n ],\n },\n ...\n\n```\n\n## Plugin Integration Points\n\n1. **Commands**\n - Available to the AI through the command system\n - Registered via Python decorators\n - Can access context and services\n - Must be listed in plugin_info.json\n\n2. **Services**\n - Similar to commands but for internal use\n - Registered via service decorator\n - Must be listed in plugin_info.json\n - Can be accessed by commands or other services\n\n3. **Routes**\n - FastAPI endpoints for HTTP interactions\n - Automatically mounted if router.py exists\n - No configuration needed in plugin_info.json\n\n4. **UI Integration**\n - inject/ - Templates appended to existing blocks\n - override/ - Templates replacing existing blocks\n - static/ - Automatically mounted static assets\n - Flexible frontend technology choice\n\n## Development Workflow\n\n1. Create plugin structure using modern Python package layout\n2. Define plugin metadata in plugin_info.json\n3. Implement commands and services in mod.py\n4. Create router.py if API endpoints are needed\n5. Add UI components and templates as needed\n6. Ensure proper __init__.py imports\n7. Install plugin with pip install -e .\n\n## Best Practices\n\n1. Use appropriate decorators for commands and services\n2. Follow modern Python package structure\n3. Choose appropriate frontend technology for needs\n4. Properly scope static assets\n5. Document commands and services\n6. Include proper type hints and docstrings\n\n## Common Patterns\n\n1. **State Management**\n - Components can maintain local state\n - Backend can store state in context\n - API endpoints for state synchronization\n\n2. **UI Updates**\n - Components handle real-time updates\n - Event-based communication\n - API polling for data updates\n\n3. **Theme Integration**\n - Use CSS variables for theming\n - Respect existing style patterns\n - Consider dark/light mode support\n\n\n## AI System Integration\n\nPlugins can integrate with the AI system through:\n\n1. **Commands**\n - Return structured data for UI rendering\n - Support streaming/partial results\n - Access conversation context\n - Handle file operations and external services\n\n2. **Context Management**\n - Store plugin-specific data in context\n - Access user session information\n - Share state between commands\n - Maintain conversation history\n\n3. **Event System**\n - Send SSE events from commands\n - Update UI in real-time\n - Stream command results\n - Handle long-running operations\n\n## Pipeline System\n\nMindRoot includes a pipeline system for processing data at different stages. Pipes allow plugins to modify or transform data as it flows through the system.\n\n### Pipe Decorator\n\n```python\nfrom lib.pipelines.pipe import pipe\n\n@pipe(name='filter_messages', priority=8)\ndef my_pipeline(data: dict, context=None) -> dict:\n # Modify or process data\n return data\n```\n\n### Pipeline Stages\n- pre_process_msg - Before message processing\n- process_results - After command execution\n- Custom stages as needed\n\n### Priority System\n- Higher numbers run earlier\n- Lower numbers run later\n- Use priority to control execution order\n\nExample use cases:\n- Transform message content\n- Add context information\n- Filter or modify results\n- Integrate with external systems\n"
|
|
410
410
|
}
|
|
411
|
+
, {
|
|
412
|
+
"name": "SysAdmin",
|
|
413
|
+
"description": "This is a sysadmin for Ubuntu Linux",
|
|
414
|
+
"hashver": "ea5e",
|
|
415
|
+
"commands": [
|
|
416
|
+
"tell_and_continue",
|
|
417
|
+
"wait_for_user_reply",
|
|
418
|
+
"markdown_await_user",
|
|
419
|
+
"append",
|
|
420
|
+
"write",
|
|
421
|
+
"read",
|
|
422
|
+
"dir",
|
|
423
|
+
"apply_udiff",
|
|
424
|
+
"execute_command",
|
|
425
|
+
"mkdir",
|
|
426
|
+
"think",
|
|
427
|
+
"task_complete"
|
|
428
|
+
],
|
|
429
|
+
"preferred_providers": [],
|
|
430
|
+
"thinking_level": "off",
|
|
431
|
+
"persona": "Assistant",
|
|
432
|
+
"recommended_plugins": [],
|
|
433
|
+
"instructions": "You are a sysadmin for an Ubuntu Linux system. You have root access. You are installed on a VPS.\n\nThe user may request your help with things that they might alternatively have used a control panel like Plesk for (there is no Plesk unless you install it), or for setting up libraries for software development, making nginx config files, or any system administration task.\n\nKeep track of your system info and changes etc. in /var/lib/ai-sysadmin/system-state.md . Make sure to read that at the start of each session. You can use commands like read(), write(), append(), and apply_udiff() (if necessary) as appropriate. Always double check apply_udiff() edits because they can be problematic. If you have to rebuild the file and it's a little longer than 400 lines, use write() for the first part followed by chunks in append() (but you should be able to just put details in other files and keep this file more compact).\n\nTry to keep the file under 400 lines or so and if necessary keep supplementary files with extra details for particular in the same directory or refer to system configuration files.\n\nYou should be cautious about system changes that could render the system inoperable if they do not complete properly, because the user is relying on you to update the system. If the system stops operating due to a configuration problem, they will not be able to access your chat interface, and may not be able to recover. If you are not sure, make a backup of key files and ask the user for confirmation. \n\nHowever, for tasks that seem routine and you do not have a reason to suspect they will break the system, go ahead and execute them.\n\n## System state file\n\n/var/lib/ai-sysadmin/system-state.md\n\nNote: if this file does not exist or is blank, start by populating it concisely with key system statistics and information.\n\n## Last command\n\nBefore doing significant changes you can record what you are about to do in /var/lib/ai-sysadmin/last-change.md . This way in case there is a problem there will be a record of the last specific modification made or attempted to help with rolling it back (in addition to the backup you made of any config files edited).\n\n",
|
|
434
|
+
"technicalInstructions": "",
|
|
435
|
+
"service_models": {
|
|
436
|
+
"stream_chat": {
|
|
437
|
+
"provider": "ah_anthropic",
|
|
438
|
+
"model": "claude-opus-4-1-20250805"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
"flags": [],
|
|
442
|
+
"required_plugins": []
|
|
443
|
+
}
|
|
411
444
|
]
|
|
412
445
|
}
|
|
@@ -11,6 +11,7 @@ from lib.session_files import load_session_data
|
|
|
11
11
|
from lib.utils.debug import debug_box
|
|
12
12
|
import secrets
|
|
13
13
|
from pathlib import Path
|
|
14
|
+
import re
|
|
14
15
|
|
|
15
16
|
def get_or_create_jwt_secret():
|
|
16
17
|
secret_key = os.environ.get("JWT_SECRET_KEY", None)
|
|
@@ -85,6 +86,48 @@ def decode_token(token: str):
|
|
|
85
86
|
print("Invalid token")
|
|
86
87
|
return False
|
|
87
88
|
|
|
89
|
+
def path_matches_pattern(request_path: str, route_pattern: str) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Check if a request path matches a route pattern with parameters.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
- path_matches_pattern('/chat/embed/abc123', '/chat/embed/{token}') -> True
|
|
95
|
+
- path_matches_pattern('/chat/widget/xyz/session', '/chat/widget/{token}/session') -> True
|
|
96
|
+
- path_matches_pattern('/login', '/login') -> True
|
|
97
|
+
"""
|
|
98
|
+
# Handle exact matches first
|
|
99
|
+
if request_path == route_pattern:
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
# Convert FastAPI route pattern to regex
|
|
103
|
+
# Replace {param} with regex pattern that matches any non-slash characters
|
|
104
|
+
regex_pattern = re.sub(r'\{[^}]+\}', r'[^/]+', route_pattern)
|
|
105
|
+
# Escape other regex special characters
|
|
106
|
+
regex_pattern = regex_pattern.replace('.', '\\.')
|
|
107
|
+
# Add start and end anchors
|
|
108
|
+
regex_pattern = f'^{regex_pattern}$'
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
return bool(re.match(regex_pattern, request_path))
|
|
112
|
+
except re.error:
|
|
113
|
+
# If regex compilation fails, fall back to exact match
|
|
114
|
+
return request_path == route_pattern
|
|
115
|
+
|
|
116
|
+
def is_public_route(request_path: str) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Check if a request path matches any registered public route pattern.
|
|
119
|
+
"""
|
|
120
|
+
# Check exact matches and pattern matches
|
|
121
|
+
for route_pattern in public_routes:
|
|
122
|
+
if path_matches_pattern(request_path, route_pattern):
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
# Check special cases
|
|
126
|
+
if request_path.startswith('/reset-password'):
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
return False
|
|
130
|
+
|
|
88
131
|
async def middleware(request: Request, call_next):
|
|
89
132
|
try:
|
|
90
133
|
print('-------------------------- auth middleware ----------------------------')
|
|
@@ -142,14 +185,16 @@ async def middleware(request: Request, call_next):
|
|
|
142
185
|
print("Error checking for static file", e)
|
|
143
186
|
pass
|
|
144
187
|
print("Did not find static file")
|
|
145
|
-
|
|
146
|
-
|
|
188
|
+
|
|
189
|
+
# Use the improved public route checking
|
|
190
|
+
if is_public_route(request.url.path):
|
|
147
191
|
print('Public route: ', request.url.path)
|
|
148
192
|
return await call_next(request)
|
|
149
193
|
elif any([request.url.path.startswith(path) for path in public_static]):
|
|
150
194
|
return await call_next(request)
|
|
151
195
|
else:
|
|
152
196
|
print('Not a public route: ', request.url.path)
|
|
197
|
+
print("public routes:", public_routes)
|
|
153
198
|
|
|
154
199
|
# Check for token in cookies first
|
|
155
200
|
token = request.cookies.get("access_token")
|