BIG PYTHON REFACTOR
Python scripts moved out of python/ folder to root to be unified with plugins + frontend extension around api calls
frdel committed
Mar 5, 2026 at 17:28 UTC
d02dda3667de4cd092afa657c42b15f3346c0c4e
326 files changed
+1096
-862
AGENTS.md
+4
-4
@@ -105,8 +105,8 @@ Key Files:
105
## Development Patterns & Conventions
106
107
### Backend (Python)
108
-- Context Access: Use from agent import AgentContext, AgentContextType (not python.helpers.context).
109
-- Communication: Use mq from python.helpers.messages to log proactive UI messages:
108
+- Context Access: Use from agent import AgentContext, AgentContextType (not helpers.context).
109
+- Communication: Use mq from helpers.messages to log proactive UI messages:
110
mq.log_user_message(context.id, "Message", source="Plugin")
111
- API Handlers: Derive from ApiHandler in python/helpers/api.py.
112
- Extensions: Use the extension framework in python/helpers/extension.py for lifecycle hooks.
@@ -164,7 +164,7 @@ Key Files:
164
165
### API Handler (Good)
166
```python
167
-from python.helpers.api import ApiHandler, Request, Response
167
+from helpers.api import ApiHandler, Request, Response
168
169
class MyHandler(ApiHandler):
170
async def process(self, input: dict, request: Request) -> dict | Response:
@@ -186,7 +186,7 @@ export const store = createStore("myStore", {
186
187
### Tool Definition (Good)
188
```python
189
-from python.helpers.tool import Tool, ToolResult
189
+from helpers.tool import Tool, ToolResult
190
191
class MyTool(Tool):
192
async def execute(self, arg1: str):
agent.py
+15
-16
@@ -7,7 +7,7 @@ from typing import Any, Awaitable, Coroutine, Dict, Literal
7
from enum import Enum
8
import models
9
10
-from python.helpers import (
10
+from helpers import (
11
extract_tools,
12
files,
13
errors,
@@ -17,22 +17,21 @@ from python.helpers import (
17
dirty_json,
18
subagents,
19
)
20
-from python.helpers import extension
21
-from python.helpers.print_style import PrintStyle
20
+from helpers import extension
21
+from helpers.print_style import PrintStyle
22
23
from langchain_core.prompts import (
24
ChatPromptTemplate,
25
)
26
from langchain_core.messages import SystemMessage, BaseMessage
27
28
-import python.helpers.log as Log
29
-from python.helpers.dirty_json import DirtyJson
30
-from python.helpers.defer import DeferredTask
28
+import helpers.log as Log
29
+from helpers.dirty_json import DirtyJson
30
+from helpers.defer import DeferredTask
31
from typing import Callable
32
-from python.helpers.localization import Localization
33
-from python.helpers.extension import call_extensions, extensible
34
-from python.helpers.errors import RepairableException, InterventionException, HandledException
35
-
32
+from helpers.localization import Localization
33
+from helpers.extension import call_extensions, extensible
34
+from helpers.errors import RepairableException, InterventionException, HandledException
35
36
class AgentContextType(Enum):
37
USER = "user"
@@ -147,7 +146,7 @@ class AgentContext:
146
@classmethod
147
def get_notification_manager(cls):
148
if cls._notification_manager is None:
150
- from python.helpers.notification import NotificationManager # type: ignore
149
+ from helpers.notification import NotificationManager # type: ignore
150
151
cls._notification_manager = NotificationManager()
152
return cls._notification_manager
@@ -177,7 +176,7 @@ class AgentContext:
176
# recursive is not used now, prepared for context hierarchy
177
self.output_data[key] = value
178
180
- @extensible
179
+ # @extensible
180
def output(self):
181
return {
182
"id": self.id,
@@ -859,7 +858,7 @@ class Agent:
858
859
# Try getting tool from MCP first
860
try:
862
- import python.helpers.mcp_handler as mcp_helper
861
+ import helpers.mcp_handler as mcp_helper
862
863
mcp_tool_candidate = mcp_helper.MCPConfig.get_instance().get_tool(
864
self, tool_name
@@ -969,13 +968,13 @@ class Agent:
968
loop_data: LoopData | None,
969
**kwargs,
970
):
972
- from python.tools.unknown import Unknown
973
- from python.helpers.tool import Tool
971
+ from tools.unknown import Unknown
972
+ from helpers.tool import Tool
973
974
classes = []
975
976
# search for tools in agent's folder hierarchy
978
- paths = subagents.get_paths(self, "tools", name + ".py", default_root="python")
977
+ paths = subagents.get_paths(self, "tools", name + ".py")
978
979
for path in paths:
980
try:
agents/_example/extensions/agent_init/_10_example_extension.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
3
# this is an example extension that renames the current agent when initialized
4
# see /extensions folder for all available extension points
agents/_example/tools/example_tool.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
3
# this is an example tool class
4
# don't forget to include instructions in the system prompt by creating
agents/_example/tools/response.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
3
# example of a tool redefinition
4
# the original response tool is in python/tools/response.py
api/agents.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers import subagents
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers import subagents
3
4
5
class Agents(ApiHandler):
api/api_files_get.py
renamed
+3
-3
@@ -1,8 +1,8 @@
1
import base64
2
import os
3
-from python.helpers.api import ApiHandler, Request, Response
4
-from python.helpers import files
5
-from python.helpers.print_style import PrintStyle
3
+from helpers.api import ApiHandler, Request, Response
4
+from helpers import files
5
+from helpers.print_style import PrintStyle
6
import json
7
8
api/api_log_get.py
renamed
+1
-1
@@ -1,5 +1,5 @@
1
from agent import AgentContext
2
-from python.helpers.api import ApiHandler, Request, Response
2
+from helpers.api import ApiHandler, Request, Response
3
4
5
class ApiLogGet(ApiHandler):
api/api_message.py
renamed
+5
-5
@@ -2,11 +2,11 @@ import base64
2
import os
3
from datetime import datetime, timedelta
4
from agent import AgentContext, UserMessage, AgentContextType
5
-from python.helpers.api import ApiHandler, Request, Response
6
-from python.helpers import files, projects
7
-from python.helpers.print_style import PrintStyle
8
-from python.helpers.projects import activate_project
9
-from python.helpers.security import safe_filename
5
+from helpers.api import ApiHandler, Request, Response
6
+from helpers import files, projects
7
+from helpers.print_style import PrintStyle
8
+from helpers.projects import activate_project
9
+from helpers.security import safe_filename
10
from initialize import initialize_agent
11
import threading
12
api/api_reset_chat.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
from agent import AgentContext
2
-from python.helpers.api import ApiHandler, Request, Response
3
-from python.helpers.print_style import PrintStyle
4
-from python.helpers import persist_chat
2
+from helpers.api import ApiHandler, Request, Response
3
+from helpers.print_style import PrintStyle
4
+from helpers import persist_chat
5
import json
6
7
api/api_terminate_chat.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
from agent import AgentContext
2
-from python.helpers.api import ApiHandler, Request, Response
3
-from python.helpers.persist_chat import remove_chat
4
-from python.helpers.print_style import PrintStyle
2
+from helpers.api import ApiHandler, Request, Response
3
+from helpers.persist_chat import remove_chat
4
+from helpers.print_style import PrintStyle
5
import json
6
7
api/backup_create.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response, send_file
2
-from python.helpers.backup import BackupService
3
-from python.helpers.persist_chat import save_tmp_chats
1
+from helpers.api import ApiHandler, Request, Response, send_file
2
+from helpers.backup import BackupService
3
+from helpers.persist_chat import save_tmp_chats
4
5
6
class BackupCreate(ApiHandler):
api/backup_get_defaults.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.backup import BackupService
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.backup import BackupService
3
4
5
class BackupGetDefaults(ApiHandler):
api/backup_inspect.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.backup import BackupService
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.backup import BackupService
3
from werkzeug.datastructures import FileStorage
4
5
api/backup_preview_grouped.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.backup import BackupService
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.backup import BackupService
3
from typing import Dict, Any
4
5
api/backup_restore.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
from werkzeug.datastructures import FileStorage
3
-from python.helpers.backup import BackupService
4
-from python.helpers.persist_chat import load_tmp_chats
3
+from helpers.backup import BackupService
4
+from helpers.persist_chat import load_tmp_chats
5
import json
6
7
api/backup_restore_preview.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
from werkzeug.datastructures import FileStorage
3
-from python.helpers.backup import BackupService
3
+from helpers.backup import BackupService
4
import json
5
6
api/backup_test.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.backup import BackupService
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.backup import BackupService
3
4
5
class BackupTest(ApiHandler):
api/banners.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.extension import call_extensions
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.extension import call_extensions
3
4
5
class GetBanners(ApiHandler):
api/cache_reset.py
new
+35
@@ -0,0 +1,35 @@
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import cache
3
+
4
+
5
+class CacheReset(ApiHandler):
6
+ @classmethod
7
+ def requires_auth(cls) -> bool:
8
+ return False
9
+
10
+ @classmethod
11
+ def requires_csrf(cls) -> bool:
12
+ return False
13
+
14
+ @classmethod
15
+ def requires_api_key(cls) -> bool:
16
+ return False
17
+
18
+ @classmethod
19
+ def requires_loopback(cls) -> bool:
20
+ return True
21
+
22
+ @classmethod
23
+ def get_methods(cls) -> list[str]:
24
+ return ["GET", "POST"]
25
+
26
+ async def process(self, input: dict, request: Request) -> dict | Response:
27
+ areas = input.get("areas", [])
28
+
29
+ if not areas:
30
+ cache.clear_all()
31
+ else:
32
+ for area in areas:
33
+ cache.clear(area)
34
+
35
+ return {"ok": True}
api/chat_create.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4
-from python.helpers import settings, projects, guids
4
+from helpers import settings, projects, guids
5
from agent import AgentContext
6
7
@@ -26,7 +26,7 @@ class CreateChat(ApiHandler):
26
new_context.set_output_data(projects.CONTEXT_DATA_KEY_PROJECT, current_data_2)
27
28
# New context should appear in other tabs' chat lists via state_push.
29
- from python.helpers.state_monitor_integration import mark_dirty_all
29
+ from helpers.state_monitor_integration import mark_dirty_all
30
mark_dirty_all(reason="api.chat_create.CreateChat")
31
32
return {
api/chat_export.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
-from python.helpers import persist_chat
3
+from helpers import persist_chat
4
5
class ExportChat(ApiHandler):
6
async def process(self, input: Input, request: Request) -> Output:
api/chat_files_path_get.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import files, projects, settings
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import files, projects, settings
3
4
5
class GetChatFilesPath(ApiHandler):
api/chat_load.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4
-from python.helpers import persist_chat
4
+from helpers import persist_chat
5
6
class LoadChats(ApiHandler):
7
async def process(self, input: Input, request: Request) -> Output:
api/chat_remove.py
renamed
+4
-4
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
from agent import AgentContext
3
-from python.helpers import persist_chat
4
-from python.helpers.task_scheduler import TaskScheduler
3
+from helpers import persist_chat
4
+from helpers.task_scheduler import TaskScheduler
5
6
7
class RemoveChat(ApiHandler):
@@ -26,7 +26,7 @@ class RemoveChat(ApiHandler):
26
await scheduler.remove_task_by_uuid(task.uuid)
27
28
# Context removal affects global chat/task lists in all tabs.
29
- from python.helpers.state_monitor_integration import mark_dirty_all
29
+ from helpers.state_monitor_integration import mark_dirty_all
30
mark_dirty_all(reason="api.chat_remove.RemoveChat")
31
32
return {
api/chat_reset.py
renamed
+4
-4
@@ -1,8 +1,8 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4
-from python.helpers import persist_chat
5
-from python.helpers.task_scheduler import TaskScheduler
4
+from helpers import persist_chat
5
+from helpers.task_scheduler import TaskScheduler
6
7
8
class Reset(ApiHandler):
@@ -19,7 +19,7 @@ class Reset(ApiHandler):
19
persist_chat.remove_msg_files(ctxid)
20
21
# Reset updates context metadata (log guid/version) and must refresh other tabs' lists.
22
- from python.helpers.state_monitor_integration import mark_dirty_all
22
+ from helpers.state_monitor_integration import mark_dirty_all
23
mark_dirty_all(reason="api.chat_reset.Reset")
24
25
return {
api/csrf_token.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
import secrets
2
from urllib.parse import urlparse
3
-from python.helpers.api import (
3
+from helpers.api import (
4
ApiHandler,
5
Input,
6
Output,
@@ -8,7 +8,7 @@ from python.helpers.api import (
8
Response,
9
session,
10
)
11
-from python.helpers import runtime, dotenv, login
11
+from helpers import runtime, dotenv, login
12
import fnmatch
13
14
ALLOWED_ORIGINS_KEY = "ALLOWED_ORIGINS"
@@ -104,7 +104,7 @@ class GetCsrfToken(ApiHandler):
104
105
# always allow tunnel url if running
106
try:
107
- from python.api.tunnel_proxy import process as tunnel_api_process
107
+ from api.tunnel_proxy import process as tunnel_api_process
108
109
tunnel = await tunnel_api_process({"action": "get"})
110
if tunnel and isinstance(tunnel, dict) and tunnel["success"]:
api/ctx_window_get.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
-from python.helpers import tokens
3
+from helpers import tokens
4
5
6
class GetCtxWindow(ApiHandler):
api/delete_work_dir_file.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
3
4
-from python.helpers.file_browser import FileBrowser
5
-from python.helpers import files, runtime
6
-from python.api import get_work_dir_files
4
+from helpers.file_browser import FileBrowser
5
+from helpers import files, runtime
6
+from api import get_work_dir_files
7
8
9
class DeleteWorkDirFile(ApiHandler):
api/download_work_dir_file.py
renamed
+3
-3
@@ -4,9 +4,9 @@ import mimetypes
4
import os
5
6
from flask import Response
7
-from python.helpers.api import ApiHandler, Input, Output, Request
8
-from python.helpers import files, runtime
9
-from python.api import file_info
7
+from helpers.api import ApiHandler, Input, Output, Request
8
+from helpers import files, runtime
9
+from api import file_info
10
from urllib.parse import quote
11
12
api/edit_work_dir_file.py
renamed
+3
-3
@@ -1,9 +1,9 @@
1
import mimetypes
2
import os
3
4
-from python.helpers.api import ApiHandler, Input, Output, Request
5
-from python.helpers.file_browser import FileBrowser
6
-from python.helpers import runtime, files
4
+from helpers.api import ApiHandler, Input, Output, Request
5
+from helpers.file_browser import FileBrowser
6
+from helpers import runtime, files
7
8
MAX_EDIT_FILE_SIZE = 1024 * 1024
9
BINARY_SAMPLE_SIZE = 10 * 1024
api/file_info.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
import os
2
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
3
-from python.helpers import files, runtime
2
+from helpers.api import ApiHandler, Input, Output, Request, Response
3
+from helpers import files, runtime
4
from typing import TypedDict
5
6
class FileInfoApi(ApiHandler):
api/get_work_dir_files.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers.file_browser import FileBrowser
3
-from python.helpers import runtime, files
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers.file_browser import FileBrowser
3
+from helpers import runtime, files
4
5
class GetWorkDirFiles(ApiHandler):
6
api/health.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import errors, git
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import errors, git
3
4
class HealthCheck(ApiHandler):
5
api/history_get.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
4
class GetHistory(ApiHandler):
api/image_get.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
import base64
2
import os
3
-from python.helpers.api import ApiHandler, Request, Response, send_file
4
-from python.helpers import files, runtime
3
+from helpers.api import ApiHandler, Request, Response, send_file
4
+from helpers import files, runtime
5
import io
6
from mimetypes import guess_type
7
api/load_webui_extensions.py
renamed
+3
-3
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import plugins
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import extension
3
4
5
class LoadWebuiExtensions(ApiHandler):
@@ -15,6 +15,6 @@ class LoadWebuiExtensions(ApiHandler):
15
if not extension_point:
16
return Response(status=400, response="Missing extension_point")
17
18
- exts = plugins.get_webui_extensions(agent=None, extension_point=extension_point, filters=filters)
18
+ exts = extension.get_webui_extensions(agent=None, extension_point=extension_point, filters=filters)
19
20
return {"extensions": exts or []}
api/logout.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler, Request, session
1
+from helpers.api import ApiHandler, Request, session
2
3
4
class ApiLogout(ApiHandler):
api/mcp_server_get_detail.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
from typing import Any
3
4
-from python.helpers.mcp_handler import MCPConfig
4
+from helpers.mcp_handler import MCPConfig
5
6
7
class McpServerGetDetail(ApiHandler):
api/mcp_server_get_log.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
from typing import Any
3
4
-from python.helpers.mcp_handler import MCPConfig
4
+from helpers.mcp_handler import MCPConfig
5
6
7
class McpServerGetLog(ApiHandler):
api/mcp_servers_apply.py
renamed
+3
-3
@@ -1,10 +1,10 @@
1
import time
2
-from python.helpers.api import ApiHandler, Request, Response
2
+from helpers.api import ApiHandler, Request, Response
3
4
from typing import Any
5
6
-from python.helpers.mcp_handler import MCPConfig
7
-from python.helpers.settings import set_settings_delta
6
+from helpers.mcp_handler import MCPConfig
7
+from helpers.settings import set_settings_delta
8
9
10
class McpServersApply(ApiHandler):
api/mcp_servers_status.py
renamed
+2
-2
@@ -1,8 +1,8 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
from typing import Any
4
5
-from python.helpers.mcp_handler import MCPConfig
5
+from helpers.mcp_handler import MCPConfig
6
7
8
class McpServersStatuss(ApiHandler):
api/message.py
renamed
+4
-4
@@ -1,10 +1,10 @@
1
from agent import AgentContext, UserMessage
2
-from python.helpers.api import ApiHandler, Request, Response
2
+from helpers.api import ApiHandler, Request, Response
3
4
-from python.helpers import files, extension, message_queue as mq
4
+from helpers import files, extension, message_queue as mq
5
import os
6
-from python.helpers.security import safe_filename
7
-from python.helpers.defer import DeferredTask
6
+from helpers.security import safe_filename
7
+from helpers.defer import DeferredTask
8
9
10
class Message(ApiHandler):
api/message_async.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
from agent import AgentContext
2
-from python.helpers.defer import DeferredTask
3
-from python.api.message import Message
2
+from helpers.defer import DeferredTask
3
+from api.message import Message
4
5
6
class MessageAsync(Message):
api/message_queue_add.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import message_queue as mq
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import message_queue as mq
3
from agent import AgentContext
4
-from python.helpers.state_monitor_integration import mark_dirty_for_context
4
+from helpers.state_monitor_integration import mark_dirty_for_context
5
6
7
class MessageQueueAdd(ApiHandler):
api/message_queue_remove.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import message_queue as mq
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import message_queue as mq
3
from agent import AgentContext
4
-from python.helpers.state_monitor_integration import mark_dirty_for_context
4
+from helpers.state_monitor_integration import mark_dirty_for_context
5
6
class MessageQueueRemove(ApiHandler):
7
"""Remove message(s) from queue."""
api/message_queue_send.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import message_queue as mq
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import message_queue as mq
3
from agent import AgentContext
4
-from python.helpers.state_monitor_integration import mark_dirty_for_context
4
+from helpers.state_monitor_integration import mark_dirty_for_context
5
6
class MessageQueueSend(ApiHandler):
7
"""Send queued message(s) immediately."""
api/notification_create.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler
1
+from helpers.api import ApiHandler
2
from flask import Request, Response
3
-from python.helpers.notification import NotificationManager, NotificationPriority, NotificationType
3
+from helpers.notification import NotificationManager, NotificationPriority, NotificationType
4
5
6
class NotificationCreate(ApiHandler):
api/notifications_clear.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler
1
+from helpers.api import ApiHandler
2
from flask import Request, Response
3
from agent import AgentContext
4
api/notifications_history.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler
1
+from helpers.api import ApiHandler
2
from flask import Request, Response
3
from agent import AgentContext
4
api/notifications_mark_read.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler
1
+from helpers.api import ApiHandler
2
from flask import Request, Response
3
from agent import AgentContext
4
api/nudge.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
class Nudge(ApiHandler):
4
async def process(self, input: dict, request: Request) -> dict | Response:
api/pause.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
4
class Pause(ApiHandler):
api/plugins.py
renamed
+2
-2
@@ -4,8 +4,8 @@ import subprocess
4
import sys
5
from datetime import datetime, timezone
6
7
-from python.helpers.api import ApiHandler, Request, Response
8
-from python.helpers import plugins, files
7
+from helpers.api import ApiHandler, Request, Response
8
+from helpers import plugins, files
9
10
11
class Plugins(ApiHandler):
api/plugins_list.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers import plugins
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers import plugins
3
4
class PluginsList(ApiHandler):
5
async def process(self, input: Input, request: Request) -> Output:
api/poll.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers.state_snapshot import build_snapshot
3
+from helpers.state_snapshot import build_snapshot
4
5
6
class Poll(ApiHandler):
api/projects.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
2
-from python.helpers import projects
3
-from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
+from helpers import projects
3
+from helpers.notification import NotificationManager, NotificationType, NotificationPriority
4
5
6
class Projects(ApiHandler):
api/rename_work_dir_file.py
renamed
+4
-4
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.file_browser import FileBrowser
3
-from python.helpers import runtime
4
-from python.api import get_work_dir_files
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.file_browser import FileBrowser
3
+from helpers import runtime
4
+from api import get_work_dir_files
5
6
7
class RenameWorkDirFile(ApiHandler):
api/restart.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import process
3
+from helpers import process
4
5
class Restart(ApiHandler):
6
async def process(self, input: dict, request: Request) -> dict | Response:
api/rfc.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import runtime
3
+from helpers import runtime
4
5
class RFC(ApiHandler):
6
api/scheduler_task_create.py
renamed
+5
-5
@@ -1,11 +1,11 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.task_scheduler import (
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.task_scheduler import (
3
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskSchedule,
4
serialize_task, parse_task_schedule, parse_task_plan, TaskType
5
)
6
-from python.helpers.projects import load_basic_project_data
7
-from python.helpers.localization import Localization
8
-from python.helpers.print_style import PrintStyle
6
+from helpers.projects import load_basic_project_data
7
+from helpers.localization import Localization
8
+from helpers.print_style import PrintStyle
9
import random
10
11
api/scheduler_task_delete.py
renamed
+4
-4
@@ -1,8 +1,8 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.task_scheduler import TaskScheduler, TaskState
3
-from python.helpers.localization import Localization
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.task_scheduler import TaskScheduler, TaskState
3
+from helpers.localization import Localization
4
from agent import AgentContext
5
-from python.helpers import persist_chat
5
+from helpers import persist_chat
6
7
8
class SchedulerTaskDelete(ApiHandler):
api/scheduler_task_run.py
renamed
+4
-4
@@ -1,7 +1,7 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.task_scheduler import TaskScheduler, TaskState
3
-from python.helpers.print_style import PrintStyle
4
-from python.helpers.localization import Localization
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.task_scheduler import TaskScheduler, TaskState
3
+from helpers.print_style import PrintStyle
4
+from helpers.localization import Localization
5
6
7
class SchedulerTaskRun(ApiHandler):
api/scheduler_task_update.py
renamed
+3
-3
@@ -1,9 +1,9 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.task_scheduler import (
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.task_scheduler import (
3
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskState,
4
serialize_task, parse_task_schedule, parse_task_plan
5
)
6
-from python.helpers.localization import Localization
6
+from helpers.localization import Localization
7
8
9
class SchedulerTaskUpdate(ApiHandler):
api/scheduler_tasks_list.py
renamed
+4
-4
@@ -1,8 +1,8 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request
2
-from python.helpers.task_scheduler import TaskScheduler
1
+from helpers.api import ApiHandler, Input, Output, Request
2
+from helpers.task_scheduler import TaskScheduler
3
import traceback
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers.localization import Localization
4
+from helpers.print_style import PrintStyle
5
+from helpers.localization import Localization
6
7
8
class SchedulerTasksList(ApiHandler):
api/scheduler_tick.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
from datetime import datetime
2
3
-from python.helpers.api import ApiHandler, Input, Output, Request
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers.task_scheduler import TaskScheduler
6
-from python.helpers.localization import Localization
3
+from helpers.api import ApiHandler, Input, Output, Request
4
+from helpers.print_style import PrintStyle
5
+from helpers.task_scheduler import TaskScheduler
6
+from helpers.localization import Localization
7
8
9
class SchedulerTick(ApiHandler):
api/settings_get.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import settings
3
+from helpers import settings
4
5
class GetSettings(ApiHandler):
6
async def process(self, input: dict, request: Request) -> dict | Response:
api/settings_set.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import settings
3
+from helpers import settings
4
5
from typing import Any
6
api/settings_workdir_file_structure.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import file_tree, files
3
+from helpers import file_tree, files
4
5
6
class SettingsWorkdirFileStructure(ApiHandler):
api/skills.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
2
-from python.helpers import runtime, skills, projects, files
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
+from helpers import runtime, skills, projects, files
3
4
5
class Skills(ApiHandler):
api/skills_import.py
renamed
+3
-3
@@ -5,9 +5,9 @@ import time
5
import uuid
6
from pathlib import Path
7
8
-from python.helpers.api import ApiHandler, Request, Response
9
-from python.helpers import files
10
-from python.helpers.skills_import import import_skills
8
+from helpers.api import ApiHandler, Request, Response
9
+from helpers import files
10
+from helpers.skills_import import import_skills
11
from werkzeug.datastructures import FileStorage
12
from werkzeug.utils import secure_filename
13
api/skills_import_preview.py
renamed
+3
-3
@@ -5,9 +5,9 @@ import time
5
import uuid
6
from pathlib import Path
7
8
-from python.helpers.api import ApiHandler, Request, Response
9
-from python.helpers import files
10
-from python.helpers.skills_import import import_skills
8
+from helpers.api import ApiHandler, Request, Response
9
+from helpers import files
10
+from helpers.skills_import import import_skills
11
from werkzeug.datastructures import FileStorage
12
from werkzeug.utils import secure_filename
13
api/subagents.py
renamed
+3
-3
@@ -1,9 +1,9 @@
1
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
2
-from python.helpers import subagents
1
+from helpers.api import ApiHandler, Input, Output, Request, Response
2
+from helpers import subagents
3
from typing import TYPE_CHECKING
4
5
if TYPE_CHECKING:
6
- from python.helpers import projects
6
+ from helpers import projects
7
8
class Subagents(ApiHandler):
9
async def process(self, input: Input, request: Request) -> Output:
api/synthesize.py
renamed
+2
-2
@@ -1,8 +1,8 @@
1
# api/synthesize.py
2
3
-from python.helpers.api import ApiHandler, Request, Response
3
+from helpers.api import ApiHandler, Request, Response
4
5
-from python.helpers import runtime, settings, kokoro_tts
5
+from helpers import runtime, settings, kokoro_tts
6
7
class Synthesize(ApiHandler):
8
async def process(self, input: dict, request: Request) -> dict | Response:
api/transcribe.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
3
-from python.helpers import runtime, settings, whisper
3
+from helpers import runtime, settings, whisper
4
5
class Transcribe(ApiHandler):
6
async def process(self, input: dict, request: Request) -> dict | Response:
api/tunnel.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import runtime
3
-from python.helpers.tunnel_manager import TunnelManager
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import runtime
3
+from helpers.tunnel_manager import TunnelManager
4
5
class Tunnel(ApiHandler):
6
async def process(self, input: dict, request: Request) -> dict | Response:
api/tunnel_proxy.py
renamed
+4
-4
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import dotenv, runtime
3
-from python.helpers.tunnel_manager import TunnelManager
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import dotenv, runtime
3
+from helpers.tunnel_manager import TunnelManager
4
import requests
5
6
@@ -34,5 +34,5 @@ async def process(input: dict) -> dict | Response:
34
return {"error": str(e)}
35
else:
36
# forward to API handler directly
37
- from python.api.tunnel import process as local_process
37
+ from api.tunnel import process as local_process
38
return await local_process(input)
api/upload.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import files
3
-from python.helpers.security import safe_filename
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import files
3
+from helpers.security import safe_filename
4
5
6
class UploadFile(ApiHandler):
api/upload_work_dir_files.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
import base64
2
from werkzeug.datastructures import FileStorage
3
-from python.helpers.api import ApiHandler, Request, Response
4
-from python.helpers.file_browser import FileBrowser
5
-from python.helpers import files, runtime
6
-from python.api import get_work_dir_files
3
+from helpers.api import ApiHandler, Request, Response
4
+from helpers.file_browser import FileBrowser
5
+from helpers import files, runtime
6
+from api import get_work_dir_files
7
import os
8
9
docs/developer/contributing-skills.md
+8
-8
@@ -37,16 +37,16 @@ Skills you create here can be used in any of these platforms!
37
38
```bash
39
# Create a new skill interactively
40
-python -m python.helpers.skills_cli create my-skill-name
40
+python -m helpers.skills_cli create my-skill-name
41
42
# List all available skills
43
-python -m python.helpers.skills_cli list
43
+python -m helpers.skills_cli list
44
45
# Validate a skill
46
-python -m python.helpers.skills_cli validate my-skill-name
46
+python -m helpers.skills_cli validate my-skill-name
47
48
# Search skills
49
-python -m python.helpers.skills_cli search "keyword"
49
+python -m helpers.skills_cli search "keyword"
50
```
51
52
### Manual Creation
@@ -161,7 +161,7 @@ Ask yourself:
161
162
```bash
163
# Using CLI
164
-python -m python.helpers.skills_cli create my-awesome-skill
164
+python -m helpers.skills_cli create my-awesome-skill
165
166
# Or manually
167
mkdir -p usr/skills/my-awesome-skill
@@ -225,7 +225,7 @@ This skill includes helper scripts:
225
226
```bash
227
# Validate the skill
228
-python -m python.helpers.skills_cli validate my-awesome-skill
228
+python -m helpers.skills_cli validate my-awesome-skill
229
230
# Test in Agent Zero
231
# Start the agent and ask it to perform the task your skill handles
@@ -281,7 +281,7 @@ One skill = one expertise area. If your skill is getting too long, split it:
281
282
1. **Validate Structure:**
283
```bash
284
- python -m python.helpers.skills_cli validate my-skill
284
+ python -m helpers.skills_cli validate my-skill
285
```
286
287
2. **Test Semantic Recall:**
@@ -304,7 +304,7 @@ One skill = one expertise area. If your skill is getting too long, split it:
304
305
2. **Create Your Skill:**
306
```bash
307
- python -m python.helpers.skills_cli create my-skill
307
+ python -m helpers.skills_cli create my-skill
308
# Edit usr/skills/my-skill/SKILL.md
309
```
310
docs/developer/extensions.md
+4
-4
@@ -48,7 +48,7 @@ To create a custom extension:
48
49
```python
50
# File: /agents/_example/extensions/agent_init/_10_example_extension.py
51
-from python.helpers.extension import Extension
51
+from helpers.extension import Extension
52
53
class ExampleExtension(Extension):
54
async def execute(self, **kwargs):
@@ -80,7 +80,7 @@ When a tool with the same name is requested, Agent Zero first checks for its exi
80
81
```python
82
# File: /agents/_example/tools/response.py
83
-from python.helpers.tool import Tool, Response
83
+from helpers.tool import Tool, Response
84
85
# example of a tool redefinition
86
# the original response tool is in python/tools/response.py
@@ -145,8 +145,8 @@ When a prompt file is processed, Agent Zero automatically looks for a correspond
145
If you have a prompt file `agent.system.tools.md`, you can create `agent.system.tools.py` alongside it:
146
147
```python
148
-from python.helpers.files import VariablesPlugin
149
-from python.helpers import files
148
+from helpers.files import VariablesPlugin
149
+from helpers import files
150
151
class Tools(VariablesPlugin):
152
def get_variables(self, file: str, backup_dirs: list[str] | None = None) -> dict[str, Any]:
docs/developer/notifications.md
+1
-1
@@ -10,7 +10,7 @@ Quick guide for using the notification system in Agent Zero.
10
Use `AgentNotification` helper methods anywhere in your Python code:
11
12
```python
13
-from python.helpers.notification import AgentNotification
13
+from helpers.notification import AgentNotification
14
15
# Basic notifications
16
AgentNotification.info("Operation completed")
docs/developer/websockets.md
+1
-1
@@ -146,7 +146,7 @@ Handlers are discovered deterministically from `python/websocket_handlers/`:
146
Create handler modules under the appropriate namespace entry and inherit from `WebSocketHandler`.
147
148
```python
149
-from python.helpers.websocket import WebSocketHandler
149
+from helpers.websocket import WebSocketHandler
150
151
class DashboardHandler(WebSocketHandler):
152
@classmethod
extensions/python/.gitkeep
renamed
extensions/python/agent_Agent_handle_exception_end/_40_handle_intervention_exception.py
renamed
+5
-5
@@ -1,10 +1,10 @@
1
from datetime import datetime, timezone
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers.localization import Localization
5
-from python.helpers.errors import InterventionException
6
-from python.helpers import errors
7
-from python.helpers.print_style import PrintStyle
4
+from helpers.localization import Localization
5
+from helpers.errors import InterventionException
6
+from helpers import errors
7
+from helpers.print_style import PrintStyle
8
9
10
class HandleInterventionException(Extension):
extensions/python/agent_Agent_handle_exception_end/_50_handle_repairable_exception.py
renamed
+5
-5
@@ -1,10 +1,10 @@
1
from datetime import datetime, timezone
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers.localization import Localization
5
-from python.helpers.errors import RepairableException
6
-from python.helpers import errors
7
-from python.helpers.print_style import PrintStyle
4
+from helpers.localization import Localization
5
+from helpers.errors import RepairableException
6
+from helpers import errors
7
+from helpers.print_style import PrintStyle
8
9
10
class HandleRepairableException(Extension):
extensions/python/agent_Agent_handle_exception_end/_90_handle_critical_exception.py
renamed
+4
-4
@@ -1,10 +1,10 @@
1
import asyncio
2
3
-from python.helpers.extension import Extension
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers import errors
3
+from helpers.extension import Extension
4
+from helpers.print_style import PrintStyle
5
+from helpers import errors
6
7
-from python.helpers.errors import HandledException
7
+from helpers.errors import HandledException
8
9
10
class HandleCriticalException(Extension):
extensions/python/agent_init/.gitkeep
renamed
extensions/python/agent_init/_10_initial_message.py
renamed
+3
-1
@@ -1,6 +1,6 @@
1
import json
2
from agent import LoopData
3
-from python.helpers.extension import Extension
3
+from helpers.extension import Extension
4
5
6
class InitialMessage(Extension):
@@ -10,6 +10,8 @@ class InitialMessage(Extension):
10
Add an initial greeting message when first user message is processed.
11
Called only once per session via _process_chain method.
12
"""
13
+ if not self.agent:
14
+ return
15
16
# Only add initial message for main agent (A0), not subordinate agents
17
if self.agent.number != 0:
extensions/python/agent_init/_15_load_profile_settings.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
from initialize import initialize_agent
2
-from python.helpers import dirty_json, files, subagents, projects
3
-from python.helpers.extension import Extension
2
+from helpers import dirty_json, files, subagents, projects
3
+from helpers.extension import Extension
4
5
6
class LoadProfileSettings(Extension):
extensions/python/banners/.gitkeep
renamed
extensions/python/banners/_10_unsecured_connection.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers import dotenv
1
+from helpers.extension import Extension
2
+from helpers import dotenv
3
import re
4
5
extensions/python/banners/_20_missing_api_key.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers import settings as settings_helper
1
+from helpers.extension import Extension
2
+from helpers import settings as settings_helper
3
import models
4
5
extensions/python/banners/_30_system_resources.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
import os
3
import psutil
4
extensions/python/before_main_llm_call/.gitkeep
renamed
extensions/python/before_main_llm_call/_10_log_for_stream.py
renamed
+7
-4
@@ -1,15 +1,18 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
-from python.helpers.log import LogItem
6
-from python.helpers import log
5
+from helpers.log import LogItem
6
+from helpers import log
7
import math
8
9
10
class LogForStream(Extension):
11
12
async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
13
+ if not self.agent:
14
+ return
15
+
16
# create log message and store it in loop data temporary params
17
if "log_item_generating" not in loop_data.params_temporary:
18
loop_data.params_temporary["log_item_generating"] = (
extensions/python/error_format/.gitkeep
renamed
extensions/python/error_format/_10_mask_errors.py
renamed
+5
-2
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
4
5
class MaskErrorSecrets(Extension):
6
7
async def execute(self, **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# Get error data from kwargs
12
msg = kwargs.get("msg")
13
if not msg:
extensions/python/hist_add_before/.gitkeep
renamed
extensions/python/hist_add_before/_10_mask_content.py
renamed
+5
-2
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
4
5
class MaskHistoryContent(Extension):
6
7
async def execute(self, **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# Get content data from kwargs
12
content_data = kwargs.get("content_data")
13
if not content_data:
extensions/python/hist_add_tool_result/.gitkeep
renamed
extensions/python/hist_add_tool_result/_90_save_tool_call_file.py
renamed
+5
-2
@@ -1,12 +1,15 @@
1
from typing import Any
2
-from python.helpers.extension import Extension
3
-from python.helpers import files, persist_chat
2
+from helpers.extension import Extension
3
+from helpers import files, persist_chat
4
import os, re
5
6
LEN_MIN = 500
7
8
class SaveToolCallFile(Extension):
9
async def execute(self, data: dict[str, Any] | None = None, **kwargs):
10
+ if not self.agent:
11
+ return
12
+
13
if not data:
14
return
15
extensions/python/job_loop/.gitkeep
renamed
extensions/python/message_loop_end/.gitkeep
renamed
extensions/python/message_loop_end/_10_organize_history.py
renamed
+5
-2
@@ -1,12 +1,15 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
-from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
3
+from helpers.defer import DeferredTask, THREAD_BACKGROUND
4
5
DATA_NAME_TASK = "_organize_history_task"
6
7
8
class OrganizeHistory(Extension):
9
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
10
+ if not self.agent:
11
+ return
12
+
13
# is there a running task? if yes, skip this round, the wait extension will double check the context size
14
task: DeferredTask|None = self.agent.get_data(DATA_NAME_TASK)
15
if task and not task.is_ready():
extensions/python/message_loop_end/_90_save_chat.py
renamed
+5
-2
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData, AgentContextType
3
-from python.helpers import persist_chat
3
+from helpers import persist_chat
4
5
6
class SaveChat(Extension):
7
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# Skip saving BACKGROUND contexts as they should be ephemeral
12
if self.agent.context.type == AgentContextType.BACKGROUND:
13
return
extensions/python/message_loop_prompts_after/.gitkeep
renamed
extensions/python/message_loop_prompts_after/_60_include_current_datetime.py
renamed
+5
-2
@@ -1,11 +1,14 @@
1
from datetime import datetime, timezone
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers.localization import Localization
4
+from helpers.localization import Localization
5
6
7
class IncludeCurrentDatetime(Extension):
8
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9
+ if not self.agent:
10
+ return
11
+
12
# get current datetime
13
current_datetime = Localization.get().utc_dt_to_localtime_str(
14
datetime.now(timezone.utc), sep=" ", timespec="seconds"
extensions/python/message_loop_prompts_after/_65_include_loaded_skills.py
renamed
+6
-3
@@ -1,11 +1,14 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers import skills
3
-from python.tools.skills_tool import DATA_NAME_LOADED_SKILLS
1
+from helpers.extension import Extension
2
+from helpers import skills
3
+from tools.skills_tool import DATA_NAME_LOADED_SKILLS
4
from agent import LoopData
5
6
7
class IncludeLoadedSkills(Extension):
8
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9
+ if not self.agent:
10
+ return
11
+
12
extras = loop_data.extras_persistent
13
14
# Get loaded skills names
extensions/python/message_loop_prompts_after/_70_include_agent_info.py
renamed
+3
-1
@@ -1,9 +1,11 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
4
5
class IncludeAgentInfo(Extension):
6
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
7
+ if not self.agent:
8
+ return
9
10
# read prompt
11
agent_info_prompt = self.agent.read_prompt(
extensions/python/message_loop_prompts_after/_75_include_workdir_extras.py
renamed
+8
-6
@@ -1,13 +1,15 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
-from python.helpers import projects
4
-from python.helpers import settings
5
-from python.helpers import runtime
6
-from python.helpers import file_tree
7
-from python.helpers import files
3
+from helpers import projects
4
+from helpers import settings
5
+from helpers import runtime
6
+from helpers import file_tree
7
+from helpers import files
8
9
class IncludeWorkdirExtras(Extension):
10
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
11
+ if not self.agent:
12
+ return
13
14
project_name = projects.get_context_project_name(self.agent.context)
15
extensions/python/message_loop_prompts_before/.gitkeep
extensions/python/message_loop_prompts_before/_90_organize_history_wait.py
renamed
+5
-3
@@ -1,11 +1,13 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
-from python.extensions.message_loop_end._10_organize_history import DATA_NAME_TASK
4
-from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
3
+from extensions.python.message_loop_end._10_organize_history import DATA_NAME_TASK
4
+from helpers.defer import DeferredTask, THREAD_BACKGROUND
5
6
7
class OrganizeHistoryWait(Extension):
8
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9
+ if not self.agent:
10
+ return
11
12
# sync action only required if the history is too large, otherwise leave it in background
13
while self.agent.history.is_over_limit():
extensions/python/message_loop_start/.gitkeep
extensions/python/message_loop_start/_10_iteration_no.py
renamed
+4
-1
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import Agent, LoopData
3
4
DATA_NAME_ITER_NO = "iteration_no"
5
6
class IterationNo(Extension):
7
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# total iteration number
12
no = self.agent.get_data(DATA_NAME_ITER_NO) or 0
13
self.agent.set_data(DATA_NAME_ITER_NO, no + 1)
extensions/python/monologue_end/.gitkeep
extensions/python/monologue_end/_90_waiting_for_input_msg.py
renamed
+4
-1
@@ -1,9 +1,12 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
4
class WaitingForInputMsg(Extension):
5
6
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
7
+ if not self.agent:
8
+ return
9
+
10
# show temp info message
11
if self.agent.number == 0:
12
self.agent.context.log.set_initial_progress()
extensions/python/monologue_start/.gitkeep
extensions/python/monologue_start/_60_rename_chat.py
renamed
+5
-2
@@ -1,5 +1,5 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
@@ -10,6 +10,9 @@ class RenameChat(Extension):
10
asyncio.create_task(self.change_name())
11
12
async def change_name(self):
13
+ if not self.agent:
14
+ return
15
+
16
try:
17
# prepare history
18
history_text = self.agent.history.output_text()
extensions/python/process_chain_end/.gitkeep
extensions/python/process_chain_end/_50_process_queue.py
renamed
+5
-2
@@ -1,6 +1,6 @@
1
import asyncio
2
-from python.helpers.extension import Extension
3
-from python.helpers import message_queue as mq
2
+from helpers.extension import Extension
3
+from helpers import message_queue as mq
4
from agent import AgentContext, Agent, LoopData
5
6
@@ -8,6 +8,9 @@ class ProcessQueue(Extension):
8
"""Process queued messages after monologue ends."""
9
10
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
11
+ if not self.agent:
12
+ return
13
+
14
# Only process for agent0 (main agent)
15
if self.agent.number != 0:
16
return
extensions/python/reasoning_stream/.gitkeep
extensions/python/reasoning_stream/_10_log_from_stream.py
renamed
+7
-5
@@ -1,15 +1,17 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
-from python.helpers.log import LogItem
6
-from python.helpers import log
5
+from helpers.log import LogItem
6
+from helpers import log
7
import math
8
-from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8
+from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10
class LogFromStream(Extension):
11
12
async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
13
+ if not self.agent:
14
+ return
15
16
# thought length indicator
17
length = f"({len(text)})" if text else ""
extensions/python/reasoning_stream_chunk/.gitkeep
extensions/python/reasoning_stream_chunk/_10_mask_stream.py
renamed
+6
-3
@@ -1,9 +1,12 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
4
5
class MaskReasoningStreamChunk(Extension):
6
async def execute(self, **kwargs):
7
+ if not self.agent:
8
+ return
9
+
10
# Get stream data and agent from kwargs
11
stream_data = kwargs.get("stream_data")
12
agent = kwargs.get("agent")
@@ -31,7 +34,7 @@ class MaskReasoningStreamChunk(Extension):
34
35
# Print the processed chunk (this is where printing should happen)
36
if processed_chunk:
34
- from python.helpers.print_style import PrintStyle
37
+ from helpers.print_style import PrintStyle
38
PrintStyle().stream(processed_chunk)
39
except Exception as e:
40
# If masking fails, proceed without masking
extensions/python/reasoning_stream_end/.gitkeep
extensions/python/reasoning_stream_end/_10_mask_end.py
renamed
+2
-2
@@ -1,4 +1,4 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
3
4
class MaskReasoningStreamEnd(Extension):
@@ -17,7 +17,7 @@ class MaskReasoningStreamEnd(Extension):
17
18
# Print any remaining masked content
19
if tail:
20
- from python.helpers.print_style import PrintStyle
20
+ from helpers.print_style import PrintStyle
21
PrintStyle().stream(tail)
22
23
# Clean up the filter
extensions/python/response_stream/.gitkeep
extensions/python/response_stream/_10_log_from_stream.py
renamed
+7
-5
@@ -1,11 +1,11 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
-from python.helpers.log import LogItem
6
-from python.helpers import log
5
+from helpers.log import LogItem
6
+from helpers import log
7
import math
8
-from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8
+from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10
11
class LogFromStream(Extension):
@@ -17,6 +17,8 @@ class LogFromStream(Extension):
17
parsed: dict = {},
18
**kwargs,
19
):
20
+ if not self.agent:
21
+ return
22
23
heading = build_default_heading(self.agent)
24
if "headline" in parsed:
extensions/python/response_stream/_15_replace_include_alias.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
from typing import Any
2
-from python.helpers.extension import Extension
3
-from python.helpers.strings import replace_file_includes
2
+from helpers.extension import Extension
3
+from helpers.strings import replace_file_includes
4
5
6
class ReplaceIncludeAlias(Extension):
extensions/python/response_stream/_20_live_response.py
renamed
+7
-4
@@ -1,9 +1,9 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
-from python.helpers.log import LogItem
6
-from python.helpers import log
5
+from helpers.log import LogItem
6
+from helpers import log
7
8
9
class LiveResponse(Extension):
@@ -15,6 +15,9 @@ class LiveResponse(Extension):
15
parsed: dict = {},
16
**kwargs,
17
):
18
+ if not self.agent:
19
+ return
20
+
21
try:
22
if (
23
not "tool_name" in parsed
extensions/python/response_stream_chunk/.gitkeep
extensions/python/response_stream_chunk/_10_mask_stream.py
renamed
+6
-3
@@ -1,11 +1,14 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import Agent, LoopData
3
-from python.helpers.secrets import get_secrets_manager
3
+from helpers.secrets import get_secrets_manager
4
5
6
class MaskResponseStreamChunk(Extension):
7
8
async def execute(self, **kwargs):
9
+ if not self.agent:
10
+ return
11
+
12
# Get stream data and agent from kwargs
13
stream_data = kwargs.get("stream_data")
14
agent = kwargs.get("agent")
@@ -33,7 +36,7 @@ class MaskResponseStreamChunk(Extension):
36
37
# Print the processed chunk (this is where printing should happen)
38
if processed_chunk:
36
- from python.helpers.print_style import PrintStyle
39
+ from helpers.print_style import PrintStyle
40
PrintStyle().stream(processed_chunk)
41
except Exception as e:
42
# If masking fails, proceed without masking
extensions/python/response_stream_end/.gitkeep
extensions/python/response_stream_end/_10_mask_end.py
renamed
+3
-3
@@ -1,5 +1,5 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import SecretsManager
1
+from helpers.extension import Extension
2
+from helpers.secrets import SecretsManager
3
4
5
class MaskResponseStreamEnd(Extension):
@@ -18,7 +18,7 @@ class MaskResponseStreamEnd(Extension):
18
19
# Print any remaining masked content
20
if tail:
21
- from python.helpers.print_style import PrintStyle
21
+ from helpers.print_style import PrintStyle
22
PrintStyle().stream(tail)
23
24
# Clean up the filter
extensions/python/response_stream_end/_15_log_from_stream_end.py
renamed
+5
-5
@@ -1,11 +1,11 @@
1
-from python.helpers import persist_chat, tokens
2
-from python.helpers.extension import Extension
1
+from helpers import persist_chat, tokens
2
+from helpers.extension import Extension
3
from agent import LoopData
4
import asyncio
5
-from python.helpers.log import LogItem
6
-from python.helpers import log
5
+from helpers.log import LogItem
6
+from helpers import log
7
import math
8
-from python.extensions.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
8
+from extensions.python.before_main_llm_call._10_log_for_stream import build_heading, build_default_heading
9
10
11
class LogFromStream(Extension):
extensions/python/system_prompt/.gitkeep
extensions/python/system_prompt/_10_system_prompt.py
renamed
+8
-5
@@ -1,9 +1,9 @@
1
from typing import Any
2
-from python.helpers.extension import Extension
3
-from python.helpers.mcp_handler import MCPConfig
2
+from helpers.extension import Extension
3
+from helpers.mcp_handler import MCPConfig
4
from agent import Agent, LoopData
5
-from python.helpers.settings import get_settings
6
-from python.helpers import projects, skills
5
+from helpers.settings import get_settings
6
+from helpers import projects, skills
7
8
9
class SystemPrompt(Extension):
@@ -14,6 +14,9 @@ class SystemPrompt(Extension):
14
loop_data: LoopData = LoopData(),
15
**kwargs: Any
16
):
17
+ if not self.agent:
18
+ return
19
+
20
# append main system prompt and tools
21
main = get_main_prompt(self.agent)
22
tools = get_tools_prompt(self.agent)
@@ -61,7 +64,7 @@ def get_mcp_tools_prompt(agent: Agent):
64
def get_secrets_prompt(agent: Agent):
65
try:
66
# Use lazy import to avoid circular dependencies
64
- from python.helpers.secrets import get_secrets_manager
67
+ from helpers.secrets import get_secrets_manager
68
69
secrets_manager = get_secrets_manager(agent.context)
70
secrets = secrets_manager.get_secrets_for_prompt()
extensions/python/tool_execute_after/.gitkeep
extensions/python/tool_execute_after/_10_mask_secrets.py
renamed
+6
-3
@@ -1,11 +1,14 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
3
-from python.helpers.tool import Response
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
+from helpers.tool import Response
4
5
6
class MaskToolSecrets(Extension):
7
8
async def execute(self, response: Response | None = None, **kwargs):
9
+ if not self.agent:
10
+ return
11
+
12
if not response:
13
return
14
secrets_mgr = get_secrets_manager(self.agent.context)
extensions/python/tool_execute_before/.gitkeep
extensions/python/tool_execute_before/_10_replace_last_tool_output.py
renamed
+4
-1
@@ -1,9 +1,12 @@
1
from typing import Any
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
4
5
class ReplaceLastToolOutput(Extension):
6
async def execute(self, tool_args: dict[str, Any] | None = None, tool_name: str = "", **kwargs):
7
+ if not self.agent:
8
+ return
9
+
10
if not tool_args:
11
return
12
extensions/python/tool_execute_before/_10_unmask_secrets.py
renamed
+5
-2
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
4
5
class UnmaskToolSecrets(Extension):
6
7
async def execute(self, **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# Get tool args from kwargs
12
tool_args = kwargs.get("tool_args")
13
if not tool_args:
extensions/python/user_message_ui/.gitkeep
extensions/python/user_message_ui/_10_update_check.py
renamed
+9
-3
@@ -1,7 +1,7 @@
1
-from python.helpers import notification
2
-from python.helpers.extension import Extension
1
+from helpers import notification
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers import settings, update_check
4
+from helpers import settings, update_check
5
import datetime
6
7
@@ -19,6 +19,9 @@ notification_cooldown_seconds = 60 * 60 * 24
19
class UpdateCheck(Extension):
20
21
async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwargs):
22
+ if not self.agent:
23
+ return
24
+
25
try:
26
global last_check, last_notification_id, last_notification_time
27
@@ -46,6 +49,9 @@ class UpdateCheck(Extension):
49
50
51
def send_notification(self, notif):
52
+ if not self.agent:
53
+ return
54
+
55
notifs = self.agent.context.get_notification_manager()
56
notifs.send_notification(
57
title=notif.get("title", "Newer version available"),
extensions/python/util_model_call_before/.gitkeep
extensions/python/util_model_call_before/_10_mask_secrets.py
renamed
+5
-2
@@ -1,10 +1,13 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers.secrets import get_secrets_manager
1
+from helpers.extension import Extension
2
+from helpers.secrets import get_secrets_manager
3
4
5
class MaskToolSecrets(Extension):
6
7
async def execute(self, **kwargs):
8
+ if not self.agent:
9
+ return
10
+
11
# model call data
12
call_data:dict = kwargs.get("call_data", {})
13
extensions/webui/.gitkeep
extensions/webui/api_call_after/.gitkeep
extensions/webui/api_call_before/.gitkeep
extensions/webui/get_message_handler/.gitkeep
extensions/webui/set_messages_after_loop/.gitkeep
extensions/webui/set_messages_before_loop/.gitkeep
helpers/api.py
renamed
+10
-10
@@ -10,9 +10,9 @@ from flask import Request, Response, jsonify, Flask, session, request, send_file
10
from werkzeug.wrappers.response import Response as BaseResponse
11
from agent import AgentContext
12
from initialize import initialize_agent
13
-from python.helpers.print_style import PrintStyle
14
-from python.helpers.errors import format_error
15
-from python.helpers import files, cache
13
+from helpers.print_style import PrintStyle
14
+from helpers.errors import format_error
15
+from helpers import files, cache
16
17
ThreadLockType = Union[threading.Lock, threading.RLock]
18
@@ -147,7 +147,7 @@ def is_loopback_address(address: str) -> bool:
147
def requires_api_key(f):
148
@wraps(f)
149
async def decorated(*args, **kwargs):
150
- from python.helpers.settings import get_settings
150
+ from helpers.settings import get_settings
151
valid_api_key = get_settings()["mcp_server_token"]
152
153
if api_key := request.headers.get("X-API-KEY"):
@@ -177,7 +177,7 @@ def requires_loopback(f):
177
def requires_auth(f):
178
@wraps(f)
179
async def decorated(*args, **kwargs):
180
- from python.helpers import login
180
+ from helpers import login
181
user_pass_hash = login.get_credentials_hash()
182
if not user_pass_hash:
183
return await f(*args, **kwargs)
@@ -191,7 +191,7 @@ def requires_auth(f):
191
def csrf_protect(f):
192
@wraps(f)
193
async def decorated(*args, **kwargs):
194
- from python.helpers import runtime
194
+ from helpers import runtime
195
token = session.get("csrf_token")
196
header = request.headers.get("X-CSRF-Token")
197
cookie = request.cookies.get("csrf_token_" + runtime.get_runtime_id())
@@ -204,8 +204,8 @@ def csrf_protect(f):
204
205
206
def register_api_route(app: Flask, lock: ThreadLockType) -> None:
207
- from python.helpers.extract_tools import load_classes_from_file
208
- from python.helpers import plugins
207
+ from helpers.extract_tools import load_classes_from_file
208
+ from helpers import plugins
209
210
async def _dispatch(path: str) -> BaseResponse:
211
# Return cached wrapped handler if available
@@ -218,8 +218,8 @@ def register_api_route(app: Flask, lock: ThreadLockType) -> None:
218
handler_cls: type[ApiHandler] | None = None
219
220
# Check built-in python/api/<path>.py
221
- builtin_file = files.get_abs_path(f"python/api/{path}.py")
222
- if files.is_in_dir(builtin_file, files.get_abs_path("python/api")) and files.exists(builtin_file):
221
+ builtin_file = files.get_abs_path(f"api/{path}.py")
222
+ if files.is_in_dir(builtin_file, files.get_abs_path("api")) and files.exists(builtin_file):
223
classes = load_classes_from_file(builtin_file, ApiHandler)
224
if classes:
225
handler_cls = classes[0]
helpers/attachment_manager.py
renamed
+2
-2
@@ -3,10 +3,10 @@ import io
3
import base64
4
from PIL import Image
5
from typing import Dict, List, Optional, Tuple
6
-from python.helpers.security import safe_filename
6
+from helpers.security import safe_filename
7
from werkzeug.datastructures import FileStorage
8
9
-from python.helpers.print_style import PrintStyle
9
+from helpers.print_style import PrintStyle
10
11
class AttachmentManager:
12
ALLOWED_EXTENSIONS = {
helpers/backup.py
renamed
+2
-2
@@ -9,8 +9,8 @@ from typing import List, Dict, Any, Optional
9
from pathspec import PathSpec
10
from pathspec.patterns.gitwildmatch import GitWildMatchPattern
11
12
-from python.helpers import files, runtime, git
13
-from python.helpers.print_style import PrintStyle
12
+from helpers import files, runtime, git
13
+from helpers.print_style import PrintStyle
14
15
16
class BackupService:
helpers/browser.py
renamed
+1
-1
@@ -9,7 +9,7 @@
9
# BrowserContext,
10
# )
11
12
-# from python.helpers import files
12
+# from helpers import files
13
14
15
# class NoPageError(Exception):
helpers/browser_use.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers import dotenv
1
+from helpers import dotenv
2
dotenv.save_dotenv_value("ANONYMIZED_TELEMETRY", "false")
3
import browser_use
4
import browser_use.utils
\ No newline at end of file
helpers/browser_use_monkeypatch.py
renamed
+1
-1
@@ -1,6 +1,6 @@
1
from typing import Any
2
from browser_use.llm import ChatGoogle
3
-from python.helpers import dirty_json
3
+from helpers import dirty_json
4
5
6
# ------------------------------------------------------------------------------
helpers/cache.py
renamed
helpers/call_llm.py
renamed
helpers/cloudflare_tunnel._py
renamed
+2
-2
@@ -3,8 +3,8 @@ import platform
3
import requests
4
import subprocess
5
import threading
6
-from python.helpers import files
7
-from python.helpers.print_style import PrintStyle
6
+from helpers import files
7
+from helpers.print_style import PrintStyle
8
9
class CloudflareTunnel:
10
def __init__(self, port: int):
helpers/context.py
renamed
helpers/crypto.py
renamed
helpers/defer.py
renamed
helpers/dirty_json.py
renamed
helpers/docker.py
renamed
+4
-4
@@ -2,10 +2,10 @@ import time
2
import docker
3
import atexit
4
from typing import Optional
5
-from python.helpers.files import get_abs_path
6
-from python.helpers.errors import format_error
7
-from python.helpers.print_style import PrintStyle
8
-from python.helpers.log import Log
5
+from helpers.files import get_abs_path
6
+from helpers.errors import format_error
7
+from helpers.print_style import PrintStyle
8
+from helpers.log import Log
9
10
class DockerContainerManager:
11
def __init__(self, image: str, name: str, ports: Optional[dict[str, int]] = None, volumes: Optional[dict[str, dict[str, str]]] = None,logger: Log|None=None):
helpers/document_query.py
renamed
+3
-3
@@ -4,7 +4,7 @@ import asyncio
4
import aiohttp
5
import json
6
7
-from python.helpers.vector_db import VectorDB
7
+from helpers.vector_db import VectorDB
8
9
os.environ["USER_AGENT"] = "@mixedbread-ai/unstructured" # noqa E402
10
from langchain_unstructured import UnstructuredLoader # noqa E402
@@ -22,8 +22,8 @@ from langchain_community.document_loaders.parsers.images import TesseractBlobPar
22
from langchain_core.documents import Document
23
from langchain.schema import SystemMessage, HumanMessage
24
25
-from python.helpers.print_style import PrintStyle
26
-from python.helpers import files, errors
25
+from helpers.print_style import PrintStyle
26
+from helpers import files, errors
27
from agent import Agent
28
29
from langchain.text_splitter import RecursiveCharacterTextSplitter
helpers/dotenv.py
renamed
helpers/duckduckgo_search.py
renamed
helpers/email_client.py
renamed
+4
-4
@@ -13,9 +13,9 @@ import html2text
13
from bs4 import BeautifulSoup
14
from imapclient import IMAPClient
15
16
-from python.helpers import files
17
-from python.helpers.errors import RepairableException, format_error
18
-from python.helpers.print_style import PrintStyle
16
+from helpers import files
17
+from helpers.errors import RepairableException, format_error
18
+from helpers.print_style import PrintStyle
19
20
21
@dataclass
@@ -560,7 +560,7 @@ async def read_messages(
560
List of Message objects
561
562
Example:
563
- from python.helpers.email_client import read_messages
563
+ from helpers.email_client import read_messages
564
messages = await read_messages(
565
server="imap.gmail.com",
566
port=993,
helpers/errors.py
renamed
+11
-6
@@ -14,9 +14,14 @@ def error_text(e: Exception):
14
return str(e)
15
16
17
-def format_error(e: Exception, start_entries=20, end_entries=15, error_message_position:Literal["top", "bottom", "none"] = "top"):
17
+def format_error(
18
+ e: Exception,
19
+ start_entries=20,
20
+ end_entries=15,
21
+ error_message_position: Literal["top", "bottom", "none"] = "top",
22
+):
23
# format traceback from the provided exception instead of the most recent one
19
- traceback_text = ''.join(traceback.format_exception(type(e), e, e.__traceback__))
24
+ traceback_text = "".join(traceback.format_exception(type(e), e, e.__traceback__))
25
# Split the traceback into lines
26
lines = traceback_text.split("\n")
27
@@ -77,15 +82,15 @@ def format_error(e: Exception, start_entries=20, end_entries=15, error_message_p
82
83
class RepairableException(Exception):
84
"""An exception type indicating errors that can be surfaced to the LLM for potential self-repair."""
80
- pass
85
82
-class InterventionException(Exception):
83
- """An exception type raised on user intervention, skipping rest of message loop iteration."""
86
pass
87
+
88
+
89
class InterventionException(Exception):
90
"""An exception type raised on user intervention, skipping rest of message loop iteration."""
91
+
92
pass
93
94
95
class HandledException(Exception):
91
- pass
\ No newline at end of file
96
+ pass
helpers/extension.py
renamed
+40
-14
@@ -1,7 +1,7 @@
1
from abc import abstractmethod
2
from typing import Any
3
-from python.helpers import extract_tools, files
4
-from python.helpers import cache
3
+from helpers import extract_tools, files
4
+from helpers import cache, plugins, subagents
5
from typing import TYPE_CHECKING
6
from functools import wraps
7
import asyncio
@@ -15,7 +15,7 @@ DEFAULT_EXTENSIONS_FOLDER = "python/extensions"
15
USER_EXTENSIONS_FOLDER = "usr/extensions"
16
17
_CACHE_AREA = "extension_folder_classes(extensions)(plugins)"
18
-cache.toggle_area(_CACHE_AREA, False) # cache off for now
18
+cache.toggle_area(_CACHE_AREA, True) # cache off for now
19
20
21
class _Unset:
@@ -58,6 +58,7 @@ def extensible(func):
58
Finally, if ``data["exception"]`` contains an exception it is raised;
59
otherwise ``data["result"]`` is returned.
60
"""
61
+
62
@wraps(func)
63
async def _inner_async(*args, **kwargs):
64
from agent import Agent
@@ -75,7 +76,9 @@ def extensible(func):
76
77
def _get_agent() -> "Agent|None":
78
candidate = kwargs.get("agent")
78
- if isinstance(candidate, Agent) and bool(getattr(candidate, "__dict__", None)):
79
+ if isinstance(candidate, Agent) and bool(
80
+ getattr(candidate, "__dict__", None)
81
+ ):
82
return candidate
83
84
for a in args:
@@ -149,18 +152,14 @@ class Extension:
152
async def call_extensions(
153
extension_point: str, agent: "Agent|None" = None, **kwargs
154
) -> Any:
152
- from python.helpers import projects, subagents, plugins
153
-
155
# search for extension folders in all agent's paths
155
- paths = subagents.get_paths(
156
- agent, "extensions", extension_point, default_root="python"
157
- )
156
+ paths = subagents.get_paths(agent, "extensions/python", extension_point)
157
159
- # Add plugin backend extension paths (plugins/*/extensions/python/{extension_point})
160
- plugin_paths = plugins.get_enabled_plugin_paths(
161
- agent, "extensions", "python", extension_point
162
- )
163
- paths.extend(p for p in plugin_paths if p not in paths)
158
+ # # Add plugin backend extension paths (plugins/*/extensions/python/{extension_point})
159
+ # plugin_paths = plugins.get_enabled_plugin_paths(
160
+ # agent, "extensions", "python", extension_point
161
+ # )
162
+ # paths.extend(p for p in plugin_paths if p not in paths)
163
164
all_exts = [cls for path in paths for cls in _get_extensions(path)]
165
@@ -179,6 +178,33 @@ async def call_extensions(
178
await cls(agent=agent).execute(**kwargs)
179
180
181
+def get_webui_extensions(
182
+ agent: "Agent | None", extension_point: str, filters: list[str] | None = None
183
+):
184
+ entries: list[str] = []
185
+ effective_filters = filters or ["*"]
186
+
187
+ # search for extension folders in all agent's paths
188
+ folders = subagents.get_paths(
189
+ agent,
190
+ "extensions/webui",
191
+ extension_point,
192
+ )
193
+
194
+ extensions = []
195
+
196
+ for folder in folders:
197
+ for filter in effective_filters:
198
+ pattern = files.get_abs_path(folder, filter)
199
+ extensions.extend(files.find_existing_paths_by_pattern(pattern))
200
+
201
+ for extension in extensions:
202
+ rel_path = files.deabsolute_path(extension)
203
+ entries.append(rel_path)
204
+
205
+ return entries
206
+
207
+
208
def _get_file_from_module(module_name: str) -> str:
209
return module_name.split(".")[-1]
210
helpers/extract_tools.py
renamed
helpers/faiss_monkey_patch.py
renamed
helpers/fasta2a_client.py
renamed
+1
-1
@@ -1,6 +1,6 @@
1
import uuid
2
from typing import Any, Dict, List, Optional
3
-from python.helpers.print_style import PrintStyle
3
+from helpers.print_style import PrintStyle
4
5
try:
6
from fasta2a.client import A2AClient # type: ignore
helpers/fasta2a_server.py
renamed
+4
-4
@@ -6,14 +6,14 @@ from typing import Any, List
6
import contextlib
7
import threading
8
9
-from python.helpers import settings, projects
9
+from helpers import settings, projects
10
from starlette.requests import Request
11
12
# Local imports
13
-from python.helpers.print_style import PrintStyle
13
+from helpers.print_style import PrintStyle
14
from agent import AgentContext, UserMessage, AgentContextType
15
from initialize import initialize_agent
16
-from python.helpers.persist_chat import remove_chat
16
+from helpers.persist_chat import remove_chat
17
18
# Import FastA2A
19
try:
@@ -358,7 +358,7 @@ class DynamicA2AProxy:
358
})
359
return
360
361
- from python.helpers import settings
361
+ from helpers import settings
362
cfg = settings.get_settings()
363
if not cfg["a2a_server_enabled"]:
364
response = b'HTTP/1.1 403 Forbidden\r\n\r\nA2A server is disabled'
helpers/file_browser.py
renamed
+3
-3
@@ -4,11 +4,11 @@ import shutil
4
import base64
5
import subprocess
6
from typing import Dict, List, Tuple, Any
7
-from python.helpers.security import safe_filename
7
+from helpers.security import safe_filename
8
from datetime import datetime
9
10
-from python.helpers import files
11
-from python.helpers.print_style import PrintStyle
10
+from helpers import files
11
+from helpers.print_style import PrintStyle
12
13
14
class FileBrowser:
helpers/file_tree.py
renamed
+1
-1
@@ -8,7 +8,7 @@ from typing import Any, Callable, Iterable, Literal, Optional, Sequence
8
9
from pathspec import PathSpec
10
11
-from python.helpers import files as files_helper
11
+from helpers import files as files_helper
12
13
SORT_BY_NAME = "name"
14
SORT_BY_CREATED = "created"
helpers/files.py
renamed
+6
-6
@@ -12,7 +12,7 @@ import zipfile
12
import glob
13
import mimetypes
14
from simpleeval import simple_eval
15
-from python.helpers import yaml
15
+from helpers import yaml
16
17
AGENTS_DIR = "agents"
18
PLUGINS_DIR = "plugins"
@@ -45,7 +45,7 @@ def load_plugin_variables(
45
46
if plugin_file and exists(plugin_file):
47
48
- from python.helpers import extract_tools
48
+ from helpers import extract_tools
49
50
classes = extract_tools.load_classes_from_file(
51
plugin_file, VariablesPlugin, one_per_file=False
@@ -80,7 +80,7 @@ def load_plugin_variables(
80
return {}
81
82
83
-from python.helpers.strings import sanitize_string
83
+from helpers.strings import sanitize_string
84
85
86
def parse_file(
@@ -536,7 +536,7 @@ def get_abs_path(*relative_paths):
536
def get_abs_path_dockerized(*relative_paths):
537
"Ensures the abs path is dockerized (i.e. /a0/... path)"
538
abs = get_abs_path(*relative_paths)
539
- from python.helpers import runtime
539
+ from helpers import runtime
540
541
if runtime.is_dockerized():
542
return abs
@@ -556,7 +556,7 @@ def deabsolute_path(path: str):
556
557
def fix_dev_path(path: str):
558
"On dev environment, convert /a0/... paths to local absolute paths"
559
- from python.helpers.runtime import is_development
559
+ from helpers.runtime import is_development
560
561
if is_development():
562
if path.startswith("/a0/"):
@@ -589,7 +589,7 @@ def is_dir(*relative_paths):
589
590
def get_base_dir():
591
# Get the base directory from the current file path
592
- base_dir = os.path.dirname(os.path.abspath(os.path.join(__file__, "../../")))
592
+ base_dir = os.path.dirname(os.path.abspath(os.path.join(__file__, "../")))
593
return base_dir
594
595
helpers/git.py
renamed
+2
-2
@@ -4,7 +4,7 @@ import os
4
import subprocess
5
import base64
6
from urllib.parse import urlparse, urlunparse
7
-from python.helpers import files
7
+from helpers import files
8
9
10
def strip_auth_from_url(url: str) -> str:
@@ -167,7 +167,7 @@ def get_repo_status(repo_path: str) -> dict:
167
commit = repo.head.commit
168
last_commit = {
169
"hash": commit.hexsha[:7],
170
- "message": commit.message.split('\n')[0][:80],
170
+ "message": str(commit.message).split("\n")[0][:80],
171
"author": str(commit.author),
172
"date": datetime.fromtimestamp(commit.committed_date).strftime('%Y-%m-%d %H:%M')
173
}
helpers/guids.py
renamed
helpers/history.py
renamed
+1
-1
@@ -5,7 +5,7 @@ from collections.abc import Mapping
5
import json
6
import math
7
from typing import Coroutine, Literal, TypedDict, cast, Union, Dict, List, Any
8
-from python.helpers import messages, tokens, settings, call_llm
8
+from helpers import messages, tokens, settings, call_llm
9
from enum import Enum
10
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage
11
helpers/images.py
renamed
helpers/job_loop.py
renamed
+4
-4
@@ -1,10 +1,10 @@
1
import asyncio
2
from datetime import datetime
3
import time
4
-from python.helpers.task_scheduler import TaskScheduler
5
-from python.helpers.print_style import PrintStyle
6
-from python.helpers import errors
7
-from python.helpers import runtime
4
+from helpers.task_scheduler import TaskScheduler
5
+from helpers.print_style import PrintStyle
6
+from helpers import errors
7
+from helpers import runtime
8
9
10
SLEEP_TIME = 60
helpers/kokoro_tts.py
renamed
+3
-3
@@ -5,9 +5,9 @@ import io
5
import warnings
6
import asyncio
7
import soundfile as sf
8
-from python.helpers import runtime
9
-from python.helpers.print_style import PrintStyle
10
-from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
8
+from helpers import runtime
9
+from helpers.print_style import PrintStyle
10
+from helpers.notification import NotificationManager, NotificationType, NotificationPriority
11
12
warnings.filterwarnings("ignore", category=FutureWarning)
13
warnings.filterwarnings("ignore", category=UserWarning)
helpers/kvp.py
renamed
+1
-1
@@ -6,7 +6,7 @@ import tempfile
6
import threading
7
from typing import Any
8
9
-from python.helpers.files import get_abs_path
9
+from helpers.files import get_abs_path
10
11
_runtime_lock = threading.RLock()
12
_runtime_store: dict[str, Any] = {}
helpers/localization.py
renamed
+2
-2
@@ -1,8 +1,8 @@
1
from datetime import datetime, timezone as dt_timezone, timedelta
2
import pytz # type: ignore
3
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers.dotenv import get_dotenv_value, save_dotenv_value
4
+from helpers.print_style import PrintStyle
5
+from helpers.dotenv import get_dotenv_value, save_dotenv_value
6
7
8
helpers/log.py
renamed
+4
-4
@@ -7,8 +7,8 @@ from collections import OrderedDict
7
from dataclasses import dataclass
8
from typing import Any, Literal, Optional, TYPE_CHECKING, TypeVar, cast
9
10
-from python.helpers.secrets import get_secrets_manager
11
-from python.helpers.strings import truncate_text_by_ratio
10
+from helpers.secrets import get_secrets_manager
11
+from helpers.strings import truncate_text_by_ratio
12
13
14
if TYPE_CHECKING:
@@ -23,7 +23,7 @@ def _lazy_mark_dirty_all(*, reason: str | None = None) -> None:
23
# Lazy import to avoid circular import at module load time (AgentContext -> Log).
24
global _MARK_DIRTY_ALL
25
if _MARK_DIRTY_ALL is None:
26
- from python.helpers.state_monitor_integration import mark_dirty_all
26
+ from helpers.state_monitor_integration import mark_dirty_all
27
28
_MARK_DIRTY_ALL = mark_dirty_all
29
_MARK_DIRTY_ALL(reason=reason)
@@ -33,7 +33,7 @@ def _lazy_mark_dirty_for_context(context_id: str, *, reason: str | None = None)
33
# Lazy import to avoid circular import at module load time (AgentContext -> Log).
34
global _MARK_DIRTY_FOR_CONTEXT
35
if _MARK_DIRTY_FOR_CONTEXT is None:
36
- from python.helpers.state_monitor_integration import mark_dirty_for_context
36
+ from helpers.state_monitor_integration import mark_dirty_for_context
37
38
_MARK_DIRTY_FOR_CONTEXT = mark_dirty_for_context
39
_MARK_DIRTY_FOR_CONTEXT(context_id, reason=reason)
helpers/login.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers import dotenv
1
+from helpers import dotenv
2
import hashlib
3
4
helpers/mcp_handler.py
renamed
+6
-6
@@ -21,9 +21,9 @@ from contextlib import AsyncExitStack
21
from shutil import which
22
from datetime import timedelta
23
import json
24
-from python.helpers import errors
25
-from python.helpers import settings
26
-from python.helpers.log import LogItem
24
+from helpers import errors
25
+from helpers import settings
26
+from helpers.log import LogItem
27
28
import httpx
29
@@ -39,9 +39,9 @@ from anyio.streams.memory import (
39
)
40
41
from pydantic import BaseModel, Field, Discriminator, Tag, PrivateAttr
42
-from python.helpers import dirty_json
43
-from python.helpers.print_style import PrintStyle
44
-from python.helpers.tool import Tool, Response
42
+from helpers import dirty_json
43
+from helpers.print_style import PrintStyle
44
+from helpers.tool import Tool, Response
45
46
47
def normalize_name(name: str) -> str:
helpers/mcp_server.py
renamed
+3
-3
@@ -9,10 +9,10 @@ from fastmcp import FastMCP
9
import contextvars
10
11
from agent import AgentContext, AgentContextType, UserMessage
12
-from python.helpers.persist_chat import remove_chat
12
+from helpers.persist_chat import remove_chat
13
from initialize import initialize_agent
14
-from python.helpers.print_style import PrintStyle
15
-from python.helpers import settings, projects
14
+from helpers.print_style import PrintStyle
15
+from helpers import settings, projects
16
from starlette.middleware import Middleware
17
from starlette.middleware.base import BaseHTTPMiddleware
18
from starlette.exceptions import HTTPException as StarletteHTTPException
helpers/message_queue.py
renamed
+2
-2
@@ -1,12 +1,12 @@
1
import os
2
import uuid
3
from typing import TYPE_CHECKING
4
-from python.helpers import guids
4
+from helpers import guids
5
6
if TYPE_CHECKING:
7
from agent import AgentContext
8
9
-from python.helpers.print_style import PrintStyle
9
+from helpers.print_style import PrintStyle
10
11
QUEUE_KEY = "message_queue"
12
QUEUE_SEQ_KEY = "message_queue_seq"
helpers/messages.py
renamed
helpers/migration.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
import os
2
import json
3
-from python.helpers import files
4
-from python.helpers import subagents
5
-from python.helpers import yaml as yaml_helper
6
-from python.helpers.print_style import PrintStyle
3
+from helpers import files
4
+from helpers import subagents
5
+from helpers import yaml as yaml_helper
6
+from helpers.print_style import PrintStyle
7
8
9
def startup_migration() -> None:
helpers/notification.py
renamed
+5
-5
@@ -115,7 +115,7 @@ class NotificationManager:
115
# Enforce limit
116
self._enforce_limit()
117
118
- from python.helpers.state_monitor_integration import mark_dirty_all
118
+ from helpers.state_monitor_integration import mark_dirty_all
119
mark_dirty_all(reason="notification.NotificationManager.add_notification")
120
return item
121
@@ -175,7 +175,7 @@ class NotificationManager:
175
if not changed_nos:
176
return 0
177
178
- from python.helpers.state_monitor_integration import mark_dirty_all
178
+ from helpers.state_monitor_integration import mark_dirty_all
179
mark_dirty_all(reason="notification.NotificationManager.mark_read_by_ids")
180
return len(changed_nos)
181
@@ -196,7 +196,7 @@ class NotificationManager:
196
if not changed:
197
return
198
199
- from python.helpers.state_monitor_integration import mark_dirty_all
199
+ from helpers.state_monitor_integration import mark_dirty_all
200
mark_dirty_all(reason="notification.NotificationManager._update_item")
201
202
def mark_all_read(self):
@@ -212,7 +212,7 @@ class NotificationManager:
212
if not changed_nos:
213
return
214
215
- from python.helpers.state_monitor_integration import mark_dirty_all
215
+ from helpers.state_monitor_integration import mark_dirty_all
216
mark_dirty_all(reason="notification.NotificationManager.mark_all_read")
217
218
def clear_all(self):
@@ -220,7 +220,7 @@ class NotificationManager:
220
self.notifications = []
221
self.updates = []
222
self.guid = str(uuid.uuid4())
223
- from python.helpers.state_monitor_integration import mark_dirty_all
223
+ from helpers.state_monitor_integration import mark_dirty_all
224
mark_dirty_all(reason="notification.NotificationManager.clear_all")
225
226
def get_notifications_by_type(self, type: NotificationType) -> list[NotificationItem]:
helpers/performance.py
new
+51
@@ -0,0 +1,51 @@
1
+import functools
2
+import inspect
3
+from pyinstrument import Profiler
4
+
5
+def trace_performance(*, show_all=False, color=True, unicode=True):
6
+ """
7
+ Decorator that profiles a function and prints a call tree when it finishes.
8
+
9
+ Works with both synchronous and asynchronous functions.
10
+ """
11
+
12
+ def decorator(func):
13
+ is_coro = inspect.iscoroutinefunction(func)
14
+
15
+ @functools.wraps(func)
16
+ async def async_wrapper(*args, **kwargs):
17
+ profiler = Profiler()
18
+ profiler.start()
19
+ try:
20
+ return await func(*args, **kwargs)
21
+ finally:
22
+ profiler.stop()
23
+ print(f"\n=== Performance trace: {func.__module__}.{func.__qualname__} (async) ===")
24
+ print(
25
+ profiler.output_text(
26
+ color=color,
27
+ unicode=unicode,
28
+ show_all=show_all,
29
+ )
30
+ )
31
+
32
+ @functools.wraps(func)
33
+ def sync_wrapper(*args, **kwargs):
34
+ profiler = Profiler()
35
+ profiler.start()
36
+ try:
37
+ return func(*args, **kwargs)
38
+ finally:
39
+ profiler.stop()
40
+ print(f"\n=== Performance trace: {func.__module__}.{func.__qualname__} ===")
41
+ print(
42
+ profiler.output_text(
43
+ color=color,
44
+ unicode=unicode,
45
+ show_all=show_all,
46
+ )
47
+ )
48
+
49
+ return async_wrapper if is_coro else sync_wrapper
50
+
51
+ return decorator
\ No newline at end of file
helpers/perplexity_search.py
renamed
helpers/persist_chat.py
renamed
+2
-2
@@ -3,11 +3,11 @@ from datetime import datetime
3
from typing import Any
4
import uuid
5
from agent import Agent, AgentConfig, AgentContext, AgentContextType
6
-from python.helpers import files, history
6
+from helpers import files, history
7
import json
8
from initialize import initialize_agent
9
10
-from python.helpers.log import Log, LogItem
10
+from helpers.log import Log, LogItem
11
12
CHATS_FOLDER = "usr/chats"
13
LOG_SIZE = 1000
helpers/playwright.py
renamed
+1
-1
@@ -3,7 +3,7 @@ import os
3
import sys
4
from pathlib import Path
5
import subprocess
6
-from python.helpers import files
6
+from helpers import files
7
8
9
# this helper ensures that playwright is installed in /lib/playwright
helpers/plugins.py
renamed
+5
-18
@@ -13,7 +13,7 @@ from typing import (
13
TypedDict,
14
)
15
16
-from python.helpers import files, print_style, yaml as yaml_helper, cache
16
+from helpers import files, print_style, yaml as yaml_helper, cache
17
from pydantic import BaseModel, Field
18
19
if TYPE_CHECKING:
@@ -235,7 +235,7 @@ def get_enabled_plugins(agent: Agent | None):
235
236
# + agent paths
237
if agent:
238
- from python.helpers import subagents
238
+ from helpers import subagents
239
240
agent_paths = subagents.get_paths(
241
agent,
@@ -349,19 +349,6 @@ def toggle_plugin(
349
files.write_file(disabled_file, "")
350
351
352
-def get_webui_extensions(agent: Agent | None, extension_point: str, filters: List[str] | None = None):
353
- entries: List[str] = []
354
- effective_filters = filters or ["*"]
355
-
356
- for filter in effective_filters:
357
- extensions = get_enabled_plugin_paths(agent, "extensions", "webui", extension_point, filter)
358
- for extension in extensions:
359
- rel_path = files.deabsolute_path(extension)
360
- entries.append(rel_path)
361
-
362
- return entries
363
-
364
-
352
def get_plugin_config(
353
plugin_name: str,
354
agent: Agent | None = None,
@@ -370,7 +357,7 @@ def get_plugin_config(
357
):
358
359
if project_name is None and agent is not None:
373
- from python.helpers import projects
360
+ from helpers import projects
361
362
project_name = projects.get_context_project_name(agent.context)
363
if agent_profile is None and agent is not None:
@@ -436,7 +423,7 @@ def find_plugin_assets(
423
agent_profile: str = "*",
424
only_first: bool = False,
425
) -> list[PluginAssetFile]:
439
- from python.helpers import projects, subagents
426
+ from helpers import projects, subagents
427
428
results: list[PluginAssetFile] = []
429
@@ -550,7 +537,7 @@ def determine_plugin_asset_path(
537
base_path = files.get_abs_path(files.USER_DIR)
538
539
if project_name:
553
- from python.helpers import projects
540
+ from helpers import projects
541
542
base_path = projects.get_project_meta(project_name)
543
helpers/print_catch.py
renamed
helpers/print_style.py
renamed
+1
-1
@@ -138,7 +138,7 @@ class PrintStyle:
138
# Automatically mask secrets in all print output
139
try:
140
if not hasattr(self, "secrets_mgr"):
141
- from python.helpers.secrets import get_secrets_manager
141
+ from helpers.secrets import get_secrets_manager
142
self.secrets_mgr = get_secrets_manager()
143
text = self.secrets_mgr.mask_values(text)
144
except Exception:
helpers/process.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
import os
2
import sys
3
-from python.helpers import runtime
4
-from python.helpers.print_style import PrintStyle
3
+from helpers import runtime
4
+from helpers.print_style import PrintStyle
5
6
_server = None
7
helpers/projects.py
renamed
+11
-11
@@ -1,8 +1,8 @@
1
import os
2
from typing import Literal, TypedDict, TYPE_CHECKING, cast
3
4
-from python.helpers import files, dirty_json, persist_chat, file_tree
5
-from python.helpers.print_style import PrintStyle
4
+from helpers import files, dirty_json, persist_chat, file_tree
5
+from helpers.print_style import PrintStyle
6
7
8
if TYPE_CHECKING:
@@ -87,7 +87,7 @@ def create_project(name: str, data: BasicProjectData):
87
88
def clone_git_project(name: str, git_url: str, git_token: str, data: BasicProjectData):
89
"""Clone a git repository as a new A0 project. Token is used only for cloning via http header."""
90
- from python.helpers import git
90
+ from helpers import git
91
92
abs_path = files.create_dir_safe(
93
files.get_abs_path(PROJECTS_PARENT_DIR, name), rename_format="{name}_{number}"
@@ -231,7 +231,7 @@ def load_basic_project_data(name: str) -> BasicProjectData:
231
232
233
def load_edit_project_data(name: str) -> EditProjectData:
234
- from python.helpers import git
234
+ from helpers import git
235
236
data = load_basic_project_data(name)
237
additional_instructions = get_additional_instructions_files(name)
@@ -316,7 +316,7 @@ def activate_project(context_id: str, name: str, *, mark_dirty: bool = True):
316
persist_chat.save_tmp_chat(context)
317
318
if mark_dirty:
319
- from python.helpers.state_monitor_integration import mark_dirty_all
319
+ from helpers.state_monitor_integration import mark_dirty_all
320
mark_dirty_all(reason="projects.activate_project")
321
322
@@ -333,7 +333,7 @@ def deactivate_project(context_id: str, *, mark_dirty: bool = True):
333
persist_chat.save_tmp_chat(context)
334
335
if mark_dirty:
336
- from python.helpers.state_monitor_integration import mark_dirty_all
336
+ from helpers.state_monitor_integration import mark_dirty_all
337
mark_dirty_all(reason="projects.deactivate_project")
338
339
@@ -345,7 +345,7 @@ def reactivate_project_in_chats(name: str):
345
activate_project(context.id, name, mark_dirty=False)
346
persist_chat.save_tmp_chat(context)
347
348
- from python.helpers.state_monitor_integration import mark_dirty_all
348
+ from helpers.state_monitor_integration import mark_dirty_all
349
mark_dirty_all(reason="projects.reactivate_project_in_chats")
350
351
@@ -357,7 +357,7 @@ def deactivate_project_in_chats(name: str):
357
deactivate_project(context.id, mark_dirty=False)
358
persist_chat.save_tmp_chat(context)
359
360
- from python.helpers.state_monitor_integration import mark_dirty_all
360
+ from helpers.state_monitor_integration import mark_dirty_all
361
mark_dirty_all(reason="projects.deactivate_project_in_chats")
362
363
@@ -425,7 +425,7 @@ def save_project_subagents(name: str, subagents_data: dict[str, SubAgentSettings
425
def _normalize_subagents(
426
subagents_data: dict[str, SubAgentSettings]
427
) -> dict[str, SubAgentSettings]:
428
- from python.helpers import subagents
428
+ from helpers import subagents
429
430
agents_dict = subagents.get_agents_dict()
431
@@ -445,14 +445,14 @@ def _normalize_subagents(
445
446
447
def load_project_secrets_masked(name: str, merge_with_global=False):
448
- from python.helpers import secrets
448
+ from helpers import secrets
449
450
mgr = secrets.get_project_secrets_manager(name, merge_with_global)
451
return mgr.get_masked_secrets()
452
453
454
def save_project_secrets(name: str, secrets: str):
455
- from python.helpers.secrets import get_project_secrets_manager
455
+ from helpers.secrets import get_project_secrets_manager
456
457
secrets_manager = get_project_secrets_manager(name)
458
secrets_manager.save_secrets_with_merge(secrets)
helpers/providers.py
renamed
+1
-1
@@ -1,5 +1,5 @@
1
import yaml
2
-from python.helpers import files
2
+from helpers import files
3
from typing import List, Dict, Optional, TypedDict, Literal
4
5
ModelType = Literal["chat", "embedding"]
helpers/rate_limiter.py
renamed
helpers/rfc.py
renamed
+2
-2
@@ -3,9 +3,9 @@ import inspect
3
import json
4
from typing import Any, TypedDict
5
import aiohttp
6
-from python.helpers import crypto
6
+from helpers import crypto
7
8
-from python.helpers import dotenv
8
+from helpers import dotenv
9
10
11
# Remote Function Call library
helpers/rfc_exchange.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers import runtime, crypto, dotenv
1
+from helpers import runtime, crypto, dotenv
2
3
async def get_root_password():
4
if runtime.is_dockerized():
helpers/rfc_files.py
renamed
+1
-1
@@ -4,7 +4,7 @@ import fnmatch
4
import base64
5
import tempfile
6
import zipfile
7
-from python.helpers import runtime
7
+from helpers import runtime
8
9
10
def get_abs_path(*relative_paths):
helpers/runtime.py
renamed
+1
-1
@@ -3,7 +3,7 @@ import inspect
3
import secrets
4
from pathlib import Path
5
from typing import TypeVar, Callable, Awaitable, Union, overload, cast
6
-from python.helpers import dotenv, rfc, settings, files
6
+from helpers import dotenv, rfc, settings, files
7
import asyncio
8
import threading
9
import queue
helpers/searxng.py
renamed
+1
-1
@@ -1,5 +1,5 @@
1
import aiohttp
2
-from python.helpers import runtime
2
+from helpers import runtime
3
4
URL = "http://localhost:55510/search"
5
helpers/secrets.py
renamed
+4
-4
@@ -6,8 +6,8 @@ from io import StringIO
6
from dataclasses import dataclass
7
from typing import Dict, Optional, List, Literal, Set, Callable, Tuple, TYPE_CHECKING
8
from dotenv.parser import parse_stream
9
-from python.helpers.errors import RepairableException
10
-from python.helpers import files
9
+from helpers.errors import RepairableException
10
+from helpers import files
11
12
if TYPE_CHECKING:
13
from agent import AgentContext
@@ -505,7 +505,7 @@ class SecretsManager:
505
506
507
def get_secrets_manager(context: "AgentContext|None" = None) -> SecretsManager:
508
- from python.helpers import projects
508
+ from helpers import projects
509
510
# default secrets file
511
secret_files = [DEFAULT_SECRETS_FILE]
@@ -524,7 +524,7 @@ def get_secrets_manager(context: "AgentContext|None" = None) -> SecretsManager:
524
return SecretsManager.get_instance(*secret_files)
525
526
def get_project_secrets_manager(project_name: str, merge_with_global: bool = False) -> SecretsManager:
527
- from python.helpers import projects
527
+ from helpers import projects
528
529
# default secrets file
530
secret_files = []
helpers/security.py
renamed
helpers/settings.py
renamed
+10
-10
@@ -7,13 +7,13 @@ import subprocess
7
from typing import Any, Literal, TypedDict, cast, TypeVar
8
9
import models
10
-from python.helpers import runtime, whisper, defer, git, subagents
10
+from helpers import runtime, whisper, defer, git, subagents
11
from . import files, dotenv
12
-from python.helpers.print_style import PrintStyle
13
-from python.helpers.providers import get_providers, FieldOption as ProvidersFO
14
-from python.helpers.secrets import get_default_secrets_manager
15
-from python.helpers import dirty_json
16
-from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
12
+from helpers.print_style import PrintStyle
13
+from helpers.providers import get_providers, FieldOption as ProvidersFO
14
+from helpers.secrets import get_default_secrets_manager
15
+from helpers import dirty_json
16
+from helpers.notification import NotificationManager, NotificationType, NotificationPriority
17
18
19
T = TypeVar('T')
@@ -600,7 +600,7 @@ def _apply_settings(previous: Settings | None):
600
or _settings["embed_model_provider"] != previous["embed_model_provider"]
601
or _settings["embed_model_kwargs"] != previous["embed_model_kwargs"]
602
):
603
- from python.helpers.extension import call_extensions
603
+ from helpers.extension import call_extensions
604
605
defer.DeferredTask().start_task(
606
call_extensions, "embedding_model_changed"
@@ -608,7 +608,7 @@ def _apply_settings(previous: Settings | None):
608
609
# update mcp settings if necessary
610
if not previous or _settings["mcp_servers"] != previous["mcp_servers"]:
611
- from python.helpers.mcp_handler import MCPConfig
611
+ from helpers.mcp_handler import MCPConfig
612
613
async def update_mcp_settings(mcp_servers: str):
614
PrintStyle(
@@ -670,7 +670,7 @@ def _apply_settings(previous: Settings | None):
670
if not previous or current_token != previous["mcp_server_token"]:
671
672
async def update_mcp_token(token: str):
673
- from python.helpers.mcp_server import DynamicMcpProxy
673
+ from helpers.mcp_server import DynamicMcpProxy
674
675
DynamicMcpProxy.get_instance().reconfigure(token=token)
676
@@ -682,7 +682,7 @@ def _apply_settings(previous: Settings | None):
682
if not previous or current_token != previous["mcp_server_token"]:
683
684
async def update_a2a_token(token: str):
685
- from python.helpers.fasta2a_server import DynamicA2AProxy
685
+ from helpers.fasta2a_server import DynamicA2AProxy
686
687
DynamicA2AProxy.get_instance().reconfigure(token=token)
688
helpers/shell_local.py
renamed
+2
-2
@@ -4,8 +4,8 @@ import subprocess
4
import time
5
import sys
6
from typing import Optional, Tuple
7
-from python.helpers import tty_session, runtime
8
-from python.helpers.shell_ssh import clean_string
7
+from helpers import tty_session, runtime
8
+from helpers.shell_ssh import clean_string
9
10
class LocalInteractiveSession:
11
def __init__(self, cwd: str|None = None):
helpers/shell_ssh.py
renamed
+3
-3
@@ -3,9 +3,9 @@ import paramiko
3
import time
4
import re
5
from typing import Tuple
6
-from python.helpers.log import Log
7
-from python.helpers.print_style import PrintStyle
8
-# from python.helpers.strings import calculate_valid_match_lengths
6
+from helpers.log import Log
7
+from helpers.print_style import PrintStyle
8
+# from helpers.strings import calculate_valid_match_lengths
9
10
11
class SSHInteractiveSession:
helpers/skills.py
renamed
+1
-1
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
6
from pathlib import Path
7
from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple, TYPE_CHECKING
8
9
-from python.helpers import files, subagents, projects, file_tree, runtime
9
+from helpers import files, subagents, projects, file_tree, runtime
10
11
if TYPE_CHECKING:
12
from agent import Agent
helpers/skills_cli.py
renamed
+7
-7
@@ -3,11 +3,11 @@
3
Skills CLI - Easy skill management for Agent Zero
4
5
Usage:
6
- python -m python.helpers.skills_cli list List all skills
7
- python -m python.helpers.skills_cli create <name> Create a new skill
8
- python -m python.helpers.skills_cli show <name> Show skill details
9
- python -m python.helpers.skills_cli validate <name> Validate a skill
10
- python -m python.helpers.skills_cli search <query> Search skills
6
+ python -m helpers.skills_cli list List all skills
7
+ python -m helpers.skills_cli create <name> Create a new skill
8
+ python -m helpers.skills_cli show <name> Show skill details
9
+ python -m helpers.skills_cli validate <name> Validate a skill
10
+ python -m helpers.skills_cli search <query> Search skills
11
"""
12
13
import argparse
@@ -23,7 +23,7 @@ from datetime import datetime
23
# Add parent directory to path for imports
24
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
25
26
-from python.helpers import files
26
+from helpers import files
27
28
29
@dataclass
@@ -306,7 +306,7 @@ Examples:
306
print(f"\nNext steps:")
307
print(f" 1. Edit {skill_dir / 'SKILL.md'} to add your instructions")
308
print(f" 2. Add any helper scripts to {skill_dir / 'scripts'}/")
309
- print(f" 3. Run: python -m python.helpers.skills_cli validate {args.name}")
309
+ print(f" 3. Run: python -m helpers.skills_cli validate {args.name}")
310
except ValueError as e:
311
print(f"\n❌ Error: {e}")
312
sys.exit(1)
helpers/skills_import.py
renamed
+4
-4
@@ -9,8 +9,8 @@ from dataclasses import dataclass
9
from pathlib import Path
10
from typing import Iterable, List, Literal, Optional, Tuple
11
12
-from python.helpers import files
13
-from python.helpers.skills import discover_skill_md_files
12
+from helpers import files
13
+from helpers.skills import discover_skill_md_files
14
15
16
ConflictPolicy = Literal["skip", "overwrite", "rename"]
@@ -172,7 +172,7 @@ def _resolve_conflict(dest: Path, policy: ConflictPolicy) -> Tuple[Path, bool]:
172
173
def get_project_skills_folder(project_name: str) -> Path:
174
"""Get the skills folder path for a project."""
175
- from python.helpers.projects import get_project_meta
175
+ from helpers.projects import get_project_meta
176
return Path(get_project_meta(project_name, PROJECT_SKILLS_DIR))
177
178
@@ -181,7 +181,7 @@ def get_agent_profile_skills_folder(profile_name: str) -> Path:
181
182
183
def get_project_agent_profile_skills_folder(project_name: str, profile_name: str) -> Path:
184
- from python.helpers.projects import get_project_meta
184
+ from helpers.projects import get_project_meta
185
return Path(get_project_meta(project_name, "agents", profile_name, "skills"))
186
187
helpers/state_monitor.py
renamed
+5
-5
@@ -7,17 +7,17 @@ import time
7
from dataclasses import dataclass, field
8
from typing import Any, TYPE_CHECKING
9
10
-from python.helpers import runtime
11
-from python.helpers.print_style import PrintStyle
12
-from python.helpers.state_snapshot import (
10
+from helpers import runtime
11
+from helpers.print_style import PrintStyle
12
+from helpers.state_snapshot import (
13
StateRequestV1,
14
advance_state_request_after_snapshot,
15
build_snapshot_from_request,
16
)
17
-from python.helpers.websocket import ConnectionNotFoundError
17
+from helpers.websocket import ConnectionNotFoundError
18
19
if TYPE_CHECKING: # pragma: no cover - hints only
20
- from python.helpers.websocket_manager import WebSocketManager
20
+ from helpers.websocket_manager import WebSocketManager
21
22
23
ConnectionIdentity = tuple[str, str] # (namespace, sid)
helpers/state_monitor_integration.py
renamed
+2
-2
@@ -2,12 +2,12 @@ from __future__ import annotations
2
3
4
def mark_dirty_all(*, reason: str | None = None) -> None:
5
- from python.helpers.state_monitor import get_state_monitor
5
+ from helpers.state_monitor import get_state_monitor
6
7
get_state_monitor().mark_dirty_all(reason=reason)
8
9
10
def mark_dirty_for_context(context_id: str, *, reason: str | None = None) -> None:
11
- from python.helpers.state_monitor import get_state_monitor
11
+ from helpers.state_monitor import get_state_monitor
12
13
get_state_monitor().mark_dirty_for_context(context_id, reason=reason)
helpers/state_snapshot.py
renamed
+3
-3
@@ -9,9 +9,9 @@ import pytz # type: ignore[import-untyped]
9
10
from agent import AgentContext, AgentContextType
11
12
-from python.helpers.dotenv import get_dotenv_value
13
-from python.helpers.localization import Localization
14
-from python.helpers.task_scheduler import TaskScheduler
12
+from helpers.dotenv import get_dotenv_value
13
+from helpers.localization import Localization
14
+from helpers.task_scheduler import TaskScheduler
15
16
17
class SnapshotV1(TypedDict):
helpers/strings.py
renamed
+1
-1
@@ -1,7 +1,7 @@
1
import re
2
import sys
3
import time
4
-from python.helpers import files
4
+from helpers import files
5
6
def sanitize_string(s: str, encoding: str = "utf-8") -> str:
7
# Replace surrogates and invalid unicode with replacement character
helpers/subagents.py
renamed
+11
-11
@@ -1,5 +1,5 @@
1
-from python.helpers import files
2
-from python.helpers import yaml as yaml_helper
1
+from helpers import files
2
+from helpers import yaml as yaml_helper
3
from typing import TypedDict, TYPE_CHECKING
4
from pydantic import BaseModel, model_validator
5
import json
@@ -58,7 +58,7 @@ def get_agents_dict(
58
)
59
return merged
60
61
- from python.helpers import plugins
61
+ from helpers import plugins
62
63
# load default, plugin, and custom agents and merge
64
default_agents = _get_agents_list_from_dir(DEFAULT_AGENTS_DIR, origin="default")
@@ -74,7 +74,7 @@ def get_agents_dict(
74
75
# merge with project agents if possible
76
if project_name:
77
- from python.helpers import projects
77
+ from helpers import projects
78
79
project_agents_dir = projects.get_project_meta(project_name, "agents")
80
project_agents = _get_agents_list_from_dir(project_agents_dir, origin="project")
@@ -117,7 +117,7 @@ def load_agent_data(name: str, project_name: str | None = None) -> SubAgent:
117
return original
118
return override
119
120
- from python.helpers import plugins
120
+ from helpers import plugins
121
122
# load default, plugin, and user agents and merge
123
default_agent = _load_agent_data_from_dir(
@@ -136,7 +136,7 @@ def load_agent_data(name: str, project_name: str | None = None) -> SubAgent:
136
137
# merge with project agent if possible
138
if project_name:
139
- from python.helpers import projects
139
+ from helpers import projects
140
141
project_agents_dir = projects.get_project_meta(project_name, "agents")
142
project_agent = _load_agent_data_from_dir(
@@ -252,7 +252,7 @@ def _merge_agent_list_items(
252
253
254
def get_agents_roots() -> list[str]:
255
- from python.helpers import plugins
255
+ from helpers import plugins
256
257
plugin_agents = plugins.get_enabled_plugin_paths(None, "agents")
258
project_agents = files.find_existing_paths_by_pattern("usr/projects/*/.a0proj/agents")
@@ -318,7 +318,7 @@ def get_available_agents_dict(
318
# all available agents
319
all_agents = get_agents_dict()
320
# filter by project settings
321
- from python.helpers import projects
321
+ from helpers import projects
322
323
project_settings = (
324
projects.load_project_subagents(project_name) if project_name else {}
@@ -351,7 +351,7 @@ def get_paths(
351
project_name = ""
352
353
if include_project and agent:
354
- from python.helpers import projects
354
+ from helpers import projects
355
356
project_name = projects.get_context_project_name(agent.context) or ""
357
@@ -378,7 +378,7 @@ def get_paths(
378
379
# plugin agents/<profile>/...
380
if include_plugins:
381
- from python.helpers import plugins
381
+ from helpers import plugins
382
for plugin_dir in plugins.get_enabled_plugin_paths(agent, "agents", profile_name):
383
path = files.get_abs_path(plugin_dir, *subpaths)
384
if (not must_exist_completely) or files.exists(files.get_abs_path(plugin_dir, *check_subpaths)):
@@ -397,7 +397,7 @@ def get_paths(
397
398
if include_plugins:
399
# plugins/*/subpaths...
400
- from python.helpers import plugins
400
+ from helpers import plugins
401
402
for plugin_dir in plugins.get_enabled_plugin_paths(agent):
403
path = files.get_abs_path(plugin_dir, *subpaths)
helpers/task_scheduler.py
renamed
+10
-10
@@ -17,12 +17,12 @@ from pydantic import BaseModel, Field, PrivateAttr
17
18
from agent import Agent, AgentContext, UserMessage
19
from initialize import initialize_agent
20
-from python.helpers.persist_chat import save_tmp_chat
21
-from python.helpers.print_style import PrintStyle
22
-from python.helpers.defer import DeferredTask
23
-from python.helpers.files import get_abs_path, make_dirs, read_file, write_file
24
-from python.helpers.localization import Localization
25
-from python.helpers import projects, guids
20
+from helpers.persist_chat import save_tmp_chat
21
+from helpers.print_style import PrintStyle
22
+from helpers.defer import DeferredTask
23
+from helpers.files import get_abs_path, make_dirs, read_file, write_file
24
+from helpers.localization import Localization
25
+from helpers import projects, guids
26
import pytz
27
from typing import Annotated
28
@@ -677,19 +677,19 @@ class TaskScheduler:
677
async def add_task(self, task: Union[ScheduledTask, AdHocTask, PlannedTask]) -> "TaskScheduler":
678
await self._tasks.add_task(task)
679
ctx = await self._get_chat_context(task) # invoke context creation
680
- from python.helpers.state_monitor_integration import mark_dirty_all
680
+ from helpers.state_monitor_integration import mark_dirty_all
681
mark_dirty_all(reason="task_scheduler.TaskScheduler.add_task")
682
return self
683
684
async def remove_task_by_uuid(self, task_uuid: str) -> "TaskScheduler":
685
await self._tasks.remove_task_by_uuid(task_uuid)
686
- from python.helpers.state_monitor_integration import mark_dirty_all
686
+ from helpers.state_monitor_integration import mark_dirty_all
687
mark_dirty_all(reason="task_scheduler.TaskScheduler.remove_task_by_uuid")
688
return self
689
690
async def remove_task_by_name(self, name: str) -> "TaskScheduler":
691
await self._tasks.remove_task_by_name(name)
692
- from python.helpers.state_monitor_integration import mark_dirty_all
692
+ from helpers.state_monitor_integration import mark_dirty_all
693
mark_dirty_all(reason="task_scheduler.TaskScheduler.remove_task_by_name")
694
return self
695
@@ -762,7 +762,7 @@ class TaskScheduler:
762
763
updated = await self._tasks.update_task_by_uuid(task_uuid, _update_task, verify_func)
764
if updated is not None:
765
- from python.helpers.state_monitor_integration import mark_dirty_all
765
+ from helpers.state_monitor_integration import mark_dirty_all
766
mark_dirty_all(reason="task_scheduler.TaskScheduler.update_task_checked")
767
return updated
768
helpers/timed_input.py
renamed
helpers/tokens.py
renamed
helpers/tool.py
renamed
+2
-2
@@ -3,8 +3,8 @@ from dataclasses import dataclass
3
from typing import Any
4
5
from agent import Agent, LoopData
6
-from python.helpers.print_style import PrintStyle
7
-from python.helpers.strings import sanitize_string
6
+from helpers.print_style import PrintStyle
7
+from helpers.strings import sanitize_string
8
9
10
@dataclass
helpers/tty_session.py
renamed
helpers/tunnel_manager.py
renamed
+1
-1
@@ -7,7 +7,7 @@ from flaredantic import (
7
import threading
8
from collections import deque
9
10
-from python.helpers.print_style import PrintStyle
10
+from helpers.print_style import PrintStyle
11
12
# Singleton to manage the tunnel instance
13
class TunnelManager:
helpers/update_check.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers import git, runtime
1
+from helpers import git, runtime
2
import hashlib
3
4
helpers/vector_db.py
renamed
+2
-2
@@ -2,7 +2,7 @@ from typing import Any, List, Sequence
2
from langchain_community.vectorstores import FAISS
3
4
# faiss needs to be patched for python 3.12 on arm #TODO remove once not needed
5
-from python.helpers import faiss_monkey_patch
5
+from helpers import faiss_monkey_patch
6
import faiss
7
8
@@ -16,7 +16,7 @@ from langchain.embeddings import CacheBackedEmbeddings
16
from simpleeval import simple_eval
17
18
from agent import Agent
19
-from python.helpers import guids
19
+from helpers import guids
20
21
22
class MyFaiss(FAISS):
helpers/wait.py
renamed
+1
-1
@@ -1,7 +1,7 @@
1
import asyncio
2
from datetime import datetime, timezone
3
4
-from python.helpers.print_style import PrintStyle
4
+from helpers.print_style import PrintStyle
5
6
7
def format_remaining_time(total_seconds: float) -> str:
helpers/websocket.py
renamed
+2
-2
@@ -9,7 +9,7 @@ from typing import Any, Iterable, Optional, TYPE_CHECKING
9
import socketio
10
11
if TYPE_CHECKING: # pragma: no cover - hints only
12
- from python.helpers.websocket_manager import WebSocketManager
12
+ from helpers.websocket_manager import WebSocketManager
13
14
_EVENT_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$")
15
_RESERVED_EVENT_NAMES: set[str] = {
@@ -272,7 +272,7 @@ class WebSocketResult:
272
class WebSocketHandler(ABC):
273
"""Base class for WebSocket event handlers.
274
275
- The interface mirrors :class:`python.helpers.api.ApiHandler` with declarative
275
+ The interface mirrors :class:`helpers.api.ApiHandler` with declarative
276
security configuration and lifecycle hooks while enforcing event-naming
277
conventions.
278
"""
helpers/websocket_manager.py
renamed
+5
-5
@@ -11,11 +11,11 @@ from typing import Any, Callable, Deque, Dict, Iterable, List, Optional, Set
11
import socketio
12
import uuid
13
14
-from python.helpers.defer import DeferredTask
15
-from python.helpers.print_style import PrintStyle
16
-from python.helpers import runtime
17
-from python.helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
18
-from python.helpers.state_monitor import _ws_debug_enabled
14
+from helpers.defer import DeferredTask
15
+from helpers.print_style import PrintStyle
16
+from helpers import runtime
17
+from helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
18
+from helpers.state_monitor import _ws_debug_enabled
19
20
BUFFER_MAX_SIZE = 100
21
BUFFER_TTL = timedelta(hours=1)
helpers/websocket_namespace_discovery.py
renamed
+3
-3
@@ -7,9 +7,9 @@ from dataclasses import dataclass
7
from types import ModuleType
8
from typing import Iterable
9
10
-from python.helpers.files import get_abs_path
11
-from python.helpers.print_style import PrintStyle
12
-from python.helpers.websocket import WebSocketHandler
10
+from helpers.files import get_abs_path
11
+from helpers.print_style import PrintStyle
12
+from helpers.websocket import WebSocketHandler
13
14
15
@dataclass(frozen=True)
helpers/whisper.py
renamed
+3
-3
@@ -3,9 +3,9 @@ import warnings
3
import whisper
4
import tempfile
5
import asyncio
6
-from python.helpers import runtime, rfc, settings, files
7
-from python.helpers.print_style import PrintStyle
8
-from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
6
+from helpers import runtime, rfc, settings, files
7
+from helpers.print_style import PrintStyle
8
+from helpers.notification import NotificationManager, NotificationType, NotificationPriority
9
10
# Suppress FutureWarning from torch.load
11
warnings.filterwarnings("ignore", category=FutureWarning)
helpers/yaml.py
renamed
initialize.py
+8
-8
@@ -1,7 +1,7 @@
1
from agent import AgentConfig
2
import models
3
-from python.helpers import runtime, settings, defer, extension
4
-from python.helpers.print_style import PrintStyle
3
+from helpers import runtime, settings, defer, extension
4
+from helpers.print_style import PrintStyle
5
6
7
@extension.extensible
@@ -98,9 +98,9 @@ def initialize_agent(override_settings: dict | None = None):
98
# defer.DeferredTask(thread_name="mcp-initializer").start_task(initialize_mcp_async, config.mcp_servers)
99
# initialize_mcp(config.mcp_servers)
100
101
- # import python.helpers.mcp_handler as mcp_helper
101
+ # import helpers.mcp_handler as mcp_helper
102
# import agent as agent_helper
103
- # import python.helpers.print_style as print_style_helper
103
+ # import helpers.print_style as print_style_helper
104
# if not mcp_helper.MCPConfig.get_instance().is_initialized():
105
# try:
106
# mcp_helper.MCPConfig.update(config.mcp_servers)
@@ -121,7 +121,7 @@ def initialize_agent(override_settings: dict | None = None):
121
122
@extension.extensible
123
def initialize_chats():
124
- from python.helpers import persist_chat
124
+ from helpers import persist_chat
125
async def initialize_chats_async():
126
persist_chat.load_tmp_chats()
127
return defer.DeferredTask().start_task(initialize_chats_async)
@@ -130,13 +130,13 @@ def initialize_chats():
130
def initialize_mcp():
131
set = settings.get_settings()
132
async def initialize_mcp_async():
133
- from python.helpers.mcp_handler import initialize_mcp as _initialize_mcp
133
+ from helpers.mcp_handler import initialize_mcp as _initialize_mcp
134
return _initialize_mcp(set["mcp_servers"])
135
return defer.DeferredTask().start_task(initialize_mcp_async)
136
137
@extension.extensible
138
def initialize_job_loop():
139
- from python.helpers.job_loop import run_loop
139
+ from helpers.job_loop import run_loop
140
return defer.DeferredTask("JobLoop").start_task(run_loop)
141
142
@extension.extensible
@@ -146,7 +146,7 @@ def initialize_preload():
146
147
@extension.extensible
148
def initialize_migration():
149
- from python.helpers import migration, dotenv
149
+ from helpers import migration, dotenv
150
# run migration
151
migration.startup_migration()
152
# reload .env as it might have been moved
models.py
+7
-7
@@ -19,13 +19,13 @@ import litellm
19
import openai
20
from litellm.types.utils import ModelResponse
21
22
-from python.helpers import dotenv
23
-from python.helpers import settings, dirty_json
24
-from python.helpers.dotenv import load_dotenv
25
-from python.helpers.providers import ModelType as ProviderModelType, get_provider_config
26
-from python.helpers.rate_limiter import RateLimiter
27
-from python.helpers.tokens import approximate_tokens
28
-from python.helpers import dirty_json, browser_use_monkeypatch
22
+from helpers import dotenv
23
+from helpers import settings, dirty_json
24
+from helpers.dotenv import load_dotenv
25
+from helpers.providers import ModelType as ProviderModelType, get_provider_config
26
+from helpers.rate_limiter import RateLimiter
27
+from helpers.tokens import approximate_tokens
28
+from helpers import dirty_json, browser_use_monkeypatch
29
30
from langchain_core.language_models.chat_models import SimpleChatModel
31
from langchain_core.outputs.chat_generation import ChatGenerationChunk
plugins/chat_branching/api/branch_chat.py
+3
-3
@@ -1,7 +1,7 @@
1
from datetime import datetime
2
3
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
4
-from python.helpers.persist_chat import (
3
+from helpers.api import ApiHandler, Input, Output, Request, Response
4
+from helpers.persist_chat import (
5
_serialize_context,
6
_deserialize_context,
7
save_tmp_chat,
@@ -65,7 +65,7 @@ class BranchChat(ApiHandler):
65
save_tmp_chat(new_context)
66
67
# Notify all tabs
68
- from python.helpers.state_monitor_integration import mark_dirty_all
68
+ from helpers.state_monitor_integration import mark_dirty_all
69
mark_dirty_all(reason="plugins.chat_branching.BranchChat")
70
71
return {
plugins/error_retry/extensions/python/agent_Agent_handle_exception_end/_80_retry_critical_exception.py
+5
-5
@@ -1,11 +1,11 @@
1
import asyncio
2
from datetime import datetime, timezone
3
-from python.helpers.extension import Extension
3
+from helpers.extension import Extension
4
from agent import LoopData
5
-from python.helpers.localization import Localization
6
-from python.helpers.errors import RepairableException, HandledException
7
-from python.helpers import errors
8
-from python.helpers.print_style import PrintStyle
5
+from helpers.localization import Localization
6
+from helpers.errors import RepairableException, HandledException
7
+from helpers import errors
8
+from helpers.print_style import PrintStyle
9
10
from plugins.error_retry.extensions.python.agent_Agent_monologue_start._10_reset_critical_exception_counter import DATA_NAME_COUNTER
11
plugins/error_retry/extensions/python/agent_Agent_monologue_start/_10_reset_critical_exception_counter.py
+5
-5
@@ -1,10 +1,10 @@
1
from datetime import datetime, timezone
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers.localization import Localization
5
-from python.helpers.errors import RepairableException
6
-from python.helpers import errors
7
-from python.helpers.print_style import PrintStyle
4
+from helpers.localization import Localization
5
+from helpers.errors import RepairableException
6
+from helpers import errors
7
+from helpers.print_style import PrintStyle
8
9
DATA_NAME_COUNTER = "_plugin.error_retry.critical_exception_counter"
10
plugins/memory/api/import_knowledge.py
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import files
3
-from python.helpers.security import safe_filename
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import files
3
+from helpers.security import safe_filename
4
from plugins.memory.helpers.memory import Memory, get_custom_knowledge_subdir_abs
5
import os
6
plugins/memory/api/knowledge_path_get.py
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import files, projects
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import files, projects
3
from plugins.memory.helpers.memory import get_custom_knowledge_subdir_abs
4
5
plugins/memory/api/knowledge_reindex.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.api import ApiHandler, Request, Response
1
+from helpers.api import ApiHandler, Request, Response
2
from plugins.memory.helpers.memory import Memory
3
4
plugins/memory/api/memory_dashboard.py
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.api import ApiHandler, Request, Response
2
-from python.helpers import files
1
+from helpers.api import ApiHandler, Request, Response
2
+from helpers import files
3
from models import ModelConfig, ModelType
4
from langchain_core.documents import Document
5
from agent import AgentContext
plugins/memory/extensions/python/embedding_model_changed/_10_memory_reload.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
3
# Direct import - this extension lives inside the memory plugin
4
from plugins.memory.helpers.memory import reload as memory_reload
plugins/memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
+2
-2
@@ -1,7 +1,7 @@
1
import asyncio
2
-from python.helpers.extension import Extension
2
+from helpers.extension import Extension
3
from agent import LoopData
4
-from python.helpers import dirty_json, errors, log, plugins
4
+from helpers import dirty_json, errors, log, plugins
5
6
# Direct import - this extension lives inside the memory plugin
7
from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/message_loop_prompts_after/_91_recall_wait.py
+2
-2
@@ -1,7 +1,7 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
from plugins.memory.extensions.python.message_loop_prompts_after._50_recall_memories import DATA_NAME_TASK as DATA_NAME_TASK_MEMORIES, DATA_NAME_ITER as DATA_NAME_ITER_MEMORIES
4
-from python.helpers import plugins
4
+from helpers import plugins
5
6
class RecallWait(Extension):
7
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
plugins/memory/extensions/python/monologue_end/_50_memorize_fragments.py
+5
-5
@@ -1,10 +1,10 @@
1
import asyncio
2
-from python.helpers import errors, plugins
3
-from python.helpers.extension import Extension
4
-from python.helpers.dirty_json import DirtyJson
2
+from helpers import errors, plugins
3
+from helpers.extension import Extension
4
+from helpers.dirty_json import DirtyJson
5
from agent import LoopData
6
-from python.helpers.log import LogItem
7
-from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
6
+from helpers.log import LogItem
7
+from helpers.defer import DeferredTask, THREAD_BACKGROUND
8
9
# Direct import - this extension lives inside the memory plugin
10
from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/monologue_end/_51_memorize_solutions.py
+5
-5
@@ -1,10 +1,10 @@
1
import asyncio
2
-from python.helpers import errors, plugins
3
-from python.helpers.extension import Extension
4
-from python.helpers.dirty_json import DirtyJson
2
+from helpers import errors, plugins
3
+from helpers.extension import Extension
4
+from helpers.dirty_json import DirtyJson
5
from agent import LoopData
6
-from python.helpers.log import LogItem
7
-from python.helpers.defer import DeferredTask, THREAD_BACKGROUND
6
+from helpers.log import LogItem
7
+from helpers.defer import DeferredTask, THREAD_BACKGROUND
8
9
# Direct import - this extension lives inside the memory plugin
10
from plugins.memory.helpers.memory import Memory
plugins/memory/extensions/python/monologue_start/_10_memory_init.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import LoopData
3
4
# Direct import - this extension lives inside the memory plugin
plugins/memory/extensions/python/system_prompt/_20_behaviour_prompt.py
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.extension import Extension
1
+from helpers.extension import Extension
2
from agent import Agent, LoopData
3
-from python.helpers import files
3
+from helpers import files
4
5
# Direct import - this extension lives inside the memory plugin
6
from plugins.memory.helpers import memory
plugins/memory/helpers/knowledge_import.py
+2
-2
@@ -8,8 +8,8 @@ from langchain_community.document_loaders import (
8
TextLoader,
9
UnstructuredHTMLLoader,
10
)
11
-from python.helpers.log import LogItem
12
-from python.helpers.print_style import PrintStyle
11
+from helpers.log import LogItem
12
+from helpers.print_style import PrintStyle
13
14
text_loader_kwargs = {"autodetect_encoding": True}
15
plugins/memory/helpers/memory.py
+9
-9
@@ -2,13 +2,13 @@ from datetime import datetime
2
from typing import Any, List, Sequence
3
from langchain.storage import InMemoryByteStore, LocalFileStore
4
from langchain.embeddings import CacheBackedEmbeddings
5
-from python.helpers import guids
5
+from helpers import guids
6
7
# from langchain_chroma import Chroma
8
from langchain_community.vectorstores import FAISS
9
10
# faiss needs to be patched for python 3.12 on arm #TODO remove once not needed
11
-from python.helpers import faiss_monkey_patch
11
+from helpers import faiss_monkey_patch
12
import faiss
13
14
@@ -22,11 +22,11 @@ import os, json
22
23
import numpy as np
24
25
-from python.helpers.print_style import PrintStyle
26
-from python.helpers import files, plugins, projects
25
+from helpers.print_style import PrintStyle
26
+from helpers import files, plugins, projects
27
from langchain_core.documents import Document
28
from . import knowledge_import
29
-from python.helpers.log import Log, LogItem
29
+from helpers.log import Log, LogItem
30
from enum import Enum
31
from agent import Agent, AgentContext
32
import models
@@ -486,7 +486,7 @@ def reload():
486
def abs_db_dir(memory_subdir: str) -> str:
487
# patch for projects, this way we don't need to re-work the structure of memory subdirs
488
if memory_subdir.startswith("projects/"):
489
- from python.helpers.projects import get_project_meta
489
+ from helpers.projects import get_project_meta
490
491
return files.get_abs_path(get_project_meta(memory_subdir[9:]), "memory")
492
# standard subdirs
@@ -496,7 +496,7 @@ def abs_db_dir(memory_subdir: str) -> str:
496
def abs_knowledge_dir(knowledge_subdir: str, *sub_dirs: str) -> str:
497
# patch for projects, this way we don't need to re-work the structure of knowledge subdirs
498
if knowledge_subdir.startswith("projects/"):
499
- from python.helpers.projects import get_project_meta
499
+ from helpers.projects import get_project_meta
500
501
return files.get_abs_path(
502
get_project_meta(knowledge_subdir[9:]), "knowledge", *sub_dirs
@@ -537,7 +537,7 @@ def get_context_memory_subdir(context: AgentContext) -> str:
537
538
def get_existing_memory_subdirs() -> list[str]:
539
try:
540
- from python.helpers.projects import (
540
+ from helpers.projects import (
541
get_project_meta,
542
get_projects_parent_folder,
543
)
@@ -566,7 +566,7 @@ def get_knowledge_subdirs_by_memory_subdir(
566
memory_subdir: str, default: list[str]
567
) -> list[str]:
568
if memory_subdir.startswith("projects/"):
569
- from python.helpers.projects import get_project_meta
569
+ from helpers.projects import get_project_meta
570
571
default.append(get_project_meta(memory_subdir[9:], "knowledge"))
572
return default
plugins/memory/helpers/memory_consolidation.py
+3
-3
@@ -8,9 +8,9 @@ from enum import Enum
8
from langchain_core.documents import Document
9
10
from plugins.memory.helpers.memory import Memory
11
-from python.helpers.dirty_json import DirtyJson
12
-from python.helpers.log import LogItem
13
-from python.helpers.print_style import PrintStyle
11
+from helpers.dirty_json import DirtyJson
12
+from helpers.log import LogItem
13
+from helpers.print_style import PrintStyle
14
from agent import Agent
15
from plugins.memory.tools.memory_load import DEFAULT_THRESHOLD as DEFAULT_MEMORY_THRESHOLD
16
plugins/memory/tools/behaviour_adjustment.py
+3
-3
@@ -1,7 +1,7 @@
1
-from python.helpers import files
2
-from python.helpers.tool import Tool, Response
1
+from helpers import files
2
+from helpers.tool import Tool, Response
3
from agent import Agent
4
-from python.helpers.log import LogItem
4
+from helpers.log import LogItem
5
from plugins.memory.helpers import memory
6
7
plugins/memory/tools/memory_delete.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
from plugins.memory.helpers.memory import Memory
3
4
plugins/memory/tools/memory_forget.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
from plugins.memory.helpers.memory import Memory
3
4
from tools.memory_load import DEFAULT_THRESHOLD
plugins/memory/tools/memory_load.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
from plugins.memory.helpers.memory import Memory
3
4
DEFAULT_THRESHOLD = 0.7
plugins/memory/tools/memory_save.py
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
from plugins.memory.helpers.memory import Memory
3
4
plugins/plugin_installer/api/plugin_install.py
+3
-3
@@ -4,8 +4,8 @@ import time
4
import uuid
5
from pathlib import Path
6
7
-from python.helpers.api import ApiHandler, Input, Request, Output
8
-from python.helpers import files
7
+from helpers.api import ApiHandler, Input, Request, Output
8
+from helpers import files
9
from werkzeug.datastructures import FileStorage
10
from werkzeug.utils import secure_filename
11
@@ -63,7 +63,7 @@ class PluginInstall(ApiHandler):
63
64
def _fetch_index(self, input: dict) -> dict:
65
from plugins.plugin_installer.helpers.install import fetch_plugin_index
66
- from python.helpers.plugins import get_plugins_list
66
+ from helpers.plugins import get_plugins_list
67
index_data = fetch_plugin_index()
68
installed = get_plugins_list()
69
return {
plugins/plugin_installer/helpers/install.py
+4
-4
@@ -8,13 +8,13 @@ import zipfile
8
from pathlib import Path
9
from typing import Optional
10
11
-from python.helpers import files
12
-from python.helpers.plugins import (
11
+from helpers import files
12
+from helpers.plugins import (
13
META_FILE_NAME,
14
PluginMetadata,
15
invalidate_plugin_cache,
16
)
17
-from python.helpers import yaml as yaml_helper
17
+from helpers import yaml as yaml_helper
18
19
_SAFE_NAME_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_]*$")
20
@@ -127,7 +127,7 @@ def install_from_zip(zip_path: str) -> dict:
127
def install_from_git(url: str, token: Optional[str] = None) -> dict:
128
"""Clone git repo into usr/plugins/, validate plugin.yaml.
129
Returns dict with plugin name and metadata."""
130
- from python.helpers.git import clone_repo
130
+ from helpers.git import clone_repo
131
132
# Derive plugin name from URL
133
repo_name = url.rstrip("/").split("/")[-1]
plugins/plugin_scan/api/plugin_scan_queue.py
+2
-2
@@ -1,6 +1,6 @@
1
from agent import AgentContext
2
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
3
-from python.helpers import message_queue as mq
2
+from helpers.api import ApiHandler, Input, Output, Request, Response
3
+from helpers import message_queue as mq
4
5
6
class PluginScanQueue(ApiHandler):
plugins/plugin_scan/api/plugin_scan_start.py
+1
-1
@@ -1,5 +1,5 @@
1
from agent import AgentContext, UserMessage
2
-from python.helpers.api import ApiHandler, Input, Output, Request, Response
2
+from helpers.api import ApiHandler, Input, Output, Request, Response
3
4
5
class PluginScanStart(ApiHandler):
plugins/text_editor/extensions/python/system_prompt/_15_text_editor_prompt.py
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.extension import Extension
2
-from python.helpers import plugins
1
+from helpers.extension import Extension
2
+from helpers import plugins
3
from agent import Agent, LoopData
4
5
plugins/text_editor/helpers/file_ops.py
+1
-1
@@ -9,7 +9,7 @@ import shutil
9
import tempfile
10
from typing import TypedDict
11
12
-from python.helpers import tokens
12
+from helpers import tokens
13
14
_BINARY_PEEK = 8192
15
plugins/text_editor/tools/text_editor.py
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.tool import Tool, Response
2
-from python.helpers.extension import call_extensions
3
-from python.helpers import plugins, runtime
1
+from helpers.tool import Tool, Response
2
+from helpers.extension import call_extensions
3
+from helpers import plugins, runtime
4
from plugins.text_editor.helpers.file_ops import (
5
FileInfo,
6
read_file,
preload.py
+3
-3
@@ -1,7 +1,7 @@
1
import asyncio
2
-from python.helpers import runtime, whisper, settings
3
-from python.helpers.print_style import PrintStyle
4
-from python.helpers import kokoro_tts
2
+from helpers import runtime, whisper, settings
3
+from helpers.print_style import PrintStyle
4
+from helpers import kokoro_tts
5
import models
6
7
prepare.py
+2
-2
@@ -1,7 +1,7 @@
1
-from python.helpers import dotenv, runtime, settings
1
+from helpers import dotenv, runtime, settings
2
import string
3
import random
4
-from python.helpers.print_style import PrintStyle
4
+from helpers.print_style import PrintStyle
5
6
7
PrintStyle.standard("Preparing environment...")
prompts/agent.system.main.tips.py
+5
-5
@@ -1,8 +1,8 @@
1
-from python.helpers.files import VariablesPlugin
2
-from python.helpers import settings
3
-from python.helpers import projects
4
-from python.helpers import runtime
5
-from python.helpers import files
1
+from helpers.files import VariablesPlugin
2
+from helpers import settings
3
+from helpers import projects
4
+from helpers import runtime
5
+from helpers import files
6
from typing import Any
7
8
class WorkdirPath(VariablesPlugin):
prompts/agent.system.tool.call_sub.py
+3
-3
@@ -1,8 +1,8 @@
1
import json
2
from typing import Any, TYPE_CHECKING
3
-from python.helpers.files import VariablesPlugin
4
-from python.helpers import files, projects, subagents
5
-from python.helpers.print_style import PrintStyle
3
+from helpers.files import VariablesPlugin
4
+from helpers import files, projects, subagents
5
+from helpers.print_style import PrintStyle
6
7
if TYPE_CHECKING:
8
from agent import Agent
prompts/agent.system.tools.py
+3
-3
@@ -1,8 +1,8 @@
1
import os
2
from typing import Any
3
-from python.helpers.files import VariablesPlugin
4
-from python.helpers import files
5
-from python.helpers.print_style import PrintStyle
3
+from helpers.files import VariablesPlugin
4
+from helpers import files
5
+from helpers.print_style import PrintStyle
6
7
8
class BuidToolsPrompt(VariablesPlugin):
python/websocket_handlers/_default.py
+1
-1
@@ -2,7 +2,7 @@ from __future__ import annotations
2
3
from typing import Any
4
5
-from python.helpers.websocket import WebSocketHandler, WebSocketResult
5
+from helpers.websocket import WebSocketHandler, WebSocketResult
6
7
8
class RootDefaultHandler(WebSocketHandler):
python/websocket_handlers/dev_websocket_test_handler.py
+3
-3
@@ -3,9 +3,9 @@ from __future__ import annotations
3
import asyncio
4
from typing import Any, Dict
5
6
-from python.helpers.print_style import PrintStyle
7
-from python.helpers import runtime
8
-from python.helpers.websocket import WebSocketHandler, WebSocketResult
6
+from helpers.print_style import PrintStyle
7
+from helpers import runtime
8
+from helpers.websocket import WebSocketHandler, WebSocketResult
9
10
11
class DevWebsocketTestHandler(WebSocketHandler):
python/websocket_handlers/hello_handler.py
+2
-2
@@ -1,7 +1,7 @@
1
from __future__ import annotations
2
3
-from python.helpers.print_style import PrintStyle
4
-from python.helpers.websocket import WebSocketHandler
3
+from helpers.print_style import PrintStyle
4
+from helpers.websocket import WebSocketHandler
5
6
7
class HelloHandler(WebSocketHandler):
python/websocket_handlers/state_sync_handler.py
+5
-5
@@ -1,10 +1,10 @@
1
from __future__ import annotations
2
3
-from python.helpers import runtime
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers.websocket import WebSocketHandler, WebSocketResult
6
-from python.helpers.state_monitor import get_state_monitor, _ws_debug_enabled
7
-from python.helpers.state_snapshot import (
3
+from helpers import runtime
4
+from helpers.print_style import PrintStyle
5
+from helpers.websocket import WebSocketHandler, WebSocketResult
6
+from helpers.state_monitor import get_state_monitor, _ws_debug_enabled
7
+from helpers.state_snapshot import (
8
StateRequestValidationError,
9
parse_state_request_payload,
10
)
requirements.dev.txt
+2
-1
@@ -1,3 +1,4 @@
1
pytest>=8.4.2
2
pytest-asyncio>=1.2.0
3
-pytest-mock>=3.15.1
\ No newline at end of file
3
+pytest-mock>=3.15.1
4
+pyinstrument==5.1.2
\ No newline at end of file
run_tunnel.py
+3
-3
@@ -1,9 +1,9 @@
1
import threading
2
from flask import Flask, request
3
-from python.helpers import runtime, dotenv, process
4
-from python.helpers.print_style import PrintStyle
3
+from helpers import runtime, dotenv, process
4
+from helpers.print_style import PrintStyle
5
6
-from python.api.tunnel import Tunnel
6
+from api.tunnel import Tunnel
7
8
# initialize the internal Flask server
9
app = Flask("app")
run_ui.py
+20
-11
@@ -11,20 +11,21 @@ from flask import Flask, request, Response, session, redirect, url_for, render_t
11
from werkzeug.wrappers.request import Request as WerkzeugRequest
12
13
import initialize
14
-from python.helpers import files, git, mcp_server, fasta2a_server, settings as settings_helper, extension
15
-from python.helpers.files import get_abs_path
16
-from python.helpers import runtime, dotenv, process
17
-from python.helpers.websocket import WebSocketHandler, validate_ws_origin
18
-from python.helpers.api import register_api_route, requires_auth
19
-from python.helpers.print_style import PrintStyle
20
-from python.helpers import login
14
+from helpers import files, git, mcp_server, fasta2a_server, settings as settings_helper, extension
15
+from helpers.files import get_abs_path
16
+from helpers import runtime, dotenv, process
17
+from helpers.websocket import WebSocketHandler, validate_ws_origin
18
+from helpers.api import register_api_route, requires_auth
19
+from helpers.print_style import PrintStyle
20
+from helpers import login
21
import socketio # type: ignore[import-untyped]
22
from socketio import ASGIApp, packet
23
from starlette.applications import Starlette
24
from starlette.routing import Mount
25
from uvicorn.middleware.wsgi import WSGIMiddleware
26
-from python.helpers.websocket_manager import WebSocketManager
27
-from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
26
+from helpers.websocket_manager import WebSocketManager
27
+from helpers.websocket_namespace_discovery import discover_websocket_namespaces
28
+from flask import send_file
29
30
# disable logging
31
import logging
@@ -144,6 +145,14 @@ async def serve_builtin_plugin_asset(plugin_name, asset_path):
145
async def serve_plugin_asset(plugin_name, asset_path):
146
return await _serve_plugin_asset(plugin_name, asset_path)
147
148
+@webapp.route("/extensions/webui/<path:asset_path>", methods=["GET"])
149
+@requires_auth
150
+async def serve_extension_asset(asset_path):
151
+ path = files.get_abs_path("extensions/webui", asset_path)
152
+ if not files.is_in_dir(path, "extensions/webui"):
153
+ return Response("Access denied", 403)
154
+ return send_file(path)
155
+
156
157
@extension.extensible
158
async def _serve_plugin_asset(plugin_name, asset_path):
@@ -151,8 +160,8 @@ async def _serve_plugin_asset(plugin_name, asset_path):
160
Serve static assets from plugin directories.
161
Resolves using the plugin system (with overrides).
162
"""
154
- from python.helpers import plugins
155
- from flask import send_file
163
+ from helpers import plugins
164
+
165
166
# Use the new find_plugin helper
167
plugin_dir = plugins.find_plugin_dir(plugin_name)
skills/a0-create-plugin/SKILL.md
+2
-2
@@ -139,7 +139,7 @@ If your plugin exposes existing core settings rather than plugin-specific ones,
139
### Sending Messages Proactively
140
```python
141
from agent import AgentContext
142
-from python.helpers.messages import UserMessage
142
+from helpers.messages import UserMessage
143
144
context = AgentContext.use(context_id)
145
task = context.communicate(UserMessage("Message text"))
@@ -148,7 +148,7 @@ response = await task.result()
148
149
### Reading Plugin Settings (backend)
150
```python
151
-from python.helpers.plugins import get_plugin_config, save_plugin_config
151
+from helpers.plugins import get_plugin_config, save_plugin_config
152
153
# Runtime (with running agent - resolves project/profile from context)
154
settings = get_plugin_config("my-plugin", agent=agent) or {}
tests/email_parser_test.py
+2
-2
@@ -4,8 +4,8 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4
5
import asyncio
6
import pytest
7
-from python.helpers.email_client import read_messages
8
-from python.helpers.dotenv import get_dotenv_value, load_dotenv
7
+from helpers.email_client import read_messages
8
+from helpers.dotenv import get_dotenv_value, load_dotenv
9
10
11
@pytest.mark.skip(reason="This test is disabled as it has eternal dependencies and tests nothing automatically, please move it to a script or a manual test")
tests/test_fasta2a_client.py
+1
-1
@@ -9,7 +9,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
10
import asyncio
11
import pytest
12
-from python.helpers import settings
12
+from helpers import settings
13
14
15
def get_test_urls():
tests/test_file_tree_visualize.py
+2
-2
@@ -23,7 +23,7 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
23
if str(REPO_ROOT) not in sys.path:
24
sys.path.insert(0, str(REPO_ROOT))
25
26
-from python.helpers.file_tree import (
26
+from helpers.file_tree import (
27
OUTPUT_MODE_FLAT,
28
OUTPUT_MODE_NESTED,
29
OUTPUT_MODE_STRING,
@@ -34,7 +34,7 @@ from python.helpers.file_tree import (
34
SORT_DESC,
35
file_tree,
36
)
37
-from python.helpers.files import create_dir, delete_dir, get_abs_path, write_file
37
+from helpers.files import create_dir, delete_dir, get_abs_path, write_file
38
39
40
BASE_TEMP_ROOT = "tmp/tests/file_tree/visualize"
tests/test_http_auth_csrf.py
+6
-6
@@ -4,7 +4,7 @@ from flask import Flask, Response
4
5
import pytest
6
7
-from python.helpers import runtime
7
+from helpers import runtime
8
9
10
def _make_app() -> Flask:
@@ -32,7 +32,7 @@ def _set_csrf_cookie(client, token: str) -> None:
32
def test_http_auth_enforced_when_configured(monkeypatch) -> None:
33
from run_ui import csrf_protect, requires_auth
34
35
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
35
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
36
37
app = _make_app()
38
@@ -50,7 +50,7 @@ def test_http_auth_enforced_when_configured(monkeypatch) -> None:
50
def test_http_csrf_required_even_when_auth_not_configured(monkeypatch) -> None:
51
from run_ui import csrf_protect, requires_auth
52
53
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: None)
53
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: None)
54
55
app = _make_app()
56
@@ -69,7 +69,7 @@ def test_http_csrf_required_even_when_auth_not_configured(monkeypatch) -> None:
69
def test_http_csrf_rejects_missing_token(monkeypatch) -> None:
70
from run_ui import csrf_protect, requires_auth
71
72
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
72
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
73
74
app = _make_app()
75
@@ -88,7 +88,7 @@ def test_http_csrf_rejects_missing_token(monkeypatch) -> None:
88
def test_http_csrf_accepts_valid_header_without_cookie(monkeypatch) -> None:
89
from run_ui import csrf_protect, requires_auth
90
91
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
91
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
92
93
app = _make_app()
94
@@ -107,7 +107,7 @@ def test_http_csrf_accepts_valid_header_without_cookie(monkeypatch) -> None:
107
def test_http_csrf_accepts_valid_cookie(monkeypatch) -> None:
108
from run_ui import csrf_protect, requires_auth
109
110
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
110
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
111
112
app = _make_app()
113
tests/test_multi_tab_isolation.py
+5
-5
@@ -11,9 +11,9 @@ if str(PROJECT_ROOT) not in sys.path:
11
12
@pytest.mark.asyncio
13
async def test_state_monitor_per_sid_isolation_independent_snapshots_seq_and_cursors(monkeypatch):
14
- import python.helpers.state_monitor as state_monitor_module
15
- from python.helpers.state_monitor import StateMonitor
16
- from python.helpers.state_snapshot import StateRequestV1
14
+ import helpers.state_monitor as state_monitor_module
15
+ from helpers.state_monitor import StateMonitor
16
+ from helpers.state_snapshot import StateRequestV1
17
18
snapshot_calls: list[dict[str, object]] = []
19
emitted: list[dict[str, object]] = []
@@ -129,8 +129,8 @@ async def test_state_monitor_per_sid_isolation_independent_snapshots_seq_and_cur
129
130
@pytest.mark.asyncio
131
async def test_state_monitor_mark_dirty_for_context_scopes_to_active_context():
132
- from python.helpers.state_monitor import StateMonitor
133
- from python.helpers.state_snapshot import StateRequestV1
132
+ from helpers.state_monitor import StateMonitor
133
+ from helpers.state_snapshot import StateRequestV1
134
135
monitor = StateMonitor(debounce_seconds=60.0)
136
namespace = "/state_sync"
tests/test_persist_chat_log_ids.py
+2
-2
@@ -2,8 +2,8 @@ from __future__ import annotations
2
3
4
def test_deserialize_log_preserves_item_id() -> None:
5
- from python.helpers.log import Log
6
- from python.helpers.persist_chat import _deserialize_log, _serialize_log
5
+ from helpers.log import Log
6
+ from helpers.persist_chat import _deserialize_log, _serialize_log
7
8
log = Log()
9
log.log(type="user", heading="User message", content="hello", id="msg-123")
tests/test_snapshot_parity.py
+3
-3
@@ -11,7 +11,7 @@ if str(PROJECT_ROOT) not in sys.path:
11
12
from agent import AgentContext
13
from initialize import initialize_agent
14
-from python.api.poll import Poll
14
+from api.poll import Poll
15
16
17
@pytest.mark.asyncio
@@ -31,7 +31,7 @@ async def test_snapshot_builder_matches_poll_output_for_null_context():
31
None, # Poll.process does not access the flask Request object.
32
)
33
34
- from python.helpers import state_snapshot as snapshot
34
+ from helpers import state_snapshot as snapshot
35
36
builder_payload = await snapshot.build_snapshot(
37
context=None,
@@ -62,7 +62,7 @@ async def test_snapshot_builder_active_context_includes_incremental_logs():
62
assert first["logs"]
63
assert first["log_version"] == len(ctx.log.updates)
64
65
- from python.helpers import state_snapshot as snapshot
65
+ from helpers import state_snapshot as snapshot
66
67
second = await snapshot.build_snapshot(
68
context=ctxid,
tests/test_snapshot_schema_v1.py
+3
-3
@@ -9,7 +9,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
if str(PROJECT_ROOT) not in sys.path:
10
sys.path.insert(0, str(PROJECT_ROOT))
11
12
-from python.api.poll import Poll
12
+from api.poll import Poll
13
14
15
EXPECTED_SNAPSHOT_KEYS = {
@@ -59,7 +59,7 @@ async def test_poll_snapshot_matches_contract_schema_key_set_null_context():
59
60
@pytest.mark.asyncio
61
async def test_snapshot_builder_produces_contract_schema_key_set_and_defaults():
62
- from python.helpers import state_snapshot as snapshot
62
+ from helpers import state_snapshot as snapshot
63
64
payload = await snapshot.build_snapshot(
65
context=None,
@@ -87,7 +87,7 @@ async def test_snapshot_builder_produces_contract_schema_key_set_and_defaults():
87
88
89
def test_snapshot_schema_rejects_unexpected_top_level_keys():
90
- from python.helpers import state_snapshot as snapshot
90
+ from helpers import state_snapshot as snapshot
91
92
payload = {
93
"deselect_chat": False,
tests/test_state_monitor.py
+5
-5
@@ -10,8 +10,8 @@ if str(PROJECT_ROOT) not in sys.path:
10
11
@pytest.mark.asyncio
12
async def test_state_monitor_debounce_coalesces_without_postponing_and_cleanup_cancels_pending():
13
- from python.helpers.state_monitor import StateMonitor
14
- from python.helpers.state_snapshot import StateRequestV1
13
+ from helpers.state_monitor import StateMonitor
14
+ from helpers.state_snapshot import StateRequestV1
15
16
namespace = "/state_sync"
17
monitor = StateMonitor(debounce_seconds=10.0)
@@ -44,8 +44,8 @@ async def test_state_monitor_namespace_identity_prevents_cross_namespace_state_p
44
import asyncio
45
from unittest.mock import AsyncMock
46
47
- from python.helpers.state_monitor import StateMonitor
48
- from python.helpers.state_snapshot import StateRequestV1
47
+ from helpers.state_monitor import StateMonitor
48
+ from helpers.state_snapshot import StateRequestV1
49
50
loop = asyncio.get_running_loop()
51
push_ready = asyncio.Event()
@@ -94,7 +94,7 @@ async def test_state_monitor_namespace_identity_prevents_cross_namespace_state_p
94
}
95
96
# Patch build_snapshot used by StateMonitor so this test stays lightweight.
97
- monkeypatch.setattr("python.helpers.state_monitor.build_snapshot_from_request", _fake_snapshot)
97
+ monkeypatch.setattr("helpers.state_monitor.build_snapshot_from_request", _fake_snapshot)
98
99
monitor.mark_dirty(ns_a, sid, reason="test")
100
await asyncio.wait_for(push_ready.wait(), timeout=1.0)
tests/test_state_sync_handler.py
+5
-5
@@ -10,7 +10,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10
if str(PROJECT_ROOT) not in sys.path:
11
sys.path.insert(0, str(PROJECT_ROOT))
12
13
-from python.helpers.websocket_manager import WebSocketManager
13
+from helpers.websocket_manager import WebSocketManager
14
15
NAMESPACE = "/state_sync"
16
@@ -28,7 +28,7 @@ async def _create_manager() -> WebSocketManager:
28
manager = WebSocketManager(socketio, threading.RLock())
29
30
from python.websocket_handlers.state_sync_handler import StateSyncHandler
31
- from python.helpers.state_monitor import _reset_state_monitor_for_testing
31
+ from helpers.state_monitor import _reset_state_monitor_for_testing
32
33
_reset_state_monitor_for_testing()
34
StateSyncHandler._reset_instance_for_testing()
@@ -43,7 +43,7 @@ async def _create_manager_with_socketio() -> tuple[WebSocketManager, FakeSocketI
43
manager = WebSocketManager(socketio, threading.RLock())
44
45
from python.websocket_handlers.state_sync_handler import StateSyncHandler
46
- from python.helpers.state_monitor import _reset_state_monitor_for_testing
46
+ from helpers.state_monitor import _reset_state_monitor_for_testing
47
48
_reset_state_monitor_for_testing()
49
StateSyncHandler._reset_instance_for_testing()
@@ -115,8 +115,8 @@ async def test_state_request_invalid_payload_returns_invalid_request_error():
115
116
@pytest.mark.asyncio
117
async def test_state_push_gating_and_initial_snapshot_delivery():
118
- from python.helpers.state_monitor import get_state_monitor
119
- from python.helpers.state_snapshot import validate_snapshot_schema_v1
118
+ from helpers.state_monitor import get_state_monitor
119
+ from helpers.state_snapshot import validate_snapshot_schema_v1
120
121
manager, socketio = await _create_manager_with_socketio()
122
tests/test_state_sync_welcome_screen.py
+3
-3
@@ -10,7 +10,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10
if str(PROJECT_ROOT) not in sys.path:
11
sys.path.insert(0, str(PROJECT_ROOT))
12
13
-from python.helpers.websocket_manager import WebSocketManager
13
+from helpers.websocket_manager import WebSocketManager
14
15
NAMESPACE = "/state_sync"
16
@@ -30,8 +30,8 @@ async def test_state_sync_handshake_and_initial_snapshot_work_with_no_selected_c
30
is null. We must still handshake and receive an initial `state_push` quickly (no hang).
31
"""
32
33
- from python.helpers.state_snapshot import validate_snapshot_schema_v1
34
- from python.helpers.state_monitor import _reset_state_monitor_for_testing
33
+ from helpers.state_snapshot import validate_snapshot_schema_v1
34
+ from helpers.state_monitor import _reset_state_monitor_for_testing
35
from python.websocket_handlers.state_sync_handler import StateSyncHandler
36
37
socketio = FakeSocketIOServer()
tests/test_websocket_csrf.py
+1
-1
@@ -5,7 +5,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
5
if str(PROJECT_ROOT) not in sys.path:
6
sys.path.insert(0, str(PROJECT_ROOT))
7
8
-from python.helpers.websocket import validate_ws_origin
8
+from helpers.websocket import validate_ws_origin
9
10
11
def test_validate_ws_origin_allows_same_origin_with_explicit_port():
tests/test_websocket_handlers.py
+3
-3
@@ -8,7 +8,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
8
if str(PROJECT_ROOT) not in sys.path:
9
sys.path.insert(0, str(PROJECT_ROOT))
10
11
-from python.helpers.websocket import (
11
+from helpers.websocket import (
12
WebSocketHandler,
13
WebSocketResult,
14
SingletonInstantiationError,
@@ -140,9 +140,9 @@ def test_get_instance_returns_singleton():
140
141
@pytest.mark.asyncio
142
async def test_state_sync_handler_registers_and_routes_state_request():
143
- from python.helpers.websocket_manager import WebSocketManager
143
+ from helpers.websocket_manager import WebSocketManager
144
from python.websocket_handlers.state_sync_handler import StateSyncHandler
145
- from python.helpers.state_monitor import _reset_state_monitor_for_testing
145
+ from helpers.state_monitor import _reset_state_monitor_for_testing
146
147
_reset_state_monitor_for_testing()
148
StateSyncHandler._reset_instance_for_testing()
tests/test_websocket_harness.py
+1
-1
@@ -9,7 +9,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
if str(PROJECT_ROOT) not in sys.path:
10
sys.path.insert(0, str(PROJECT_ROOT))
11
12
-from python.helpers.websocket_manager import WebSocketManager
12
+from helpers.websocket_manager import WebSocketManager
13
from python.websocket_handlers.dev_websocket_test_handler import (
14
DevWebsocketTestHandler,
15
)
tests/test_websocket_manager.py
+9
-9
@@ -12,8 +12,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
12
if str(PROJECT_ROOT) not in sys.path:
13
sys.path.insert(0, str(PROJECT_ROOT))
14
15
-from python.helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
16
-from python.helpers.websocket_manager import (
15
+from helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
16
+from helpers.websocket_manager import (
17
WebSocketManager,
18
BUFFER_TTL,
19
DIAGNOSTIC_EVENT,
@@ -354,7 +354,7 @@ async def test_buffer_overflow_drops_oldest(monkeypatch):
354
await manager.handle_connect(NAMESPACE, "offline")
355
await manager.handle_disconnect(NAMESPACE, "offline")
356
357
- monkeypatch.setattr("python.helpers.websocket_manager.BUFFER_MAX_SIZE", 2)
357
+ monkeypatch.setattr("helpers.websocket_manager.BUFFER_MAX_SIZE", 2)
358
359
await manager.emit_to(NAMESPACE, "offline", "event", {"idx": 0})
360
await manager.emit_to(NAMESPACE, "offline", "event", {"idx": 1})
@@ -385,7 +385,7 @@ async def test_expired_buffer_entries_are_discarded(monkeypatch):
385
socketio.emit.reset_mock()
386
387
monkeypatch.setattr(
388
- "python.helpers.websocket_manager._utcnow",
388
+ "helpers.websocket_manager._utcnow",
389
lambda: future,
390
)
391
await manager.handle_connect(NAMESPACE, "sid-expired")
@@ -479,7 +479,7 @@ async def test_timestamps_are_timezone_aware():
479
assert info.connected_at.tzinfo is not None
480
assert info.last_activity.tzinfo is not None
481
482
- with patch("python.helpers.websocket_manager._utcnow") as mocked_now:
482
+ with patch("helpers.websocket_manager._utcnow") as mocked_now:
483
mocked_now.return_value = info.last_activity
484
await manager.route_event(NAMESPACE, "unknown", {}, "sid-utc")
485
assert info.last_activity.tzinfo is not None
@@ -512,7 +512,7 @@ def test_register_handlers_warns_on_duplicates(monkeypatch):
512
warnings.append(message)
513
514
monkeypatch.setattr(
515
- "python.helpers.print_style.PrintStyle.warning", staticmethod(capture_warning)
515
+ "helpers.print_style.PrintStyle.warning", staticmethod(capture_warning)
516
)
517
518
DuplicateHandler._reset_instance_for_testing()
@@ -804,13 +804,13 @@ def test_debug_logging_respects_runtime_flag(monkeypatch):
804
def capture(message: str) -> None:
805
logs.append(message)
806
807
- monkeypatch.setattr("python.helpers.print_style.PrintStyle.debug", staticmethod(capture))
808
- monkeypatch.setattr("python.helpers.websocket_manager.runtime.is_development", lambda: False)
807
+ monkeypatch.setattr("helpers.print_style.PrintStyle.debug", staticmethod(capture))
808
+ monkeypatch.setattr("helpers.websocket_manager.runtime.is_development", lambda: False)
809
810
manager._debug("should-not-log") # noqa: SLF001
811
assert logs == []
812
813
- monkeypatch.setattr("python.helpers.websocket_manager.runtime.is_development", lambda: True)
813
+ monkeypatch.setattr("helpers.websocket_manager.runtime.is_development", lambda: True)
814
manager._debug("should-log") # noqa: SLF001
815
assert logs == ["should-log"]
816
tests/test_websocket_namespace_discovery.py
+8
-8
@@ -55,7 +55,7 @@ def _write_handler_module(path: Path, class_name: str, event_type: str) -> None:
55
"",
56
"from typing import Any",
57
"",
58
- "from python.helpers.websocket import WebSocketHandler",
58
+ "from helpers.websocket import WebSocketHandler",
59
"",
60
f"class {class_name}(WebSocketHandler):",
61
" @classmethod",
@@ -80,7 +80,7 @@ def _write_handler_module(path: Path, class_name: str, event_type: str) -> None:
80
81
82
def test_discovery_supports_folder_entries_and_ignores_deeper_nesting(tmp_path: Path) -> None:
83
- from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
83
+ from helpers.websocket_namespace_discovery import discover_websocket_namespaces
84
85
folder = tmp_path / "orders"
86
folder.mkdir()
@@ -100,7 +100,7 @@ def test_discovery_supports_folder_entries_and_ignores_deeper_nesting(tmp_path:
100
101
102
def test_discovery_folder_suffix_handler_stripped(tmp_path: Path) -> None:
103
- from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
103
+ from helpers.websocket_namespace_discovery import discover_websocket_namespaces
104
105
folder = tmp_path / "sales_handler"
106
folder.mkdir()
@@ -115,8 +115,8 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
115
from flask import Flask
116
import socketio
117
118
- from python.helpers.websocket_manager import WebSocketManager
119
- from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
118
+ from helpers.websocket_manager import WebSocketManager
119
+ from helpers.websocket_namespace_discovery import discover_websocket_namespaces
120
from run_ui import configure_websocket_namespaces
121
122
empty = tmp_path / "empty"
@@ -128,7 +128,7 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
128
def _warn(message: str) -> None:
129
warnings.append(message)
130
131
- monkeypatch.setattr("python.helpers.print_style.PrintStyle.warning", staticmethod(_warn))
131
+ monkeypatch.setattr("helpers.print_style.PrintStyle.warning", staticmethod(_warn))
132
133
discoveries = discover_websocket_namespaces(handlers_folder=str(tmp_path), include_root_default=False)
134
assert "/empty" not in {d.namespace for d in discoveries}
@@ -181,7 +181,7 @@ def test_discovery_empty_folder_warns_and_treats_namespace_unregistered(tmp_path
181
182
183
def test_discovery_invalid_modules_fail_fast_with_descriptive_errors(tmp_path: Path) -> None:
184
- from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
184
+ from helpers.websocket_namespace_discovery import discover_websocket_namespaces
185
186
# 0 handlers in a *_handler.py module
187
(tmp_path / "bad_handler.py").write_text(
@@ -196,7 +196,7 @@ def test_discovery_invalid_modules_fail_fast_with_descriptive_errors(tmp_path: P
196
(tmp_path / "two_handler.py").write_text(
197
"\n".join(
198
[
199
- "from python.helpers.websocket import WebSocketHandler",
199
+ "from helpers.websocket import WebSocketHandler",
200
"class A(WebSocketHandler):",
201
" @classmethod",
202
" def requires_auth(cls): return False",
tests/test_websocket_namespace_security.py
+18
-18
@@ -61,9 +61,9 @@ async def test_connect_security_is_computed_per_namespace_and_enforced(monkeypat
61
from flask import Flask
62
import socketio
63
64
- from python.helpers.websocket import WebSocketHandler
65
- from python.helpers.websocket_manager import WebSocketManager
66
- from python.helpers import runtime
64
+ from helpers.websocket import WebSocketHandler
65
+ from helpers.websocket_manager import WebSocketManager
66
+ from helpers import runtime
67
from run_ui import configure_websocket_namespaces
68
69
class OpenHandler(WebSocketHandler):
@@ -93,7 +93,7 @@ async def test_connect_security_is_computed_per_namespace_and_enforced(monkeypat
93
OpenHandler._reset_instance_for_testing()
94
SecureHandler._reset_instance_for_testing()
95
96
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
96
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
97
98
webapp = Flask("test_websocket_namespace_security")
99
webapp.secret_key = "test-secret"
@@ -181,8 +181,8 @@ async def test_unknown_namespace_rejected_with_deterministic_connect_error_paylo
181
from flask import Flask
182
import socketio
183
184
- from python.helpers.websocket import WebSocketHandler
185
- from python.helpers.websocket_manager import WebSocketManager
184
+ from helpers.websocket import WebSocketHandler
185
+ from helpers.websocket_manager import WebSocketManager
186
from run_ui import configure_websocket_namespaces
187
188
class OpenHandler(WebSocketHandler):
@@ -248,9 +248,9 @@ async def test_secure_namespace_rejects_missing_auth_even_with_valid_csrf(monkey
248
from flask import Flask
249
import socketio
250
251
- from python.helpers.websocket import WebSocketHandler
252
- from python.helpers.websocket_manager import WebSocketManager
253
- from python.helpers import runtime
251
+ from helpers.websocket import WebSocketHandler
252
+ from helpers.websocket_manager import WebSocketManager
253
+ from helpers import runtime
254
from run_ui import configure_websocket_namespaces
255
256
class SecureHandler(WebSocketHandler):
@@ -263,7 +263,7 @@ async def test_secure_namespace_rejects_missing_auth_even_with_valid_csrf(monkey
263
264
SecureHandler._reset_instance_for_testing()
265
266
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
266
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
267
268
webapp = Flask("test_ws_secure_missing_auth")
269
webapp.secret_key = "test-secret"
@@ -314,9 +314,9 @@ async def test_secure_namespace_rejects_invalid_csrf_cookie(monkeypatch) -> None
314
from flask import Flask
315
import socketio
316
317
- from python.helpers.websocket import WebSocketHandler
318
- from python.helpers.websocket_manager import WebSocketManager
319
- from python.helpers import runtime
317
+ from helpers.websocket import WebSocketHandler
318
+ from helpers.websocket_manager import WebSocketManager
319
+ from helpers import runtime
320
from run_ui import configure_websocket_namespaces
321
322
class SecureHandler(WebSocketHandler):
@@ -329,7 +329,7 @@ async def test_secure_namespace_rejects_invalid_csrf_cookie(monkeypatch) -> None
329
330
SecureHandler._reset_instance_for_testing()
331
332
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: "hash")
332
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: "hash")
333
334
webapp = Flask("test_ws_secure_invalid_csrf")
335
webapp.secret_key = "test-secret"
@@ -381,9 +381,9 @@ async def test_csrf_required_without_auth_is_enforced(monkeypatch) -> None:
381
from flask import Flask
382
import socketio
383
384
- from python.helpers.websocket import WebSocketHandler
385
- from python.helpers.websocket_manager import WebSocketManager
386
- from python.helpers import runtime
384
+ from helpers.websocket import WebSocketHandler
385
+ from helpers.websocket_manager import WebSocketManager
386
+ from helpers import runtime
387
from run_ui import configure_websocket_namespaces
388
389
class CsrfOnlyHandler(WebSocketHandler):
@@ -404,7 +404,7 @@ async def test_csrf_required_without_auth_is_enforced(monkeypatch) -> None:
404
405
CsrfOnlyHandler._reset_instance_for_testing()
406
407
- monkeypatch.setattr("python.helpers.login.get_credentials_hash", lambda: None)
407
+ monkeypatch.setattr("helpers.login.get_credentials_hash", lambda: None)
408
409
webapp = Flask("test_ws_csrf_only")
410
webapp.secret_key = "test-secret"
tests/test_websocket_namespaces.py
+10
-10
@@ -13,8 +13,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
13
if str(PROJECT_ROOT) not in sys.path:
14
sys.path.insert(0, str(PROJECT_ROOT))
15
16
-from python.helpers.state_monitor import StateMonitor
17
-from python.helpers.websocket_manager import WebSocketManager
16
+from helpers.state_monitor import StateMonitor
17
+from helpers.websocket_manager import WebSocketManager
18
19
20
class FakeSocketIOServer:
@@ -108,8 +108,8 @@ async def test_namespace_isolation_state_sync_vs_dev_websocket_test() -> None:
108
from flask import Flask
109
import socketio
110
111
- from python.helpers.websocket import WebSocketHandler
112
- from python.helpers.websocket_manager import WebSocketManager
111
+ from helpers.websocket import WebSocketHandler
112
+ from helpers.websocket_manager import WebSocketManager
113
from run_ui import configure_websocket_namespaces
114
115
class StateHandler(WebSocketHandler):
@@ -220,8 +220,8 @@ async def test_diagnostics_include_source_namespace_and_deliver_on_dev_namespace
220
but must include `sourceNamespace` identifying the origin namespace.
221
"""
222
223
- from python.helpers.websocket import WebSocketHandler
224
- from python.helpers.websocket_manager import DIAGNOSTIC_EVENT, WebSocketManager
223
+ from helpers.websocket import WebSocketHandler
224
+ from helpers.websocket_manager import DIAGNOSTIC_EVENT, WebSocketManager
225
226
class DummyHandler(WebSocketHandler):
227
@classmethod
@@ -267,7 +267,7 @@ def test_namespace_discovery_maps_core_handlers_to_expected_namespaces() -> None
267
(no cross-registration).
268
"""
269
270
- from python.helpers.websocket_namespace_discovery import discover_websocket_namespaces
270
+ from helpers.websocket_namespace_discovery import discover_websocket_namespaces
271
272
discoveries = discover_websocket_namespaces(
273
handlers_folder="python/websocket_handlers",
@@ -309,7 +309,7 @@ async def test_route_event_dispatches_only_within_connected_namespace_and_result
309
CONTRACT.NS.ROUTING: inbound routing is restricted to handlers in the connected namespace.
310
"""
311
312
- from python.helpers.websocket import WebSocketHandler
312
+ from helpers.websocket import WebSocketHandler
313
314
socketio = FakeSocketIOServer()
315
manager = WebSocketManager(socketio, threading.RLock())
@@ -365,7 +365,7 @@ async def test_lifecycle_broadcasts_deliver_only_within_the_namespace() -> None:
365
CONTRACT.NS.DELIVERY: lifecycle broadcasts are namespace-scoped.
366
"""
367
368
- from python.helpers.websocket_manager import (
368
+ from helpers.websocket_manager import (
369
LIFECYCLE_CONNECT_EVENT,
370
LIFECYCLE_DISCONNECT_EVENT,
371
)
@@ -422,7 +422,7 @@ async def test_request_semantics_no_handlers_and_timeouts_are_namespace_scoped_a
422
CONTRACT.REQUEST.RESULTS + CONTRACT.REQUEST.RESULTS.ORDERING + CONTRACT.NS.ROUTING.
423
"""
424
425
- from python.helpers.websocket import WebSocketHandler
425
+ from helpers.websocket import WebSocketHandler
426
427
socketio = FakeSocketIOServer()
428
manager = WebSocketManager(socketio, threading.RLock())
tests/test_websocket_namespaces_integration.py
+2
-2
@@ -51,8 +51,8 @@ async def test_unregistered_namespace_connection_fails_with_unknown_namespace_co
51
from flask import Flask
52
import socketio
53
54
- from python.helpers.websocket import WebSocketHandler
55
- from python.helpers.websocket_manager import WebSocketManager
54
+ from helpers.websocket import WebSocketHandler
55
+ from helpers.websocket_manager import WebSocketManager
56
from run_ui import configure_websocket_namespaces
57
58
class OpenHandler(WebSocketHandler):
tests/test_websocket_root_namespace.py
+4
-4
@@ -51,8 +51,8 @@ async def test_root_namespace_request_style_calls_resolve_with_no_handlers() ->
51
from flask import Flask
52
import socketio
53
54
- from python.helpers.websocket import WebSocketHandler
55
- from python.helpers.websocket_manager import WebSocketManager
54
+ from helpers.websocket import WebSocketHandler
55
+ from helpers.websocket_manager import WebSocketManager
56
from run_ui import configure_websocket_namespaces
57
58
app = Flask("test_ws_root_namespace")
@@ -124,8 +124,8 @@ async def test_root_namespace_fire_and_forget_does_not_invoke_application_handle
124
from flask import Flask
125
import socketio
126
127
- from python.helpers.websocket import WebSocketHandler
128
- from python.helpers.websocket_manager import WebSocketManager
127
+ from helpers.websocket import WebSocketHandler
128
+ from helpers.websocket_manager import WebSocketManager
129
from run_ui import configure_websocket_namespaces
130
131
app = Flask("test_ws_root_fire_and_forget")
tests/test_webui_extension_surfaces.py
+1
-1
@@ -14,7 +14,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
14
if str(PROJECT_ROOT) not in sys.path:
15
sys.path.insert(0, str(PROJECT_ROOT))
16
17
-from python.api.load_webui_extensions import LoadWebuiExtensions
17
+from api.load_webui_extensions import LoadWebuiExtensions
18
19
20
SURFACE_SCENARIOS: list[tuple[str, str]] = [
tools/a2a_chat.py
renamed
+3
-3
@@ -1,6 +1,6 @@
1
-from python.helpers.tool import Tool, Response
2
-from python.helpers.print_style import PrintStyle
3
-from python.helpers.fasta2a_client import connect_to_agent, is_client_available
1
+from helpers.tool import Tool, Response
2
+from helpers.print_style import PrintStyle
3
+from helpers.fasta2a_client import connect_to_agent, is_client_available
4
5
6
class A2AChatTool(Tool):
tools/browser._py
renamed
+4
-4
@@ -1,10 +1,10 @@
1
# import asyncio
2
# from dataclasses import dataclass
3
# import time
4
-# from python.helpers.tool import Tool, Response
5
-# from python.helpers import files, rfc_exchange
6
-# from python.helpers.print_style import PrintStyle
7
-# from python.helpers.browser import Browser as BrowserManager
4
+# from helpers.tool import Tool, Response
5
+# from helpers import files, rfc_exchange
6
+# from helpers.print_style import PrintStyle
7
+# from helpers.browser import Browser as BrowserManager
8
# import uuid
9
10
tools/browser_agent.py
renamed
+8
-8
@@ -4,16 +4,16 @@ from typing import Optional, cast
4
from agent import Agent, InterventionException
5
from pathlib import Path
6
7
-from python.helpers.tool import Tool, Response
8
-from python.helpers import files, defer, persist_chat, strings
9
-from python.helpers.browser_use import browser_use # type: ignore[attr-defined]
10
-from python.helpers.print_style import PrintStyle
11
-from python.helpers.playwright import ensure_playwright_binary
12
-from python.helpers.secrets import get_secrets_manager
13
-from python.extensions.message_loop_start._10_iteration_no import get_iter_no
7
+from helpers.tool import Tool, Response
8
+from helpers import files, defer, persist_chat, strings
9
+from helpers.browser_use import browser_use # type: ignore[attr-defined]
10
+from helpers.print_style import PrintStyle
11
+from helpers.playwright import ensure_playwright_binary
12
+from helpers.secrets import get_secrets_manager
13
+from extensions.python.message_loop_start._10_iteration_no import get_iter_no
14
from pydantic import BaseModel
15
import uuid
16
-from python.helpers.dirty_json import DirtyJson
16
+from helpers.dirty_json import DirtyJson
17
18
19
class State:
tools/browser_do._py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
# import asyncio
2
-# from python.helpers.tool import Tool, Response
3
-# from python.tools.browser import Browser
4
-# from python.helpers.browser import NoPageError
2
+# from helpers.tool import Tool, Response
3
+# from tools.browser import Browser
4
+# from helpers.browser import NoPageError
5
# import asyncio
6
7
tools/browser_open._py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
# import asyncio
2
-# from python.helpers.tool import Tool, Response
3
-# from python.tools import browser
4
-# from python.tools.browser import Browser
2
+# from helpers.tool import Tool, Response
3
+# from tools import browser
4
+# from tools.browser import Browser
5
6
7
# class BrowserOpen(Browser):
tools/call_subordinate.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
from agent import Agent, UserMessage
2
-from python.helpers.tool import Tool, Response
2
+from helpers.tool import Tool, Response
3
from initialize import initialize_agent
4
-from python.extensions.hist_add_tool_result import _90_save_tool_call_file as save_tool_call_file
4
+from extensions.python.hist_add_tool_result import _90_save_tool_call_file as save_tool_call_file
5
6
7
class Delegation(Tool):
tools/code_execution_tool.py
renamed
+7
-7
@@ -2,13 +2,13 @@ import asyncio
2
from dataclasses import dataclass
3
import shlex
4
import time
5
-from python.helpers.tool import Tool, Response
6
-from python.helpers import files, rfc_exchange, projects, runtime, settings
7
-from python.helpers.print_style import PrintStyle
8
-from python.helpers.shell_local import LocalInteractiveSession
9
-from python.helpers.shell_ssh import SSHInteractiveSession
10
-from python.helpers.strings import truncate_text as truncate_text_string
11
-from python.helpers.messages import truncate_text as truncate_text_agent
5
+from helpers.tool import Tool, Response
6
+from helpers import files, rfc_exchange, projects, runtime, settings
7
+from helpers.print_style import PrintStyle
8
+from helpers.shell_local import LocalInteractiveSession
9
+from helpers.shell_ssh import SSHInteractiveSession
10
+from helpers.strings import truncate_text as truncate_text_string
11
+from helpers.messages import truncate_text as truncate_text_agent
12
import re
13
14
# Timeouts for python, nodejs, and terminal runtimes.
tools/document_query.py
renamed
+2
-2
@@ -1,7 +1,7 @@
1
import asyncio
2
3
-from python.helpers.tool import Tool, Response
4
-from python.helpers.document_query import DocumentQueryHelper
3
+from helpers.tool import Tool, Response
4
+from helpers.document_query import DocumentQueryHelper
5
6
7
class DocumentQueryTool(Tool):
tools/input.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
from agent import Agent, UserMessage
2
-from python.helpers.tool import Tool, Response
3
-from python.tools.code_execution_tool import CodeExecution
2
+from helpers.tool import Tool, Response
3
+from tools.code_execution_tool import CodeExecution
4
5
6
class Input(Tool):
tools/knowledge_tool._py
renamed
+3
-3
@@ -1,10 +1,10 @@
1
import asyncio
2
-from python.helpers import dotenv, perplexity_search, duckduckgo_search
2
+from helpers import dotenv, perplexity_search, duckduckgo_search
3
from plugins.memory.helpers.memory import Memory
4
from plugins.memory.tools.memory_load import DEFAULT_THRESHOLD as DEFAULT_MEMORY_THRESHOLD
5
6
-from python.helpers.tool import Tool, Response
7
-from python.helpers.document_query import DocumentQueryHelper
6
+from helpers.tool import Tool, Response
7
+from helpers.document_query import DocumentQueryHelper
8
9
SEARCH_ENGINE_RESULTS = 10
10
tools/notify_user.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
from agent import AgentContext
3
-from python.helpers.notification import NotificationPriority, NotificationType
3
+from helpers.notification import NotificationPriority, NotificationType
4
5
class NotifyUserTool(Tool):
6
tools/response.py
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-from python.helpers.tool import Tool, Response
1
+from helpers.tool import Tool, Response
2
3
4
class ResponseTool(Tool):
tools/scheduler.py
renamed
+4
-4
@@ -3,14 +3,14 @@ from datetime import datetime
3
import json
4
import random
5
import re
6
-from python.helpers.tool import Tool, Response
7
-from python.helpers.task_scheduler import (
6
+from helpers.tool import Tool, Response
7
+from helpers.task_scheduler import (
8
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask,
9
serialize_task, TaskState, TaskSchedule, TaskPlan, parse_datetime, serialize_datetime
10
)
11
from agent import AgentContext
12
-from python.helpers import persist_chat
13
-from python.helpers.projects import get_context_project_name, load_basic_project_data
12
+from helpers import persist_chat
13
+from helpers.projects import get_context_project_name, load_basic_project_data
14
15
DEFAULT_WAIT_TIMEOUT = 300
16
tools/search_engine.py
renamed
+5
-5
@@ -1,10 +1,10 @@
1
import os
2
import asyncio
3
-from python.helpers import dotenv, perplexity_search, duckduckgo_search
4
-from python.helpers.tool import Tool, Response
5
-from python.helpers.print_style import PrintStyle
6
-from python.helpers.errors import handle_error
7
-from python.helpers.searxng import search as searxng
3
+from helpers import dotenv, perplexity_search, duckduckgo_search
4
+from helpers.tool import Tool, Response
5
+from helpers.print_style import PrintStyle
6
+from helpers.errors import handle_error
7
+from helpers.searxng import search as searxng
8
9
SEARCH_ENGINE_RESULTS = 10
10
tools/skills_tool.py
renamed
+3
-3
@@ -3,9 +3,9 @@ from __future__ import annotations
3
from pathlib import Path
4
from typing import List
5
6
-from python.helpers.tool import Tool, Response
7
-from python.helpers import projects, files, file_tree
8
-from python.helpers import skills as skills_helper, runtime
6
+from helpers.tool import Tool, Response
7
+from helpers import projects, files, file_tree
8
+from helpers import skills as skills_helper, runtime
9
10
11
DATA_NAME_LOADED_SKILLS = "loaded_skills"
tools/unknown.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from python.helpers.tool import Tool, Response
2
-from python.extensions.system_prompt._10_system_prompt import (
1
+from helpers.tool import Tool, Response
2
+from extensions.python.system_prompt._10_system_prompt import (
3
get_tools_prompt,
4
)
5
tools/vision_load.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
import base64
2
-from python.helpers.print_style import PrintStyle
3
-from python.helpers.tool import Tool, Response
4
-from python.helpers import runtime, files, images
2
+from helpers.print_style import PrintStyle
3
+from helpers.tool import Tool, Response
4
+from helpers import runtime, files, images
5
from mimetypes import guess_type
6
-from python.helpers import history
6
+from helpers import history
7
8
# image optimization and token estimation for context window
9
MAX_PIXELS = 768_000
tools/wait.py
renamed
+4
-4
@@ -1,9 +1,9 @@
1
import asyncio
2
from datetime import datetime, timedelta, timezone
3
-from python.helpers.tool import Tool, Response
4
-from python.helpers.print_style import PrintStyle
5
-from python.helpers.wait import managed_wait
6
-from python.helpers.localization import Localization
3
+from helpers.tool import Tool, Response
4
+from helpers.print_style import PrintStyle
5
+from helpers.wait import managed_wait
6
+from helpers.localization import Localization
7
8
class WaitTool(Tool):
9
webui/components/sidebar/chats/chats-store.js
-7
@@ -98,8 +98,6 @@ const model = {
98
return;
99
}
100
101
- console.log("Deleting chat with ID:", id);
102
-
101
try {
102
// Switch to another context if deleting current
103
if (this.selected === id) {
@@ -111,11 +109,6 @@ const model = {
109
110
// Update the UI - remove from contexts
111
const updatedContexts = this.contexts.filter((ctx) => ctx.id !== id);
114
- console.log(
115
- "Updated contexts after deletion:",
116
- JSON.stringify(updatedContexts.map((c) => ({ id: c.id, name: c.name })))
117
- );
118
-
112
// Force UI update by creating a new array
113
this.contexts = [...updatedContexts];
114
webui/js/api.js
+51
-5
@@ -31,6 +31,25 @@ export async function callJsonApi(endpoint, data) {
31
* @returns {Promise<Response>} The fetch response
32
*/
33
export async function fetchApi(url, request) {
34
+ async function _getExtensions() {
35
+ try {
36
+ return await import("./extensions.js");
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {string} apiUrl
44
+ * @returns {Promise<boolean>}
45
+ */
46
+ async function _shouldCallApiExtensions(apiUrl) {
47
+ const extensions = await _getExtensions();
48
+ if (!extensions) return false;
49
+ const excluded = extensions.API_EXTENSION_EXCLUDED_ENDPOINTS;
50
+ return !(excluded instanceof Set && excluded.has(apiUrl));
51
+ }
52
+
53
async function _wrap(retry) {
54
// get the CSRF token
55
const token = await getCsrfToken();
@@ -46,21 +65,48 @@ export async function fetchApi(url, request) {
65
66
// perform the fetch with the updated request
67
const apiUrl = url.startsWith('/api/') || url.startsWith('api/') ? `/${url.replace(/^\/+/, '')}` : `/api/${url.replace(/^\/+/, '')}`;
49
- const response = await fetch(apiUrl, finalRequest);
68
+
69
+ /** @type {{ url: string, apiUrl: string, request: any, response: Response | null, retry: boolean }} */
70
+ const ctx = {
71
+ url,
72
+ apiUrl,
73
+ request: finalRequest,
74
+ response: null,
75
+ retry,
76
+ };
77
+
78
+ if (await _shouldCallApiExtensions(apiUrl)) {
79
+ const extensions = await _getExtensions();
80
+ if (extensions) {
81
+ await extensions.callJsExtensions("api_call_before", ctx);
82
+ }
83
+ }
84
+
85
+ const response = ctx.response || await fetch(ctx.apiUrl, ctx.request);
86
+ ctx.response = response;
87
+
88
+ if (await _shouldCallApiExtensions(apiUrl)) {
89
+ const extensions = await _getExtensions();
90
+ if (extensions) {
91
+ await extensions.callJsExtensions("api_call_after", ctx);
92
+ }
93
+ }
94
+
95
+ const finalResponse = ctx.response;
96
97
// check if there was an CSRF error
52
- if (response.status === 403 && retry) {
98
+ if (finalResponse.status === 403 && retry) {
99
// retry the request with new token
100
csrfToken = null;
101
return await _wrap(false);
56
- } else if (response.redirected && response.url.endsWith("/login")) {
102
+ } else if (finalResponse.redirected && finalResponse.url.endsWith("/login")) {
103
// redirect to login
58
- window.location.href = response.url;
104
+ window.location.href = finalResponse.url;
105
return;
106
}
107
108
// return the response
63
- return response;
109
+ return finalResponse;
110
}
111
112
// perform the request
webui/js/extensions.js
+4
@@ -21,6 +21,10 @@ import * as cache from "./cache.js";
21
const JS_CACHE_AREA = "frontend_extensions_js(extensions)(plugins)";
22
const HTML_CACHE_AREA = "frontend_extensions_html(extensions)(plugins)";
23
24
+export const API_EXTENSION_EXCLUDED_ENDPOINTS = new Set([
25
+ "/api/load_webui_extensions",
26
+]);
27
+
28
export function invalidateCache() {
29
cache.clear(JS_CACHE_AREA);
30
cache.clear(HTML_CACHE_AREA);
webui/js/messages.js
+1
-1
@@ -122,7 +122,7 @@ export async function getMessageHandler(type) {
122
123
async function getHandlerFromExtensions(type){
124
const extData = { type: type, handler: undefined }
125
- await callJsExtensions("getMessageHandler", extData);
125
+ await callJsExtensions("get_message_handler", extData);
126
// return handler from extensions
127
if(typeof extData.handler == "function") return extData.handler;
128
//not set by extensions, return default