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