Change default self-update backup directory from /a0/tmp to /root and add lazy aiogram dependency loading for Telegram plugin
- Update default backup path from /a0/tmp/self-update-backups to /root/update-backups in self_update_manager.py, helpers/self_update.py, and documentation - Move aiogram from global requirements.txt to plugin-local requirements for _telegram_integration - Add ensure_dependencies() helper that installs aiogram on-demand via uv pip install - Add has_aiogram() check to avoid
frdel committed
Mar 26, 2026 at 10:20 UTC
247c8d845ffab8acbf4db5d3b93cf343557a080c
15 files changed
+135
-35
docker/run/fs/exe/self_update_manager.py
+1
-1
@@ -614,7 +614,7 @@ def execute_pending_update(
614
if bool(request_data.get("backup_usr", True)):
615
backup_destination = create_usr_backup(
616
repo_dir=REPO_DIR,
617
- backup_path=str(request_data.get("backup_path", "/a0/tmp/self-update-backups")),
617
+ backup_path=str(request_data.get("backup_path", "/root/update-backups")),
618
backup_name=str(request_data.get("backup_name", "agent-zero-usr-backup.zip")),
619
conflict_policy=str(request_data.get("backup_conflict_policy", "rename")),
620
logger=logger,
docs/guides/self-update.md
+1
-1
@@ -27,7 +27,7 @@ Because these files live in `/exe`, you can recover from an older downgraded `/a
27
28
The updater can create a zip backup of `/a0/usr` before replacing repository files.
29
30
-- The default backup directory is `/a0/tmp/self-update-backups`
30
+- The default backup directory is `/root/update-backups`
31
- The default file name format is `usr-YYYYMMDD-HHMMSS.zip`
32
- Conflict handling supports rename, overwrite, or fail-before-restart
33
helpers/self_update.py
+1
-2
@@ -108,8 +108,7 @@ def get_log_text() -> str:
108
109
110
def get_default_backup_dir(repo_dir: str | Path | None = None) -> Path:
111
- repository = get_repo_dir(repo_dir)
112
- return repository / "tmp" / "self-update-backups"
111
+ return Path("/root/update-backups")
112
113
114
def get_repo_dir(repo_dir: str | Path | None = None) -> Path:
plugins/_telegram_integration/README.md
+3
@@ -8,6 +8,9 @@ This plugin connects one or more Telegram bots to Agent Zero. Each bot runs inde
8
9
## Main Behavior
10
11
+- **Dependency management**
12
+ - Keeps `aiogram` in the plugin-local `requirements.txt` instead of the global root requirements.
13
+ - Active code paths call `helpers/dependencies.py::ensure_dependencies()` to install `aiogram` into the framework runtime on first use via `uv pip install --python <current interpreter> -r plugins/_telegram_integration/requirements.txt`.
14
- **Bot lifecycle**
15
- Managed by a `job_loop` extension that starts, restarts, or stops bots whenever plugin settings change.
16
- Supports both long-polling and webhook delivery modes.
plugins/_telegram_integration/api/test_connection.py
+2
@@ -1,5 +1,6 @@
1
from helpers.api import ApiHandler, Request
2
from helpers.errors import format_error
3
+from plugins._telegram_integration.helpers.dependencies import ensure_dependencies
4
5
6
class TestConnection(ApiHandler):
@@ -18,6 +19,7 @@ class TestConnection(ApiHandler):
19
return {"success": False, "results": results}
20
21
try:
22
+ ensure_dependencies()
23
from plugins._telegram_integration.helpers.bot_manager import test_token
24
ok, message = await test_token(token)
25
results.append({
plugins/_telegram_integration/api/webhook.py
+2
@@ -1,5 +1,6 @@
1
from helpers.api import ApiHandler, Request, Response
2
from helpers.print_style import PrintStyle
3
+from plugins._telegram_integration.helpers.dependencies import ensure_dependencies
4
5
6
class TelegramWebhook(ApiHandler):
@@ -18,6 +19,7 @@ class TelegramWebhook(ApiHandler):
19
return ["POST"]
20
21
async def process(self, input: dict, request: Request) -> dict | Response:
22
+ ensure_dependencies()
23
from aiogram.types import Update
24
25
from plugins._telegram_integration.helpers.bot_manager import get_bot
plugins/_telegram_integration/extensions/python/job_loop/_10_telegram_bot.py
+14
-6
@@ -5,6 +5,7 @@ from helpers.extension import Extension
5
from helpers.errors import format_error
6
from helpers.print_style import PrintStyle
7
from helpers import plugins
8
+from plugins._telegram_integration.helpers.dependencies import ensure_dependencies, has_aiogram
9
10
11
PLUGIN_NAME: str = "_telegram_integration"
@@ -13,6 +14,19 @@ PLUGIN_NAME: str = "_telegram_integration"
14
class TelegramBotManager(Extension):
15
16
async def execute(self, **kwargs: Any) -> None:
17
+ config = plugins.get_plugin_config(PLUGIN_NAME) or {}
18
+ bots_cfg = config.get("bots", [])
19
+ enabled_names = {
20
+ b["name"] for b in bots_cfg if b.get("enabled") and b.get("name") and b.get("token")
21
+ }
22
+
23
+ # Avoid installing aiogram on idle ticks when Telegram is not configured.
24
+ if not enabled_names and not has_aiogram():
25
+ return
26
+
27
+ if enabled_names:
28
+ ensure_dependencies()
29
+
30
from plugins._telegram_integration.helpers.bot_manager import (
31
get_all_bots,
32
create_bot,
@@ -32,12 +46,6 @@ class TelegramBotManager(Extension):
46
47
cleanup_old_attachments()
48
35
- config = plugins.get_plugin_config(PLUGIN_NAME) or {}
36
- bots_cfg = config.get("bots", [])
37
- enabled_names = {
38
- b["name"] for b in bots_cfg if b.get("enabled") and b.get("name") and b.get("token")
39
- }
40
-
49
running = get_all_bots()
50
51
# Stop bots that are no longer enabled
plugins/_telegram_integration/extensions/python/process_chain_end/_55_telegram_reply.py
+8
-3
@@ -2,10 +2,14 @@ from helpers.extension import Extension
2
from helpers.print_style import PrintStyle
3
from helpers.errors import format_error
4
from agent import AgentContext, LoopData, UserMessage
5
-from plugins._telegram_integration.helpers.handler import (
6
- CTX_TG_BOT, CTX_TG_ATTACHMENTS, CTX_TG_KEYBOARD,
7
- CTX_TG_TYPING_STOP, CTX_TG_REPLY_TO,
5
+from plugins._telegram_integration.helpers.constants import (
6
+ CTX_TG_BOT,
7
+ CTX_TG_ATTACHMENTS,
8
+ CTX_TG_KEYBOARD,
9
+ CTX_TG_TYPING_STOP,
10
+ CTX_TG_REPLY_TO,
11
)
12
+from plugins._telegram_integration.helpers.dependencies import ensure_dependencies
13
14
MAX_SEND_RETRIES: int = 2
15
CTX_SEND_FAILURES: str = "_telegram_send_failures"
@@ -46,6 +50,7 @@ class TelegramAutoReply(Extension):
50
attachments: list[str],
51
keyboard: list[list[dict]] | None,
52
):
53
+ ensure_dependencies()
54
from plugins._telegram_integration.helpers.handler import send_telegram_reply
55
56
error = await send_telegram_reply(
plugins/_telegram_integration/extensions/python/system_prompt/_20_telegram_context.py
+1
-1
@@ -1,6 +1,6 @@
1
from helpers.extension import Extension
2
from agent import LoopData
3
-from plugins._telegram_integration.helpers.handler import CTX_TG_BOT, CTX_TG_BOT_CFG
3
+from plugins._telegram_integration.helpers.constants import CTX_TG_BOT, CTX_TG_BOT_CFG
4
5
6
class TelegramContextPrompt(Extension):
plugins/_telegram_integration/extensions/python/tool_execute_after/_50_telegram_response.py
+3
-1
@@ -1,10 +1,11 @@
1
from helpers.extension import Extension
2
from helpers.tool import Response
3
-from plugins._telegram_integration.helpers.handler import (
3
+from plugins._telegram_integration.helpers.constants import (
4
CTX_TG_BOT,
5
CTX_TG_ATTACHMENTS,
6
CTX_TG_KEYBOARD,
7
)
8
+from plugins._telegram_integration.helpers.dependencies import ensure_dependencies
9
10
11
class TelegramResponseIntercept(Extension):
@@ -40,6 +41,7 @@ class TelegramResponseIntercept(Extension):
41
await self._send_inline(context, tool, response)
42
43
async def _send_inline(self, context, tool, response: Response):
44
+ ensure_dependencies()
45
from plugins._telegram_integration.helpers.handler import send_telegram_reply
46
47
agent = self.agent
plugins/_telegram_integration/helpers/constants.py
new
+16
@@ -0,0 +1,16 @@
1
+PLUGIN_NAME = "_telegram_integration"
2
+DOWNLOAD_FOLDER = "usr/uploads"
3
+STATE_FILE = "usr/plugins/_telegram_integration/state.json"
4
+
5
+# Context data keys
6
+CTX_TG_BOT = "telegram_bot"
7
+CTX_TG_BOT_CFG = "telegram_bot_cfg"
8
+CTX_TG_CHAT_ID = "telegram_chat_id"
9
+CTX_TG_USER_ID = "telegram_user_id"
10
+CTX_TG_USERNAME = "telegram_username"
11
+CTX_TG_TYPING_STOP = "_telegram_typing_stop"
12
+CTX_TG_REPLY_TO = "_telegram_reply_to_message_id"
13
+
14
+# Transient
15
+CTX_TG_ATTACHMENTS = "_telegram_response_attachments"
16
+CTX_TG_KEYBOARD = "_telegram_response_keyboard"
plugins/_telegram_integration/helpers/dependencies.py
new
+68
@@ -0,0 +1,68 @@
1
+from __future__ import annotations
2
+
3
+import importlib
4
+import importlib.util
5
+import shutil
6
+import subprocess
7
+import sys
8
+import threading
9
+from pathlib import Path
10
+
11
+from helpers.errors import format_error
12
+from helpers.print_style import PrintStyle
13
+
14
+
15
+_LOCK = threading.Lock()
16
+_CHECKED = False
17
+_PLUGIN_DIR = Path(__file__).resolve().parents[1]
18
+_REQUIREMENTS_FILE = _PLUGIN_DIR / "requirements.txt"
19
+
20
+
21
+def has_aiogram() -> bool:
22
+ return importlib.util.find_spec("aiogram") is not None
23
+
24
+
25
+def ensure_dependencies() -> None:
26
+ global _CHECKED
27
+
28
+ if _CHECKED and has_aiogram():
29
+ return
30
+
31
+ with _LOCK:
32
+ if _CHECKED and has_aiogram():
33
+ return
34
+ if has_aiogram():
35
+ _CHECKED = True
36
+ return
37
+
38
+ _install_aiogram()
39
+ importlib.invalidate_caches()
40
+
41
+ if not has_aiogram():
42
+ raise RuntimeError("Telegram dependency 'aiogram' is still unavailable after installation")
43
+
44
+ _CHECKED = True
45
+
46
+
47
+def _install_aiogram() -> None:
48
+ uv = shutil.which("uv")
49
+ if not uv:
50
+ raise RuntimeError("Telegram plugin requires 'uv' to install aiogram automatically")
51
+ if not _REQUIREMENTS_FILE.is_file():
52
+ raise RuntimeError(f"Telegram plugin requirements file not found: {_REQUIREMENTS_FILE}")
53
+
54
+ cmd = [
55
+ uv,
56
+ "pip",
57
+ "install",
58
+ "--python",
59
+ sys.executable,
60
+ "-r",
61
+ str(_REQUIREMENTS_FILE),
62
+ ]
63
+
64
+ PrintStyle.info("Telegram: aiogram not found, installing plugin dependency")
65
+ try:
66
+ subprocess.check_call(cmd, cwd=str(_PLUGIN_DIR))
67
+ except Exception as e:
68
+ raise RuntimeError(f"Failed to install Telegram dependency 'aiogram': {format_error(e)}") from e
plugins/_telegram_integration/helpers/handler.py
+14
-19
@@ -21,24 +21,20 @@ from initialize import initialize_agent
21
22
from plugins._telegram_integration.helpers import telegram_client as tc
23
from plugins._telegram_integration.helpers.bot_manager import get_bot
24
-
25
-
26
-PLUGIN_NAME = "_telegram_integration"
27
-DOWNLOAD_FOLDER = "usr/uploads"
28
-STATE_FILE = "usr/plugins/_telegram_integration/state.json"
29
-
30
-# Context data keys
31
-CTX_TG_BOT = "telegram_bot"
32
-CTX_TG_BOT_CFG = "telegram_bot_cfg"
33
-CTX_TG_CHAT_ID = "telegram_chat_id"
34
-CTX_TG_USER_ID = "telegram_user_id"
35
-CTX_TG_USERNAME = "telegram_username"
36
-CTX_TG_TYPING_STOP = "_telegram_typing_stop"
37
-CTX_TG_REPLY_TO = "_telegram_reply_to_message_id"
38
-
39
-# Transient
40
-CTX_TG_ATTACHMENTS = "_telegram_response_attachments"
41
-CTX_TG_KEYBOARD = "_telegram_response_keyboard"
24
+from plugins._telegram_integration.helpers.constants import (
25
+ PLUGIN_NAME,
26
+ DOWNLOAD_FOLDER,
27
+ STATE_FILE,
28
+ CTX_TG_BOT,
29
+ CTX_TG_BOT_CFG,
30
+ CTX_TG_CHAT_ID,
31
+ CTX_TG_USER_ID,
32
+ CTX_TG_USERNAME,
33
+ CTX_TG_TYPING_STOP,
34
+ CTX_TG_REPLY_TO,
35
+ CTX_TG_ATTACHMENTS,
36
+ CTX_TG_KEYBOARD,
37
+)
38
39
# Chat mapping: (bot_name, tg_user_id) → AgentContext ID
40
@@ -590,4 +586,3 @@ def _inherit_model_override(ctx: AgentContext):
586
)
587
if source:
588
ctx.set_data("chat_model_override", source.get_data("chat_model_override"))
593
-
plugins/_telegram_integration/requirements.txt
new
+1
@@ -0,0 +1 @@
1
+aiogram>=3.15.0
requirements.txt
-1
@@ -48,7 +48,6 @@ html2text>=2024.2.26
48
beautifulsoup4>=4.12.3
49
boto3>=1.35.0
50
exchangelib>=5.4.3
51
-aiogram>=3.15.0
51
pywinpty==3.0.2; sys_platform == "win32"
52
python-socketio>=5.14.2
53
uvicorn>=0.38.0