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.
- mindroot/coreplugins/admin/static/js/agent-form.js +38 -22
- mindroot/coreplugins/admin/static/js/plugin-advanced-install.js +43 -20
- mindroot/coreplugins/agent/templates/system.jinja2 +18 -0
- mindroot/coreplugins/chat/commands.py +11 -2
- mindroot/coreplugins/chat/router.py +48 -1
- mindroot/coreplugins/chat/services.py +4 -2
- mindroot/coreplugins/google_auth/README.md +76 -0
- mindroot/coreplugins/google_auth/__init__.py +1 -0
- mindroot/coreplugins/google_auth/inject/login.jinja2 +69 -0
- mindroot/coreplugins/google_auth/mod.py +1 -0
- mindroot/coreplugins/google_auth/router.py +170 -0
- mindroot/coreplugins/jwt_auth/middleware.py +52 -5
- mindroot/lib/chatcontext.py +100 -4
- mindroot/lib/chatlog.py +52 -14
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/METADATA +4 -1
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/RECORD +20 -15
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/WHEEL +1 -1
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/entry_points.txt +0 -0
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-8.8.0.dist-info → mindroot-8.10.0.dist-info}/top_level.txt +0 -0
|
@@ -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 ?
|
|
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=${
|
|
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=${
|
|
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 ===
|
|
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=${
|
|
1103
|
+
.value=${agentForRender.instructions || ''}
|
|
1088
1104
|
@input=${this.handleInputChange}></textarea>
|
|
1089
1105
|
` : html`
|
|
1090
1106
|
<div class="markdown-preview">
|
|
1091
|
-
${unsafeHTML(this.renderMarkdown(
|
|
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=${
|
|
1131
|
+
.value=${agentForRender.technicalInstructions || ''}
|
|
1116
1132
|
@input=${this.handleInputChange}></textarea>
|
|
1117
1133
|
` : html`
|
|
1118
1134
|
<div class="markdown-preview">
|
|
1119
|
-
${unsafeHTML(this.renderMarkdown(
|
|
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=${
|
|
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=${
|
|
1159
|
+
.value=${agentForRender.thinking_level || 'off'}
|
|
1144
1160
|
@change=${this.handleInputChange}>
|
|
1145
1161
|
<option value="off"
|
|
1146
|
-
?selected=${(
|
|
1162
|
+
?selected=${(agentForRender.thinking_level || 'off') === 'off'}>
|
|
1147
1163
|
Off
|
|
1148
1164
|
</option>
|
|
1149
1165
|
<option value="low"
|
|
1150
|
-
?selected=${(
|
|
1166
|
+
?selected=${(agentForRender.thinking_level || 'off') === 'low'}>
|
|
1151
1167
|
Low
|
|
1152
1168
|
</option>
|
|
1153
1169
|
<option value="medium"
|
|
1154
|
-
?selected=${(
|
|
1170
|
+
?selected=${(agentForRender.thinking_level || 'off') === 'medium'}>
|
|
1155
1171
|
Medium
|
|
1156
1172
|
</option>
|
|
1157
1173
|
<option value="high"
|
|
1158
|
-
?selected=${(
|
|
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
|
|
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
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
//
|
|
140
|
-
this.dispatch('plugin-installed');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
mindroot/lib/chatcontext.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
#
|
|
353
|
-
|
|
354
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
456
|
-
mindroot/coreplugins/chat/services.py,sha256=
|
|
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=
|
|
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=
|
|
1775
|
-
mindroot/lib/chatlog.py,sha256=
|
|
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.
|
|
1824
|
-
mindroot-8.
|
|
1825
|
-
mindroot-8.
|
|
1826
|
-
mindroot-8.
|
|
1827
|
-
mindroot-8.
|
|
1828
|
-
mindroot-8.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|