mindroot 8.8.0__py3-none-any.whl → 8.10.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.
@@ -67,6 +67,10 @@ class AgentForm extends BaseEl {
67
67
  background: rgba(255, 255, 255, 0.02);
68
68
  }
69
69
 
70
+ .form-hidden-when-no-agent {
71
+ display: none !important;
72
+ }
73
+
70
74
  .form-group {
71
75
  margin-bottom: 15px;
72
76
  }
@@ -1030,13 +1034,26 @@ class AgentForm extends BaseEl {
1030
1034
  }
1031
1035
 
1032
1036
  _render() {
1037
+ // Default structure for rendering when this.agent is null
1038
+ // This ensures the DOM structure is present but hidden.
1039
+ const agentForRender = this.agent || {
1040
+ name: '',
1041
+ persona: '',
1042
+ instructions: '',
1043
+ technicalInstructions: '',
1044
+ uncensored: false,
1045
+ thinking_level: 'off',
1046
+ // commands, preferred_providers, etc., are handled by helper functions
1047
+ // which use optional chaining (this.agent?.property) and are safe.
1048
+ };
1049
+
1033
1050
  return html`
1034
1051
  <div class="agent-selector">
1035
1052
  <select @change=${this.handleAgentChange}
1036
1053
  .value=${this.selectedAgentName || ''}>
1037
1054
  <option value="">Select an agent</option>
1038
1055
  ${this.agents.map(agent => html`
1039
- <option value=${agent.name} ?selected=${agent.name === this.selectedAgentName}>${agent.name}</option>
1056
+ <option .value=${agent.name} ?selected=${agent.name === this.selectedAgentName}>${agent.name}</option>
1040
1057
  `)}
1041
1058
  </select>
1042
1059
  <button class="btn btn-secondary" @click=${this.handleNewAgent}>
@@ -1044,23 +1061,22 @@ class AgentForm extends BaseEl {
1044
1061
  </button>
1045
1062
  </div>
1046
1063
 
1047
- ${this.agent ? html`
1048
- <form class="agent-form" @submit=${this.handleSubmit}>
1064
+ <form class="agent-form ${!this.agent ? 'form-hidden-when-no-agent' : ''}" @submit=${this.handleSubmit}>
1049
1065
  <div class="form-group">
1050
1066
  <label class="required">Name:</label>
1051
1067
  <input type="text" name="name"
1052
- .value=${this.agent.name || ''}
1068
+ .value=${agentForRender.name || ''}
1053
1069
  @input=${this.handleInputChange}>
1054
1070
  </div>
1055
1071
 
1056
1072
  <div class="form-group">
1057
1073
  <label class="required">Persona:</label>
1058
1074
  <select name="persona"
1059
- value=${this.agent.persona || ''}
1075
+ .value=${agentForRender.persona || ''}
1060
1076
  @change=${this.handleInputChange}>
1061
1077
  <option value="">Select a persona</option>
1062
1078
  ${this.personas.map(persona => html`
1063
- <option value="${persona.name}" ?selected=${persona.name === this.agent.persona}>${persona.name}</option>
1079
+ <option value="${persona.name}" ?selected=${persona.name === agentForRender.persona}>${persona.name}</option>
1064
1080
  `)}
1065
1081
  </select>
1066
1082
  </div>
@@ -1084,11 +1100,11 @@ class AgentForm extends BaseEl {
1084
1100
  </div>
1085
1101
  ${this.showInstructionsEditor ? html`
1086
1102
  <textarea name="instructions"
1087
- .value=${this.agent.instructions || ''}
1103
+ .value=${agentForRender.instructions || ''}
1088
1104
  @input=${this.handleInputChange}></textarea>
1089
1105
  ` : html`
1090
1106
  <div class="markdown-preview">
1091
- ${unsafeHTML(this.renderMarkdown(this.agent.instructions || ''))}
1107
+ ${unsafeHTML(this.renderMarkdown(agentForRender.instructions || ''))}
1092
1108
  </div>
1093
1109
  `}
1094
1110
  </div>
@@ -1112,11 +1128,11 @@ class AgentForm extends BaseEl {
1112
1128
  </div>
1113
1129
  ${this.showTechnicalInstructionsEditor ? html`
1114
1130
  <textarea name="technicalInstructions"
1115
- .value=${this.agent.technicalInstructions || ''}
1131
+ .value=${agentForRender.technicalInstructions || ''}
1116
1132
  @input=${this.handleInputChange}></textarea>
1117
1133
  ` : html`
1118
1134
  <div class="markdown-preview">
1119
- ${unsafeHTML(this.renderMarkdown(this.agent.technicalInstructions || ''))}
1135
+ ${unsafeHTML(this.renderMarkdown(agentForRender.technicalInstructions || ''))}
1120
1136
  </div>
1121
1137
  `}
1122
1138
  </div>
@@ -1125,7 +1141,7 @@ class AgentForm extends BaseEl {
1125
1141
  <label>
1126
1142
  Uncensored:
1127
1143
  <toggle-switch
1128
- .checked=${this.agent.uncensored || false}
1144
+ .checked=${agentForRender.uncensored || false}
1129
1145
  @toggle-change=${(e) => this.handleInputChange({
1130
1146
  target: {
1131
1147
  name: 'uncensored',
@@ -1140,22 +1156,22 @@ class AgentForm extends BaseEl {
1140
1156
  <div class="form-group reasoning-level-group">
1141
1157
  <label>Reasoning Effort:</label>
1142
1158
  <select name="thinking_level"
1143
- value=${this.agent.thinking_level || 'off'}
1159
+ .value=${agentForRender.thinking_level || 'off'}
1144
1160
  @change=${this.handleInputChange}>
1145
1161
  <option value="off"
1146
- ?selected=${(this.agent.thinking_level || 'off') === 'off'}>
1162
+ ?selected=${(agentForRender.thinking_level || 'off') === 'off'}>
1147
1163
  Off
1148
1164
  </option>
1149
1165
  <option value="low"
1150
- ?selected=${(this.agent.thinking_level || 'off') === 'low'}>
1166
+ ?selected=${(agentForRender.thinking_level || 'off') === 'low'}>
1151
1167
  Low
1152
1168
  </option>
1153
1169
  <option value="medium"
1154
- ?selected=${(this.agent.thinking_level || 'off') === 'medium'}>
1170
+ ?selected=${(agentForRender.thinking_level || 'off') === 'medium'}>
1155
1171
  Medium
1156
1172
  </option>
1157
1173
  <option value="high"
1158
- ?selected=${(this.agent.thinking_level || 'off') === 'high'}>
1174
+ ?selected=${(agentForRender.thinking_level || 'off') === 'high'}>
1159
1175
  High
1160
1176
  </option>
1161
1177
  </select>
@@ -1184,11 +1200,11 @@ class AgentForm extends BaseEl {
1184
1200
  </details>
1185
1201
  </div>
1186
1202
 
1187
- ${this.agent.name && this.missingCommands && Object.keys(this.missingCommands).length > 0 ? html`
1203
+ ${this.agent?.name && this.missingCommands && Object.keys(this.missingCommands).length > 0 ? html`
1188
1204
  <div class="form-group commands-section">
1189
1205
  <details>
1190
1206
  <summary>Missing Commands (${Object.keys(this.missingCommands).length})</summary>
1191
- <missing-commands .agentName=${this.agent.name}></missing-commands>
1207
+ <missing-commands .agentName=${this.agent.name}></missing-commands> <!-- this.agent.name is safe due to condition -->
1192
1208
  </details>
1193
1209
  </div>
1194
1210
  ` : ''}
@@ -1203,13 +1219,13 @@ class AgentForm extends BaseEl {
1203
1219
  <button class="btn" type="submit" ?disabled=${this.loading}>
1204
1220
  ${this.loading ? 'Saving...' : 'Save'}
1205
1221
  </button>
1206
-
1207
- </form>
1208
- ` : html`
1222
+ </form>
1223
+
1224
+ ${!this.agent ? html`
1209
1225
  <div class="no-agent-message">
1210
1226
  Select an agent from the dropdown above or click "New Agent" to create one.
1211
1227
  </div>
1212
- `}
1228
+ ` : ''}
1213
1229
  `;
1214
1230
  }
1215
1231
  }
@@ -112,8 +112,19 @@ export class PluginAdvancedInstall extends PluginBase {
112
112
  return;
113
113
  }
114
114
 
115
- // Extract repo name from URL
116
- const repoName = githubUrl.split('/').pop();
115
+ // Extract repo name from URL - handle various GitHub URL formats
116
+ let repoPath = githubUrl;
117
+
118
+ // Handle full GitHub URLs
119
+ if (githubUrl.includes('github.com/')) {
120
+ const match = githubUrl.match(/github\.com\/([^/]+\/[^/]+)/);
121
+ if (match) {
122
+ repoPath = match[1];
123
+ }
124
+ }
125
+
126
+ // Extract just the repo name for display
127
+ const repoName = repoPath.split('/').pop();
117
128
 
118
129
  try {
119
130
  // Close the modal
@@ -122,27 +133,39 @@ export class PluginAdvancedInstall extends PluginBase {
122
133
  // Open the installation dialog
123
134
  this.installDialog.open(repoName || 'GitHub Plugin', 'GitHub');
124
135
 
125
- // Show initial message
126
- this.installDialog.addOutput(`Starting installation of ${repoName} from GitHub...`, 'info');
136
+ // Connect to SSE endpoint for streaming GitHub installation
137
+ // Build URL with properly encoded parameters
138
+ const params = new URLSearchParams();
139
+ params.append('plugin', repoName || 'plugin');
140
+ params.append('source', 'github');
141
+ params.append('source_path', repoPath);
127
142
 
128
- try {
129
- // Use the existing GitHub installation endpoint which handles the download and extraction
130
- await this.apiCall('/plugin-manager/install-x-github-plugin', 'POST', {
131
- plugin: repoName || 'plugin',
132
- url: githubUrl
133
- });
134
-
135
- // Show success message
136
- this.installDialog.addOutput(`Plugin ${repoName} installed successfully from GitHub`, 'success');
143
+ const eventSource = new EventSource(`/plugin-manager/stream-install-plugin?${params.toString()}`);
144
+
145
+ eventSource.addEventListener('message', (event) => {
146
+ this.installDialog.addOutput(event.data, 'info');
147
+ });
148
+
149
+ eventSource.addEventListener('error', (event) => {
150
+ this.installDialog.addOutput(event.data, 'error');
151
+ });
152
+
153
+ eventSource.addEventListener('warning', (event) => {
154
+ this.installDialog.addOutput(event.data, 'warning');
155
+ });
156
+
157
+ eventSource.addEventListener('complete', (event) => {
158
+ this.installDialog.addOutput(event.data, 'success');
137
159
  this.installDialog.setComplete(false);
138
-
139
- // Notify parent components to refresh their lists
140
- this.dispatch('plugin-installed');
141
- } catch (error) {
142
- // Show error message
143
- this.installDialog.addOutput(`Failed to install plugin from GitHub: ${error.message}`, 'error');
160
+ eventSource.close();
161
+ // Dispatch event for parent components to refresh their lists
162
+ this.dispatch('plugin-installed', { plugin: { name: repoName, source: 'github' } });
163
+ });
164
+
165
+ eventSource.onerror = () => {
166
+ eventSource.close();
144
167
  this.installDialog.setComplete(true);
145
- }
168
+ };
146
169
  } catch (error) {
147
170
  this.installDialog.addOutput(`Failed to install plugin from GitHub: ${error.message}`, 'error');
148
171
  this.installDialog.setComplete(true);
@@ -357,3 +357,21 @@ If you do not do this, then your reasoning will not make it into the chat histor
357
357
  and you will end up repeating the same thought process after every command!
358
358
 
359
359
  {% endblock %}
360
+
361
+ {% block reasoning_reminder_3 %}
362
+
363
+ # Reasonable Reasoning for Commands
364
+
365
+ IMPORTANT:
366
+
367
+ If you have a built-in thinking/reasoning stage and are handling a multiple step
368
+ or iterative process, **DO NOT TRY TO PLAN THE DETAILS OF ALL STEPS IN ADVANCE**.
369
+ Your built-in reasoning efforts are discarded after each batch of commands you output.
370
+ So planning all of the remaining steps in advance each time is very wasteful.
371
+ Instead, you should **only plan for the next few commands that you need to execute**.
372
+ Then you can adjust based on the output of your commands which will be returned
373
+ by the system.
374
+
375
+ {% endblock %}
376
+
377
+
@@ -127,7 +127,15 @@ async def delegate_task(instructions: str, agent_name, log_id=None, retries=3, c
127
127
  Note: do not specify any other arguments than those in the example.
128
128
  In particular, 'context' is not a valid argument!
129
129
 
130
- Use something unique for the log_id.
130
+ log_id is optional, if you specify it you must use something unique.
131
+
132
+ You can also let the system assign log_id automatically:
133
+
134
+ Example:
135
+
136
+ { "delegate_task": { "instructions": "Write a poem about the moon", "agent_name": "poet" } }
137
+
138
+
131
139
  """
132
140
  print("in delegate task, context is:")
133
141
  print(context)
@@ -140,8 +148,9 @@ async def delegate_task(instructions: str, agent_name, log_id=None, retries=3, c
140
148
  elif 'llm' in context.data:
141
149
  llm = context.data['llm']
142
150
  (text, full_results, xx) = await service_manager.run_task(instructions, user=context.username, log_id=log_id,
151
+ parent_log_id = context.log_id,
143
152
  llm=llm, agent_name=agent_name, retries=retries, context=None)
144
- return f"""<a href="/session/{agent_name}/{log_id}" target="_blank">Task completed in log ID: {log_id}</a>\nResults:\n\n{text}"""
153
+ return f"""<a href="/session/{agent_name}/{log_id}" target="_blank">Task completed with log ID: {log_id}</a>\nResults:\n\n{text}"""
145
154
 
146
155
 
147
156
  @command()
@@ -1,4 +1,4 @@
1
- from fastapi import APIRouter, HTTPException, Request, Response
1
+ from fastapi import APIRouter, HTTPException, Request, Response, Depends
2
2
  from fastapi.responses import HTMLResponse, RedirectResponse
3
3
  from fastapi import File, UploadFile, Form
4
4
  from sse_starlette.sse import EventSourceResponse
@@ -6,6 +6,7 @@ from .models import MessageParts
6
6
  from lib.providers.services import service, service_manager
7
7
  from .services import init_chat_session, send_message_to_agent, subscribe_to_agent_messages, get_chat_history, run_task
8
8
  from lib.templates import render
9
+ from lib.auth.auth import require_user
9
10
  from lib.plugins import list_enabled
10
11
  import nanoid
11
12
  from lib.providers.commands import *
@@ -17,6 +18,8 @@ from lib.providers.commands import command_manager
17
18
  from lib.utils.debug import debug_box
18
19
  from lib.session_files import load_session_data, save_session_data
19
20
  import os
21
+ import json
22
+ from lib.chatcontext import ChatContext
20
23
  import shutil
21
24
  from pydantic import BaseModel
22
25
 
@@ -241,6 +244,50 @@ async def get_token_count(request: Request, log_id: str):
241
244
 
242
245
  return {"status": "ok", "token_counts": token_counts}
243
246
 
247
+ @router.get("/chat/del_session/{log_id}")
248
+ async def delete_chat_session(request: Request, log_id: str, user=Depends(require_user)):
249
+ """
250
+ Delete a chat session by log_id, including chat logs, context files, and all child sessions.
251
+
252
+ Parameters:
253
+ - log_id: The log ID of the session to delete
254
+
255
+ Returns:
256
+ - JSON with success status and message
257
+ """
258
+ try:
259
+ # Try to determine the agent name from the context file first
260
+ agent_name = "unknown"
261
+ context_file_path = f"data/context/{user.username}/context_{log_id}.json"
262
+
263
+ if os.path.exists(context_file_path):
264
+ try:
265
+ with open(context_file_path, 'r') as f:
266
+ context_data = json.load(f)
267
+ agent_name = context_data.get('agent_name', 'unknown')
268
+ print(f"Found agent name '{agent_name}' from context file for log_id {log_id}")
269
+ except Exception as e:
270
+ print(f"Error reading context file {context_file_path}: {e}")
271
+
272
+ # If we still don't have the agent name, try to find the chatlog file
273
+ if agent_name == "unknown":
274
+ from lib.chatlog import find_chatlog_file
275
+ chatlog_path = find_chatlog_file(log_id)
276
+ if chatlog_path:
277
+ # Extract agent from path: data/chat/{user}/{agent}/chatlog_{log_id}.json
278
+ path_parts = chatlog_path.split(os.sep)
279
+ if len(path_parts) >= 3:
280
+ agent_name = path_parts[-2] # Agent is the second-to-last part
281
+ print(f"Found agent name '{agent_name}' from chatlog file path for log_id {log_id}")
282
+
283
+ await ChatContext.delete_session_by_id(log_id=log_id, user=user.username, agent=agent_name, cascade=True)
284
+
285
+ return {"status": "ok", "message": f"Chat session {log_id} deleted successfully"}
286
+ except Exception as e:
287
+ print(f"Error deleting chat session {log_id}: {e}")
288
+ raise HTTPException(status_code=500, detail=f"Error deleting chat session: {str(e)}")
289
+
290
+
244
291
  @router.get("/chat/{log_id}/tokens")
245
292
  async def get_token_count(request: Request, log_id: str):
246
293
  """
@@ -50,7 +50,8 @@ def results_text_output(results):
50
50
 
51
51
 
52
52
  @service()
53
- async def run_task(instructions: str, agent_name:str = None, user:str = None, log_id=None, llm=None, retries=3, context=None):
53
+ async def run_task(instructions: str, agent_name:str = None, user:str = None, log_id=None,
54
+ parent_log_id=None, llm=None, retries=3, context=None):
54
55
  """
55
56
  Run a task with the given instructions
56
57
  IMPORTANT NOTE: agent must have the task_result() command enabled.
@@ -70,10 +71,11 @@ async def run_task(instructions: str, agent_name:str = None, user:str = None, lo
70
71
  context.username = user
71
72
  context.name = agent_name
72
73
  context.log_id = log_id
74
+ context.parent_log_id = parent_log_id
73
75
  context.agent = await service_manager.get_agent_data(agent_name)
74
76
  context.data['llm'] = llm
75
77
  context.current_model = llm
76
- context.chat_log = ChatLog(log_id=log_id, agent=agent_name, user=user)
78
+ context.chat_log = ChatLog(log_id=log_id, agent=agent_name, user=user, parent_log_id=parent_log_id)
77
79
  context.save_context()
78
80
  else:
79
81
  debug_box("Context is not none")
@@ -0,0 +1,76 @@
1
+ # Google OAuth Authentication Plugin
2
+
3
+ This plugin adds Google Sign-In functionality to MindRoot, allowing users to authenticate using their Google accounts.
4
+
5
+ ## Setup Instructions
6
+
7
+ ### 1. Create Google OAuth Credentials
8
+
9
+ 1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
10
+ 2. Create a new project or select an existing one
11
+ 3. Enable the Google+ API for your project
12
+ 4. Go to "Credentials" in the left sidebar
13
+ 5. Click "Create Credentials" > "OAuth client ID"
14
+ 6. Select "Web application" as the application type
15
+ 7. Add your redirect URI (e.g., `http://localhost:8000/google_auth/callback` for local development)
16
+ 8. Save your Client ID and Client Secret
17
+
18
+ ### 2. Configure Environment Variables
19
+
20
+ Add the following to your `.env` file in the MindRoot root directory:
21
+
22
+ ```bash
23
+ # Google OAuth Configuration
24
+ GOOGLE_CLIENT_ID=your-client-id-here
25
+ GOOGLE_CLIENT_SECRET=your-client-secret-here
26
+ GOOGLE_REDIRECT_URI=http://localhost:8000/google_auth/callback
27
+ ```
28
+
29
+ For production, update `GOOGLE_REDIRECT_URI` to your actual domain:
30
+ ```bash
31
+ GOOGLE_REDIRECT_URI=https://yourdomain.com/google_auth/callback
32
+ ```
33
+
34
+ ### 3. Update Google OAuth Authorized Redirect URIs
35
+
36
+ Make sure to add your redirect URI to the authorized redirect URIs in your Google OAuth client settings.
37
+
38
+ ## How It Works
39
+
40
+ 1. Users click "Sign in with Google" on the login page
41
+ 2. They are redirected to Google's OAuth consent screen
42
+ 3. After authorization, Google redirects back to `/google_auth/callback`
43
+ 4. The plugin:
44
+ - Verifies the OAuth response
45
+ - Creates a new user account if needed (username: `google_[partial_google_id]`)
46
+ - Sets the same JWT token used by the regular login system
47
+ - Redirects to the home page
48
+
49
+ ## User Data
50
+
51
+ When a user signs in with Google:
52
+ - A new user account is created with a generated username
53
+ - Their Google email is stored
54
+ - Email is automatically verified if Google has verified it
55
+ - Additional Google profile info is stored in `google_info.json`
56
+
57
+ ## Security Notes
58
+
59
+ - State tokens are used to prevent CSRF attacks
60
+ - Google ID tokens are verified server-side
61
+ - Users get the same JWT tokens as regular login
62
+ - Passwords for OAuth users are randomly generated and not used
63
+
64
+ ## Troubleshooting
65
+
66
+ 1. **"Google OAuth not configured" error**: Make sure you've set the environment variables
67
+ 2. **Redirect URI mismatch**: Ensure the redirect URI in your `.env` matches exactly what's configured in Google Cloud Console
68
+ 3. **Invalid state token**: This is a security feature - just try signing in again
69
+
70
+ ## Integration with Existing System
71
+
72
+ The plugin integrates seamlessly with MindRoot's existing authentication:
73
+ - Uses the same JWT token system
74
+ - Compatible with existing middleware
75
+ - Users can access all the same features
76
+ - Admin can manage Google-authenticated users like any other users
@@ -0,0 +1 @@
1
+ from .mod import *
@@ -0,0 +1,69 @@
1
+ {% block head_extra %}
2
+ <style>
3
+ .google-signin-btn {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ width: 100%;
8
+ padding: 0.5rem;
9
+ margin-top: 1rem;
10
+ background-color: #4285f4;
11
+ color: white;
12
+ border: none;
13
+ border-radius: 4px;
14
+ cursor: pointer;
15
+ text-decoration: none;
16
+ font-size: 14px;
17
+ transition: background-color 0.3s;
18
+ }
19
+
20
+ .google-signin-btn:hover {
21
+ background-color: #357ae8;
22
+ }
23
+
24
+ .google-signin-btn svg {
25
+ width: 18px;
26
+ height: 18px;
27
+ margin-right: 8px;
28
+ }
29
+
30
+ .divider {
31
+ text-align: center;
32
+ margin: 1rem 0;
33
+ position: relative;
34
+ }
35
+
36
+ .divider::before {
37
+ content: '';
38
+ position: absolute;
39
+ top: 50%;
40
+ left: 0;
41
+ right: 0;
42
+ height: 1px;
43
+ background-color: #555;
44
+ }
45
+
46
+ .divider span {
47
+ background-color: #2a2a2a;
48
+ padding: 0 10px;
49
+ position: relative;
50
+ color: #888;
51
+ font-size: 14px;
52
+ }
53
+ </style>
54
+ {% endblock %}
55
+
56
+ {% block content %}
57
+ <div class="divider">
58
+ <span>OR</span>
59
+ </div>
60
+ <a href="/google_auth/login" class="google-signin-btn">
61
+ <svg viewBox="0 0 24 24" fill="currentColor">
62
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
63
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
64
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
65
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
66
+ </svg>
67
+ Sign in with Google
68
+ </a>
69
+ {% endblock %}
@@ -0,0 +1 @@
1
+ # Google Auth plugin - provides Google OAuth2 authentication
@@ -0,0 +1,170 @@
1
+ from fastapi import APIRouter, Request, HTTPException
2
+ from fastapi.responses import RedirectResponse, HTMLResponse
3
+ from lib.route_decorators import public_route
4
+ from lib.providers.services import service_manager
5
+ from mindroot.coreplugins.jwt_auth.middleware import create_access_token
6
+ from mindroot.coreplugins.user_service.models import UserCreate
7
+ from google.auth.transport import requests
8
+ from google.oauth2 import id_token
9
+ import google_auth_oauthlib.flow
10
+ import os
11
+ import secrets
12
+ import json
13
+ from typing import Optional
14
+
15
+ router = APIRouter()
16
+
17
+ # OAuth 2.0 configuration
18
+ CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID')
19
+ CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET')
20
+ REDIRECT_URI = os.environ.get('GOOGLE_REDIRECT_URI', 'http://localhost:8000/google_auth/callback')
21
+
22
+ # OAuth2 flow configuration
23
+ CLIENT_CONFIG = {
24
+ "web": {
25
+ "client_id": CLIENT_ID,
26
+ "client_secret": CLIENT_SECRET,
27
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
28
+ "token_uri": "https://oauth2.googleapis.com/token",
29
+ "redirect_uris": [REDIRECT_URI]
30
+ }
31
+ }
32
+
33
+ SCOPES = ['openid', 'email', 'profile']
34
+
35
+ # Store state tokens temporarily (in production, use Redis or similar)
36
+ state_tokens = {}
37
+
38
+ @router.get("/google_auth/login")
39
+ @public_route()
40
+ async def google_login(request: Request):
41
+ """Initiate Google OAuth2 login flow"""
42
+ if not CLIENT_ID or not CLIENT_SECRET:
43
+ raise HTTPException(status_code=500, detail="Google OAuth not configured")
44
+
45
+ # Create flow instance
46
+ flow = google_auth_oauthlib.flow.Flow.from_client_config(
47
+ CLIENT_CONFIG,
48
+ scopes=SCOPES
49
+ )
50
+ flow.redirect_uri = REDIRECT_URI
51
+
52
+ # Generate state token for CSRF protection
53
+ state = secrets.token_urlsafe(32)
54
+ authorization_url, _ = flow.authorization_url(
55
+ access_type='offline',
56
+ state=state,
57
+ prompt='select_account'
58
+ )
59
+
60
+ # Store state token
61
+ state_tokens[state] = True
62
+
63
+ return RedirectResponse(url=authorization_url)
64
+
65
+ @router.get("/google_auth/callback")
66
+ @public_route()
67
+ async def google_callback(request: Request, code: str, state: str):
68
+ """Handle Google OAuth2 callback"""
69
+ # Verify state token
70
+ if state not in state_tokens:
71
+ return RedirectResponse(url="/login?error=Invalid+state+token")
72
+
73
+ # Remove used state token
74
+ del state_tokens[state]
75
+
76
+ try:
77
+ # Create flow instance
78
+ flow = google_auth_oauthlib.flow.Flow.from_client_config(
79
+ CLIENT_CONFIG,
80
+ scopes=SCOPES,
81
+ state=state
82
+ )
83
+ flow.redirect_uri = REDIRECT_URI
84
+
85
+ # Exchange authorization code for tokens
86
+ flow.fetch_token(code=code)
87
+
88
+ # Get user info from ID token
89
+ credentials = flow.credentials
90
+ request_session = requests.Request()
91
+ id_info = id_token.verify_oauth2_token(
92
+ credentials.id_token,
93
+ request_session,
94
+ CLIENT_ID
95
+ )
96
+
97
+ # Extract user information
98
+ google_id = id_info['sub']
99
+ email = id_info['email']
100
+ name = id_info.get('name', email.split('@')[0])
101
+ email_verified = id_info.get('email_verified', False)
102
+
103
+ # Create username from email or name
104
+ username = f"google_{google_id[:8]}"
105
+
106
+ # Check if user exists
107
+ existing_user = await service_manager.get_user_data(username)
108
+
109
+ if not existing_user:
110
+ # Create new user
111
+ user_data = UserCreate(
112
+ username=username,
113
+ email=email,
114
+ password=secrets.token_urlsafe(32) # Random password for OAuth users
115
+ )
116
+
117
+ # Create user with email already verified if Google verified it
118
+ await service_manager.create_user(
119
+ user_data,
120
+ roles=["user"],
121
+ skip_verification=email_verified
122
+ )
123
+
124
+ # Update user metadata with Google info
125
+ user_dir = os.path.join("data/users", username)
126
+ google_info = {
127
+ "google_id": google_id,
128
+ "name": name,
129
+ "picture": id_info.get('picture', ''),
130
+ "locale": id_info.get('locale', ''),
131
+ "auth_method": "google_oauth"
132
+ }
133
+ with open(os.path.join(user_dir, "google_info.json"), 'w') as f:
134
+ json.dump(google_info, f, indent=2)
135
+
136
+ # Create JWT token
137
+ user_data = await service_manager.get_user_data(username)
138
+ access_token = create_access_token(data={"sub": username, **user_data.dict()})
139
+
140
+ # Create response with redirect
141
+ response = RedirectResponse(url="/", status_code=303)
142
+
143
+ # Set cookie
144
+ response.set_cookie(
145
+ key="access_token",
146
+ value=access_token,
147
+ max_age=604800, # 1 week
148
+ httponly=True,
149
+ samesite="Lax"
150
+ )
151
+
152
+ return response
153
+
154
+ except Exception as e:
155
+ print(f"Google OAuth error: {e}")
156
+ import traceback
157
+ traceback.print_exc()
158
+ return RedirectResponse(
159
+ url="/login?error=Google+authentication+failed",
160
+ status_code=303
161
+ )
162
+
163
+ @router.get("/google_auth/config_check")
164
+ @public_route()
165
+ async def config_check():
166
+ """Check if Google OAuth is configured"""
167
+ return {
168
+ "configured": bool(CLIENT_ID and CLIENT_SECRET),
169
+ "redirect_uri": REDIRECT_URI
170
+ }
@@ -9,11 +9,58 @@ from lib.providers.services import service_manager
9
9
  import os
10
10
  from lib.session_files import load_session_data
11
11
  from lib.utils.debug import debug_box
12
-
13
- SECRET_KEY = os.environ.get("JWT_SECRET_KEY", None)
14
-
15
- if SECRET_KEY is None:
16
- raise ValueError("JWT_SECRET_KEY environment variable not set")
12
+ import secrets
13
+ from pathlib import Path
14
+
15
+ def get_or_create_jwt_secret():
16
+ secret_key = os.environ.get("JWT_SECRET_KEY", None)
17
+
18
+ if secret_key:
19
+ print("JWT_SECRET_KEY found in environment variables")
20
+ return secret_key
21
+
22
+ # Check if .env file exists and contains JWT_SECRET_KEY
23
+ env_path = Path.cwd() / ".env"
24
+
25
+ if env_path.exists():
26
+ with open(env_path, 'r') as f:
27
+ lines = f.readlines()
28
+ for line in lines:
29
+ if line.strip().startswith('JWT_SECRET_KEY='):
30
+ # Extract the key value
31
+ key_value = line.strip().split('=', 1)[1]
32
+ if key_value:
33
+ print(f"JWT_SECRET_KEY found in {env_path}")
34
+ # Also set it in environment for current session
35
+ os.environ['JWT_SECRET_KEY'] = key_value
36
+ return key_value
37
+
38
+ # If we get here, no key was found anywhere, so generate one
39
+ print("JWT_SECRET_KEY not found, generating new key...")
40
+ secret_key = secrets.token_urlsafe(32)
41
+
42
+ # Save to .env file
43
+ # Check if file exists and needs a newline before appending
44
+ needs_newline = False
45
+ if env_path.exists() and env_path.stat().st_size > 0:
46
+ with open(env_path, 'rb') as f:
47
+ f.seek(-1, 2) # Go to last byte
48
+ last_char = f.read(1)
49
+ needs_newline = last_char != b'\n'
50
+
51
+ with open(env_path, 'a') as f:
52
+ if needs_newline:
53
+ f.write('\n')
54
+ f.write(f"JWT_SECRET_KEY={secret_key}\n")
55
+
56
+ print(f"Generated new JWT_SECRET_KEY and saved to {env_path}")
57
+ os.environ['JWT_SECRET_KEY'] = secret_key
58
+
59
+ return secret_key
60
+
61
+ # Get or create the secret key
62
+ SECRET_KEY = get_or_create_jwt_secret()
63
+ print(f"JWT_SECRET_KEY loaded successfully")
17
64
 
18
65
  ALGORITHM = "HS256"
19
66
  ACCESS_TOKEN_EXPIRE_MINUTES = 10080 # 1 week
@@ -3,7 +3,8 @@ from .providers.commands import command_manager
3
3
  import os
4
4
  import json
5
5
  from .chatlog import ChatLog
6
- from typing import TypeVar, Type, Protocol, runtime_checkable
6
+ from .chatlog import extract_delegate_task_log_ids, find_child_logs_by_parent_id, find_chatlog_file
7
+ from typing import TypeVar, Type, Protocol, runtime_checkable, Set
7
8
  from .utils.debug import debug_box
8
9
  contexts = {}
9
10
 
@@ -29,7 +30,7 @@ CommandSetT = TypeVar('CommandSetT', bound=BaseCommandSet)
29
30
 
30
31
  class ChatContext:
31
32
 
32
- def __init__(self, command_manager_=None, service_manager_=None, user=None, log_id=None):
33
+ def __init__(self, command_manager_=None, service_manager_=None, user=None, log_id=None, parent_log_id=None):
33
34
  if not user:
34
35
  raise ValueError('User is required to create a chat context')
35
36
  else:
@@ -39,6 +40,7 @@ class ChatContext:
39
40
  self._commands = command_manager.functions
40
41
  self._services = service_manager.functions
41
42
  self.response_started = False
43
+ self.current_model = None
42
44
  self.uncensored = False
43
45
  if user is None:
44
46
  raise ValueError('User is required to create a chat context. Use SYSTEM if no user')
@@ -66,6 +68,7 @@ class ChatContext:
66
68
  self.agent_name = None
67
69
  self.name = None
68
70
  self.log_id = None
71
+ self.parent_log_id = None
69
72
  if log_id is not None:
70
73
  self.log_id = log_id
71
74
  else:
@@ -142,7 +145,10 @@ class ChatContext:
142
145
  pass
143
146
  self.flags = self.agent.get('flags', [])
144
147
  self.data['log_id'] = log_id
145
- self.chat_log = ChatLog(log_id=log_id, agent=self.agent_name, user=self.username)
148
+ parent_log_id = None
149
+ if self.parent_log_id:
150
+ parent_log_id = self.parent_log_id
151
+ self.chat_log = ChatLog(log_id=log_id, agent=self.agent_name, user=self.username, parent_log_id=parent_log_id)
146
152
  self.uncensored = True
147
153
  else:
148
154
  raise ValueError('Context file not found for id:', log_id)
@@ -160,5 +166,95 @@ class ChatContext:
160
166
  if name in self._commands:
161
167
  self.command_manager.context = self
162
168
  return getattr(self.command_manager, name)
169
+ raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
170
+
171
+ @classmethod
172
+ async def delete_session_by_id(cls, log_id: str, user: str, agent: str, cascade: bool = True, visited_log_ids: Set[str] = None):
173
+ if visited_log_ids is None:
174
+ visited_log_ids = set()
175
+
176
+ if log_id in visited_log_ids:
177
+ print(f"Skipping already visited log_id for deletion: {log_id}")
178
+ return
179
+ visited_log_ids.add(log_id)
180
+
181
+ print(f"Attempting to delete session: log_id={log_id}, user={user}, agent={agent}")
182
+
183
+ # --- Cascading Deletion ---
184
+ if cascade:
185
+ messages_for_child_finding = []
186
+ chatlog_dir_base = os.environ.get('CHATLOG_DIR', 'data/chat')
187
+ chatlog_file_path_current = os.path.join(chatlog_dir_base, user, agent, f'chatlog_{log_id}.json')
188
+
189
+ if os.path.exists(chatlog_file_path_current):
190
+ try:
191
+ with open(chatlog_file_path_current, 'r') as f:
192
+ log_data = json.load(f)
193
+ messages_for_child_finding = log_data.get('messages', [])
194
+ except Exception as e:
195
+ print(f"Error reading chatlog {chatlog_file_path_current} for child finding: {e}")
196
+
197
+ delegated_child_ids = extract_delegate_task_log_ids(messages_for_child_finding)
198
+ parented_child_ids = find_child_logs_by_parent_id(log_id)
199
+ all_child_log_ids = set(delegated_child_ids) | set(parented_child_ids)
200
+
201
+ for child_id in all_child_log_ids:
202
+ if child_id in visited_log_ids: # Check again before processing child
203
+ continue
204
+ child_log_path = find_chatlog_file(child_id) # This searches across all users/agents
205
+ if child_log_path:
206
+ try:
207
+ relative_path = os.path.relpath(child_log_path, chatlog_dir_base)
208
+ path_components = relative_path.split(os.sep)
209
+ # Expecting {user}/{agent}/chatlog_{child_id}.json
210
+ if len(path_components) >= 3 and path_components[-1] == f"chatlog_{child_id}.json":
211
+ child_user = path_components[0]
212
+ child_agent = path_components[1]
213
+ print(f"Recursively deleting child session: log_id={child_id}, user={child_user}, agent={child_agent}")
214
+ await cls.delete_session_by_id(child_id, child_user, child_agent, cascade=True, visited_log_ids=visited_log_ids)
215
+ else:
216
+ print(f"Could not parse user/agent from child log path: {child_log_path} relative to {chatlog_dir_base}")
217
+ except Exception as e:
218
+ print(f"Error processing child log {child_id} (path: {child_log_path}): {e}")
219
+ else:
220
+ print(f"Could not find chatlog file for child_id: {child_id} during cascade.")
221
+
222
+ # --- Delete Current Session's Files ---
223
+ # ChatLog File
224
+ chatlog_file_to_delete = os.path.join(os.environ.get('CHATLOG_DIR', 'data/chat'), user, agent, f'chatlog_{log_id}.json')
225
+ if os.path.exists(chatlog_file_to_delete):
226
+ try:
227
+ os.remove(chatlog_file_to_delete)
228
+ print(f"Deleted chatlog file: {chatlog_file_to_delete}")
229
+ except Exception as e:
230
+ print(f"Error deleting chatlog file {chatlog_file_to_delete}: {e}")
163
231
  else:
164
- pass
232
+ print(f"Chatlog file not found for deletion: {chatlog_file_to_delete}")
233
+
234
+ # ChatContext File (Agent is not part of the context file path structure)
235
+ context_file_to_delete = os.path.join('data/context', user, f'context_{log_id}.json')
236
+ if os.path.exists(context_file_to_delete):
237
+ try:
238
+ os.remove(context_file_to_delete)
239
+ print(f"Deleted context file: {context_file_to_delete}")
240
+ except Exception as e:
241
+ print(f"Error deleting context file {context_file_to_delete}: {e}")
242
+ else:
243
+ print(f"Context file not found for deletion: {context_file_to_delete}")
244
+
245
+ # --- Clear In-Memory Cache ---
246
+ if log_id in contexts: # 'contexts' is the global dict at module level
247
+ try:
248
+ del contexts[log_id]
249
+ print(f"Removed log_id {log_id} from in-memory contexts cache.")
250
+ except Exception as e:
251
+ print(f"Error removing log_id {log_id} from in-memory contexts cache: {e}")
252
+
253
+ async def delete(self, cascade: bool = True):
254
+ if not self.log_id or not self.username or not self.agent_name:
255
+ error_msg = "log_id, username, or agent_name not set on ChatContext instance. Cannot call instance delete."
256
+ print(f"Error: {error_msg}")
257
+ raise ValueError(error_msg)
258
+
259
+ print(f"Instance delete called for log_id={self.log_id}, user={self.username}, agent={self.agent_name}")
260
+ await ChatContext.delete_session_by_id(self.log_id, self.username, self.agent_name, cascade=cascade)
mindroot/lib/chatlog.py CHANGED
@@ -8,9 +8,10 @@ import time
8
8
  from mindroot.lib.utils.debug import debug_box
9
9
 
10
10
  class ChatLog:
11
- def __init__(self, log_id=0, agent=None, context_length: int = 4096, user: str = None):
11
+ def __init__(self, log_id=0, agent=None, parent_log_id=None, context_length: int = 4096, user: str = None):
12
12
  self.log_id = log_id
13
13
  self.messages = []
14
+ self.parent_log_id = parent_log_id
14
15
  self.agent = agent
15
16
  if user is None or user == '' or user == 'None':
16
17
  raise ValueError('User must be provided')
@@ -32,10 +33,13 @@ class ChatLog:
32
33
  if not os.path.exists(self.log_dir):
33
34
  os.makedirs(self.log_dir)
34
35
  self.load_log()
36
+
35
37
  def _get_log_data(self) -> Dict[str, any]:
36
38
  return {
37
39
  'agent': self.agent,
38
- 'messages': self.messages
40
+ 'log_id': self.log_id,
41
+ 'messages': self.messages,
42
+ 'parent_log_id': self.parent_log_id
39
43
  }
40
44
 
41
45
  def _calculate_message_length(self, message: Dict[str, str]) -> int:
@@ -124,20 +128,13 @@ class ChatLog:
124
128
  log_data = json.load(f)
125
129
  self.agent = log_data.get('agent')
126
130
  self.messages = log_data.get('messages', [])
131
+ self.parent_log_id = log_data.get('parent_log_id', None)
127
132
  print("Loaded log file at ", log_file)
128
133
  print("Message length: ", len(self.messages))
129
134
  else:
130
135
  print("Could not find log file at ", log_file)
131
136
  self.messages = []
132
137
 
133
- def delete_log(self) -> None:
134
- log_file = os.path.join(self.log_dir, f'chatlog_{self.log_id}.json')
135
- if os.path.exists(log_file):
136
- os.remove(log_file)
137
- print("Deleted log file at ", log_file)
138
- else:
139
- print("Could not find log file at ", log_file)
140
-
141
138
  def count_tokens(self) -> Dict[str, int]:
142
139
  """
143
140
  Count tokens in the chat log, providing both sequence totals and cumulative request totals.
@@ -198,6 +195,34 @@ def find_chatlog_file(log_id: str) -> str:
198
195
 
199
196
  return None
200
197
 
198
+ def find_child_logs_by_parent_id(parent_log_id: str) -> List[str]:
199
+ """
200
+ Find all chat logs that have the given parent_log_id.
201
+
202
+ Args:
203
+ parent_log_id: The parent log ID to search for
204
+
205
+ Returns:
206
+ List of log IDs that have this parent_log_id
207
+ """
208
+ child_log_ids = []
209
+ chat_dir = os.environ.get('CHATLOG_DIR', 'data/chat')
210
+
211
+ # Search through all chatlog files
212
+ for root, dirs, files in os.walk(chat_dir):
213
+ for file in files:
214
+ if file.startswith("chatlog_") and file.endswith(".json"):
215
+ try:
216
+ with open(os.path.join(root, file), 'r') as f:
217
+ log_data = json.load(f)
218
+ if log_data.get('parent_log_id') == parent_log_id:
219
+ # Extract log_id from the data
220
+ child_log_ids.append(log_data.get('log_id'))
221
+ except (json.JSONDecodeError, IOError):
222
+ continue
223
+
224
+ return child_log_ids
225
+
201
226
  def extract_delegate_task_log_ids(messages: List[Dict]) -> List[str]:
202
227
  """
203
228
  Extract log IDs from delegate_task commands in messages.
@@ -331,6 +356,9 @@ def count_tokens_for_log_id(log_id: str) -> Dict[str, int]:
331
356
  # Load the chat log
332
357
  with open(chatlog_path, 'r') as f:
333
358
  log_data = json.load(f)
359
+
360
+ # Get parent_log_id if it exists
361
+ parent_log_id = log_data.get('parent_log_id')
334
362
 
335
363
  # Create a temporary ChatLog instance to count tokens
336
364
  temp_log = ChatLog(log_id=log_id, user="system", agent=log_data.get('agent', 'unknown'))
@@ -349,9 +377,19 @@ def count_tokens_for_log_id(log_id: str) -> Dict[str, int]:
349
377
  # Find delegated task log IDs
350
378
  delegated_log_ids = extract_delegate_task_log_ids(temp_log.messages)
351
379
 
352
- # Recursively count tokens for delegated tasks
353
- for delegated_id in delegated_log_ids:
354
- delegated_counts = count_tokens_for_log_id(delegated_id)
380
+ # Also find child logs by parent_log_id
381
+ child_logs_by_parent = find_child_logs_by_parent_id(log_id)
382
+
383
+ # Combine all child log IDs (delegated tasks and parent_log_id children)
384
+ all_child_log_ids = set(delegated_log_ids) | set(child_logs_by_parent)
385
+
386
+ # If this log has a parent_log_id, we should not double-count it
387
+ # (it will be counted as part of its parent's cumulative total)
388
+ # But we still want to count its own children
389
+
390
+ # Recursively count tokens for all child tasks
391
+ for child_id in all_child_log_ids:
392
+ delegated_counts = count_tokens_for_log_id(child_id)
355
393
  if delegated_counts:
356
394
  combined_counts['input_tokens_sequence'] += delegated_counts['input_tokens_sequence']
357
395
  combined_counts['output_tokens_sequence'] += delegated_counts['output_tokens_sequence']
@@ -372,4 +410,4 @@ def count_tokens_for_log_id(log_id: str) -> Dict[str, int]:
372
410
  # Save to cache
373
411
  save_token_counts_to_cache(log_id, token_counts)
374
412
 
375
- return token_counts
413
+ return token_counts
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindroot
3
- Version: 8.8.0
3
+ Version: 8.10.0
4
4
  Summary: MindRoot AI Agent Framework
5
5
  Requires-Python: >=3.9
6
6
  License-File: LICENSE
@@ -30,4 +30,7 @@ Requires-Dist: bcrypt
30
30
  Requires-Dist: rich
31
31
  Requires-Dist: colorama
32
32
  Requires-Dist: python-dotenv
33
+ Requires-Dist: google-auth
34
+ Requires-Dist: google-auth-oauthlib
35
+ Requires-Dist: google-auth-httplib2
33
36
  Dynamic: license-file
@@ -38,7 +38,7 @@ mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png,sha25
38
38
  mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico,sha256=5tNfs1TPDqu8ltrcvAKXw_n7tpkRNXP7rlpdtAqyitk,15406
39
39
  mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest,sha256=ep4Hzh9zhmiZF2At3Fp1dQrYQuYF_3ZPZxc1KcGBvwQ,263
40
40
  mindroot/coreplugins/admin/static/js/agent-editor.js,sha256=ZCJBNQ-l4kJj-ZufYuzEg45ZpqxwliNmxuqUa2GNiqY,2825
41
- mindroot/coreplugins/admin/static/js/agent-form.js,sha256=76d2VmiyMcUiVraBCq6Pk9NkFQbkcRRlG5gtfamGDko,37376
41
+ mindroot/coreplugins/admin/static/js/agent-form.js,sha256=NBPHY8L23n007gX0r8J_9gZZltobzQ4DVwWcwBv4cRs,38053
42
42
  mindroot/coreplugins/admin/static/js/agent-list.js,sha256=86mqFyHspx9EnzJpgUDyeAgEq-jcqQ4v96CrgfUXoGM,2239
43
43
  mindroot/coreplugins/admin/static/js/base.js,sha256=xGCA6ngMapQ_jqMgHXg__CS3R46qprycOjkKTFDCMlA,1307
44
44
  mindroot/coreplugins/admin/static/js/github-import.js,sha256=iP-O2WrbKumyDetDjK7EQdi9yivFe6IlvHZLd2qo1dA,3658
@@ -51,7 +51,7 @@ mindroot/coreplugins/admin/static/js/missing-commands.js,sha256=adNF9GWN981_KX7H
51
51
  mindroot/coreplugins/admin/static/js/model-preferences.js,sha256=J0G7gcGACaPyslWJO42urf5wbZZsqO0LyPicAu-uV_Y,3365
52
52
  mindroot/coreplugins/admin/static/js/notification.js,sha256=296rVCr6MNtzvzdzW3bGiMa231-BnWJtwZZ_sDWX-3c,5633
53
53
  mindroot/coreplugins/admin/static/js/persona-editor.js,sha256=xO2jobJXwqGY7Uajj3vQxyERubsHZovIPF_8FHpvzFE,8604
54
- mindroot/coreplugins/admin/static/js/plugin-advanced-install.js,sha256=urj95n_Jcp_ZlSoLIwaw9dfA6SHCoosISbaP3-dxxHs,6950
54
+ mindroot/coreplugins/admin/static/js/plugin-advanced-install.js,sha256=nrCpHlQTd9c_SRIqR0MNqsqgocUPL1K7_yfA99f-MlM,7679
55
55
  mindroot/coreplugins/admin/static/js/plugin-base.js,sha256=KWp5DqueHtyTxYKbuHMoFpoFXrfMbIjzK4M1ulAR9m8,5095
56
56
  mindroot/coreplugins/admin/static/js/plugin-index-browser.js,sha256=P-V4wqlYGxjr7oF2LiD5ti8Is3wtSsKPwpRgRJpT0VI,10028
57
57
  mindroot/coreplugins/admin/static/js/plugin-install-dialog.js,sha256=ty_dZ9dZcpp9El1j3eY4Z6Wp8iZ5WNkf_lHSV-W1IhA,8216
@@ -439,7 +439,7 @@ mindroot/coreplugins/agent/preferred_models.default.json,sha256=Zpi6psgjhY750vyR
439
439
  mindroot/coreplugins/agent/providers.default.json,sha256=FPmY5qVOrBy_Z4RgDJWQwLwxd-zWWI83nnAE6z5fEeg,1524
440
440
  mindroot/coreplugins/agent/system.j2.backup,sha256=itPx-urDBtKBqwps5T6yly4M9gX45AdrM-sznwefG_U,8927
441
441
  mindroot/coreplugins/agent/Assistant/agent.json,sha256=-WPUqffe5XNUTUb23zFRBW97jl4pBbleq_MMiH4U55c,206
442
- mindroot/coreplugins/agent/templates/system.jinja2,sha256=GKEztbJJKUvNodXAHlihRwocK7FtE_w7R1M69kyFmDs,10515
442
+ mindroot/coreplugins/agent/templates/system.jinja2,sha256=EODOqlKUawqbrEwZl2lEnSAt_imqc3b5Npj4VlSC2Jc,11127
443
443
  mindroot/coreplugins/api_keys/__init__.py,sha256=znoBzjEYNIDi7AD3NQMntC4MINqLofXh75L5_Ez8_bY,138
444
444
  mindroot/coreplugins/api_keys/api_key_manager.py,sha256=GH2V1PGnpLguAB5KMumgPgVwTXoft1F0FsvBhD781go,3205
445
445
  mindroot/coreplugins/api_keys/cli.py,sha256=h9VSt3-89AH2-uM7JS8TCfKYpGG79tChU-Ynr1R18Vc,1253
@@ -448,12 +448,12 @@ mindroot/coreplugins/api_keys/router.py,sha256=pz6VjUDPryKxYgnPt8-5AP1P-wjIIQq2c
448
448
  mindroot/coreplugins/api_keys/inject/admin.jinja2,sha256=t50he2aeK_GJ6838LekBcGjkYRbo5p6OHeTWtlggbyU,372
449
449
  mindroot/coreplugins/api_keys/static/js/api-key-manager.js,sha256=imqlhd85Z-1e7uxDlprphGV_N467WKo8_BYVQsJJ1V0,5327
450
450
  mindroot/coreplugins/chat/__init__.py,sha256=0Zs02XeMuTCBdKHvtpO-utxyWmFaQJpH-O1tCiVBw9I,141
451
- mindroot/coreplugins/chat/commands.py,sha256=QozVxkLqtP6rlK3tSVLThfZz6nE7G_YygcmDzZdgb7c,16773
451
+ mindroot/coreplugins/chat/commands.py,sha256=vlgGOvwvjpCbSsW25x4HaeFzeRNWXoEKrdqNpwX_EGg,17077
452
452
  mindroot/coreplugins/chat/format_result_msgs.py,sha256=daEdpEyAJIa8b2VkCqSKcw8PaExcB6Qro80XNes_sHA,2
453
453
  mindroot/coreplugins/chat/mod.py,sha256=Xydjv3feKJJRbwdiB7raqiQnWtaS_2GcdC9bXYQX3nE,425
454
454
  mindroot/coreplugins/chat/models.py,sha256=GRcRuDUAJFpyWERPMxkxUaZ21igNlWeeamriruEKiEQ,692
455
- mindroot/coreplugins/chat/router.py,sha256=ucSQ6_wztDq2PCUP0D4KHL5JaFUAaBtmCQy-4iI8e8c,9087
456
- mindroot/coreplugins/chat/services.py,sha256=Ngv_TceFeXn2EzlZbSbR8Iv1E4M7qL7o3q1L7tufFq4,17835
455
+ mindroot/coreplugins/chat/router.py,sha256=u7zSFwRenVzdyZxSE81mBzqeOHgNgubdvHGNycC8zoI,11260
456
+ mindroot/coreplugins/chat/services.py,sha256=Wc_PzC9yT53vWKhiuh9wBrvdAigD5OKfwPwQT-27bXA,17950
457
457
  mindroot/coreplugins/chat/utils.py,sha256=BiE14PpsAcQSO5vbU88klHGm8cAXJDXxgVgva-EXybU,155
458
458
  mindroot/coreplugins/chat/static/assistant.png,sha256=oAt1ctkFKLSPBoAZGNnSixooW9ANVIk1GwniauVWDXo,215190
459
459
  mindroot/coreplugins/chat/static/mindgen.png,sha256=fN3E3oOFvAGYjJq-Pvg2f75jIMv7kg5WRU0EeEbxCWg,235353
@@ -923,6 +923,11 @@ mindroot/coreplugins/events/mod.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329N
923
923
  mindroot/coreplugins/events/router.py,sha256=a-77306_fQPNHPXP5aYtbpfC0gbqMBNRu04aYOh75L4,3587
924
924
  mindroot/coreplugins/events/backup/mod.py,sha256=9QeJpg6WKwxRdjiKVHD1froguTe86FS2-2wWm1B9xa8,1832
925
925
  mindroot/coreplugins/events/backup/router_backup.py,sha256=ImU8xoxdSd45V1yOFVqdtDQ614V6CMsDZQ1gtJj0Mnk,254
926
+ mindroot/coreplugins/google_auth/README.md,sha256=9BTSYrU-k22RshkKDsszZwrB40xFu-1JUBi34QD52I8,2801
927
+ mindroot/coreplugins/google_auth/__init__.py,sha256=qw8b_7YoN67q1kEdXYXmQkXycF1NaYb3dMbjP-6FsUs,19
928
+ mindroot/coreplugins/google_auth/mod.py,sha256=wVhrOK7gw7kTjGRCS3nlhbELHPxydUqKMDQcjdGIUfU,61
929
+ mindroot/coreplugins/google_auth/router.py,sha256=15ix01TVTexfhgGRAhkNiQeHZamj-jqNDBNVQm1yqW4,5519
930
+ mindroot/coreplugins/google_auth/inject/login.jinja2,sha256=Vlihmk8vgxup0Y6Pvi5nzvbZUQyhUzZH0I5qREW_Vj8,1944
926
931
  mindroot/coreplugins/home/mod.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
927
932
  mindroot/coreplugins/home/router.py,sha256=kzPg2eIimG_2Qa1bZ0gKCmoo2uzd8GurrePODOO1How,1982
928
933
  mindroot/coreplugins/home/static/css/dark.css,sha256=Q9FHaEsf9xeJjtouyKgr1Su6vTzsN07NHxxqDrDfyx8,14259
@@ -1694,7 +1699,7 @@ mindroot/coreplugins/index/static/js/lit-html/node/directives/until.js.map,sha25
1694
1699
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
1695
1700
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
1696
1701
  mindroot/coreplugins/jwt_auth/__init__.py,sha256=qFCBnx0oAKTtMSXiPEa7VXOIlWDTU-5CY0XvodgSUlM,79
1697
- mindroot/coreplugins/jwt_auth/middleware.py,sha256=wwd6U6uRH-mB_weeLy3g3_HhrQTJaVeFBSYGQ-sOOPc,7519
1702
+ mindroot/coreplugins/jwt_auth/middleware.py,sha256=Is8haTLe9K4PdN8hrpoN9lqee6fYfI8Viww6oZfTNIk,9227
1698
1703
  mindroot/coreplugins/jwt_auth/mod.py,sha256=AfRDh9vyGGTE0qLdEOXl2TZYufYxqjsE34uIDQXq--o,1039
1699
1704
  mindroot/coreplugins/jwt_auth/role_checks.py,sha256=bruZIIBSOvXNWB1YZ2s5btrbbXNf18w6MdORpJByV60,1555
1700
1705
  mindroot/coreplugins/jwt_auth/router.py,sha256=ecXYao_UG33UjQF15Hi-tf_X0eFsqLEldyqGpt7JNSw,1162
@@ -1771,8 +1776,8 @@ mindroot/lib/__init__.py,sha256=388n_hMskU0TnZ4xT10US_kFkya-EPBjWcv7AZf_HOk,74
1771
1776
  mindroot/lib/buchatlog.py,sha256=LJZc3ksKgJcStltmHrrwNLaON3EDzhOKVAWj0Wl22wk,5861
1772
1777
  mindroot/lib/buchatlog2.py,sha256=Va9FteBWePEjWD9OZcw-OtQfEb-IoCVGTmJeMRaX9is,13729
1773
1778
  mindroot/lib/butemplates.py,sha256=gfHGPTOjvoEenXsR7xokNuqMjOAPuC2DawheH1Ae4bU,12196
1774
- mindroot/lib/chatcontext.py,sha256=OR63K63NMjaV7kXDf0MIcvOXFZhdiqr7LKgbyfZzjkE,6211
1775
- mindroot/lib/chatlog.py,sha256=6brFJASM7r1qlRX3t1HsXHehEnkaUW5dwmCG3WIPOqc,14690
1779
+ mindroot/lib/chatcontext.py,sha256=yJQOC0lhS-M7sk0oHet8W3B8urxUZBRwEkvQlDUpsws,11702
1780
+ mindroot/lib/chatlog.py,sha256=F5rKxiDotLvJnZVjHlbUrChRLdFkbS4V2gNcAQ-XE-s,16176
1776
1781
  mindroot/lib/json_escape.py,sha256=5cAmAdNbnYX2uyfQcnse2fFtNI0CdB-AfZ23RwaDm-k,884
1777
1782
  mindroot/lib/model_selector.py,sha256=Wz-8NZoiclmnhLeCNnI3WCuKFmjsO5HE4bK5F8GpZzU,1397
1778
1783
  mindroot/lib/parent_templates.py,sha256=elcQFFwrFtfAYfQOSTs06aiDDigN1f1R2f8I1V-wj6Q,2731
@@ -1820,9 +1825,9 @@ mindroot/protocols/services/stream_chat.py,sha256=fMnPfwaB5fdNMBLTEg8BXKAGvrELKH
1820
1825
  mindroot/registry/__init__.py,sha256=40Xy9bmPHsgdIrOzbtBGzf4XMqXVi9P8oZTJhn0r654,151
1821
1826
  mindroot/registry/component_manager.py,sha256=WZFNPg4SNvpqsM5NFiC2DpgmrJQCyR9cNhrCBpp30Qk,995
1822
1827
  mindroot/registry/data_access.py,sha256=NgNMamxIjaKeYxzxnVaQz1Y-Rm0AI51si3788_JHUTM,5316
1823
- mindroot-8.8.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1824
- mindroot-8.8.0.dist-info/METADATA,sha256=rLdkd-0bi6EYoltbj4FBPv5xnoCziteqvJIzFeSCBEM,792
1825
- mindroot-8.8.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
1826
- mindroot-8.8.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1827
- mindroot-8.8.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1828
- mindroot-8.8.0.dist-info/RECORD,,
1828
+ mindroot-8.10.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1829
+ mindroot-8.10.0.dist-info/METADATA,sha256=hZ6UII6f1aM5Dhvh2mN7Rst9znemJwqt_CO2suVYO58,892
1830
+ mindroot-8.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1831
+ mindroot-8.10.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1832
+ mindroot-8.10.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1833
+ mindroot-8.10.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5