AstrBot 4.13.1__py3-none-any.whl → 4.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. astrbot/builtin_stars/astrbot/main.py +0 -5
  2. astrbot/cli/__init__.py +1 -1
  3. astrbot/core/__init__.py +2 -0
  4. astrbot/core/agent/agent.py +2 -1
  5. astrbot/core/agent/handoff.py +14 -1
  6. astrbot/core/agent/runners/tool_loop_agent_runner.py +14 -1
  7. astrbot/core/agent/tool.py +5 -0
  8. astrbot/core/astr_agent_run_util.py +21 -3
  9. astrbot/core/astr_agent_tool_exec.py +178 -3
  10. astrbot/core/astr_main_agent.py +980 -0
  11. astrbot/core/astr_main_agent_resources.py +453 -0
  12. astrbot/core/computer/computer_client.py +10 -1
  13. astrbot/core/computer/tools/fs.py +22 -14
  14. astrbot/core/config/default.py +132 -58
  15. astrbot/core/core_lifecycle.py +50 -4
  16. astrbot/core/cron/__init__.py +3 -0
  17. astrbot/core/cron/events.py +67 -0
  18. astrbot/core/cron/manager.py +376 -0
  19. astrbot/core/db/__init__.py +60 -0
  20. astrbot/core/db/po.py +31 -0
  21. astrbot/core/db/sqlite.py +120 -0
  22. astrbot/core/log.py +189 -1
  23. astrbot/core/message/message_event_result.py +21 -3
  24. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +130 -580
  25. astrbot/core/platform/astr_message_event.py +14 -0
  26. astrbot/core/platform/platform.py +9 -0
  27. astrbot/core/platform/platform_metadata.py +2 -0
  28. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +1 -0
  29. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -0
  30. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +1 -0
  31. astrbot/core/platform/sources/webchat/webchat_adapter.py +1 -0
  32. astrbot/core/platform/sources/wecom/wecom_adapter.py +1 -0
  33. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +1 -0
  34. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +1 -0
  35. astrbot/core/provider/entities.py +1 -1
  36. astrbot/core/skills/skill_manager.py +9 -8
  37. astrbot/core/star/context.py +8 -0
  38. astrbot/core/star/filter/custom_filter.py +3 -3
  39. astrbot/core/star/register/star_handler.py +1 -1
  40. astrbot/core/subagent_orchestrator.py +96 -0
  41. astrbot/core/tools/cron_tools.py +174 -0
  42. astrbot/core/utils/history_saver.py +31 -0
  43. astrbot/core/utils/trace.py +77 -0
  44. astrbot/dashboard/routes/__init__.py +4 -0
  45. astrbot/dashboard/routes/cron.py +174 -0
  46. astrbot/dashboard/routes/log.py +36 -0
  47. astrbot/dashboard/routes/plugin.py +11 -0
  48. astrbot/dashboard/routes/skills.py +12 -37
  49. astrbot/dashboard/routes/subagent.py +117 -0
  50. astrbot/dashboard/routes/tools.py +41 -14
  51. astrbot/dashboard/server.py +3 -0
  52. {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/METADATA +21 -2
  53. {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/RECORD +56 -49
  54. astrbot/builtin_stars/astrbot/process_llm_request.py +0 -300
  55. astrbot/builtin_stars/reminder/main.py +0 -266
  56. astrbot/builtin_stars/reminder/metadata.yaml +0 -4
  57. astrbot/core/pipeline/process_stage/utils.py +0 -219
  58. {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/WHEEL +0 -0
  59. {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/entry_points.txt +0 -0
  60. {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
5
5
 
6
6
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
7
7
 
8
- VERSION = "4.13.1"
8
+ VERSION = "4.14.0"
9
9
  DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
10
10
 
11
11
  WEBHOOK_SUPPORTED_PLATFORMS = [
@@ -91,7 +91,7 @@ DEFAULT_CONFIG = {
91
91
  "3. If there was an initial user goal, state it first and describe the current progress/status.\n"
92
92
  "4. Write the summary in the user's language.\n"
93
93
  ),
94
- "llm_compress_keep_recent": 4,
94
+ "llm_compress_keep_recent": 6,
95
95
  "llm_compress_provider_id": "",
96
96
  "max_context_length": -1,
97
97
  "dequeue_context_length": 1,
@@ -114,15 +114,31 @@ DEFAULT_CONFIG = {
114
114
  "provider": "moonshotai",
115
115
  "moonshotai_api_key": "",
116
116
  },
117
+ "proactive_capability": {
118
+ "add_cron_tools": True,
119
+ },
120
+ "computer_use_runtime": "local",
117
121
  "sandbox": {
118
- "enable": False,
119
122
  "booter": "shipyard",
120
123
  "shipyard_endpoint": "",
121
124
  "shipyard_access_token": "",
122
125
  "shipyard_ttl": 3600,
123
126
  "shipyard_max_sessions": 10,
124
127
  },
125
- "skills": {"runtime": "sandbox"},
128
+ },
129
+ # SubAgent orchestrator mode:
130
+ # - main_enable = False: disabled; main LLM mounts tools normally (persona selection).
131
+ # - main_enable = True: enabled; main LLM will include handoff tools and can optionally
132
+ # remove tools that are duplicated on subagents via remove_main_duplicate_tools.
133
+ "subagent_orchestrator": {
134
+ "main_enable": False,
135
+ "remove_main_duplicate_tools": False,
136
+ "router_system_prompt": (
137
+ "You are a task router. Your job is to chat naturally, recognize user intent, "
138
+ "and delegate work to the most suitable subagent using transfer_to_* tools. "
139
+ "Do not try to use domain tools yourself. If no subagent fits, respond directly."
140
+ ),
141
+ "agents": [],
126
142
  },
127
143
  "provider_stt_settings": {
128
144
  "enable": False,
@@ -182,6 +198,13 @@ DEFAULT_CONFIG = {
182
198
  },
183
199
  "wake_prefix": ["/"],
184
200
  "log_level": "INFO",
201
+ "log_file_enable": False,
202
+ "log_file_path": "logs/astrbot.log",
203
+ "log_file_max_mb": 20,
204
+ "trace_enable": False,
205
+ "trace_log_enable": False,
206
+ "trace_log_path": "logs/astrbot.trace.log",
207
+ "trace_log_max_mb": 20,
185
208
  "pip_install_arg": "",
186
209
  "pypi_index_url": "https://mirrors.aliyun.com/pypi/simple/",
187
210
  "persona": [], # deprecated
@@ -2201,15 +2224,12 @@ CONFIG_METADATA_2 = {
2201
2224
  },
2202
2225
  },
2203
2226
  },
2204
- "skills": {
2227
+ "proactive_capability": {
2205
2228
  "type": "object",
2206
2229
  "items": {
2207
- "enable": {
2230
+ "add_cron_tools": {
2208
2231
  "type": "bool",
2209
2232
  },
2210
- "runtime": {
2211
- "type": "string",
2212
- },
2213
2233
  },
2214
2234
  },
2215
2235
  },
@@ -2321,6 +2341,18 @@ CONFIG_METADATA_2 = {
2321
2341
  "type": "string",
2322
2342
  "options": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
2323
2343
  },
2344
+ "log_file_enable": {"type": "bool"},
2345
+ "log_file_path": {"type": "string", "condition": {"log_file_enable": True}},
2346
+ "log_file_max_mb": {"type": "int", "condition": {"log_file_enable": True}},
2347
+ "trace_log_enable": {"type": "bool"},
2348
+ "trace_log_path": {
2349
+ "type": "string",
2350
+ "condition": {"trace_log_enable": True},
2351
+ },
2352
+ "trace_log_max_mb": {
2353
+ "type": "int",
2354
+ "condition": {"trace_log_enable": True},
2355
+ },
2324
2356
  "t2i_strategy": {
2325
2357
  "type": "string",
2326
2358
  "options": ["remote", "local"],
@@ -2472,6 +2504,7 @@ CONFIG_METADATA_3 = {
2472
2504
  },
2473
2505
  "persona": {
2474
2506
  "description": "人格",
2507
+ "hint": "",
2475
2508
  "type": "object",
2476
2509
  "items": {
2477
2510
  "provider_settings.default_personality": {
@@ -2487,6 +2520,7 @@ CONFIG_METADATA_3 = {
2487
2520
  },
2488
2521
  "knowledgebase": {
2489
2522
  "description": "知识库",
2523
+ "hint": "",
2490
2524
  "type": "object",
2491
2525
  "items": {
2492
2526
  "kb_names": {
@@ -2519,6 +2553,7 @@ CONFIG_METADATA_3 = {
2519
2553
  },
2520
2554
  "websearch": {
2521
2555
  "description": "网页搜索",
2556
+ "hint": "",
2522
2557
  "type": "object",
2523
2558
  "items": {
2524
2559
  "provider_settings.web_search": {
@@ -2529,6 +2564,9 @@ CONFIG_METADATA_3 = {
2529
2564
  "description": "网页搜索提供商",
2530
2565
  "type": "string",
2531
2566
  "options": ["default", "tavily", "baidu_ai_search"],
2567
+ "condition": {
2568
+ "provider_settings.web_search": True,
2569
+ },
2532
2570
  },
2533
2571
  "provider_settings.websearch_tavily_key": {
2534
2572
  "description": "Tavily API Key",
@@ -2537,6 +2575,7 @@ CONFIG_METADATA_3 = {
2537
2575
  "hint": "可添加多个 Key 进行轮询。",
2538
2576
  "condition": {
2539
2577
  "provider_settings.websearch_provider": "tavily",
2578
+ "provider_settings.web_search": True,
2540
2579
  },
2541
2580
  },
2542
2581
  "provider_settings.websearch_baidu_app_builder_key": {
@@ -2550,6 +2589,9 @@ CONFIG_METADATA_3 = {
2550
2589
  "provider_settings.web_search_link": {
2551
2590
  "description": "显示来源引用",
2552
2591
  "type": "bool",
2592
+ "condition": {
2593
+ "provider_settings.web_search": True,
2594
+ },
2553
2595
  },
2554
2596
  },
2555
2597
  "condition": {
@@ -2557,45 +2599,17 @@ CONFIG_METADATA_3 = {
2557
2599
  "provider_settings.enable": True,
2558
2600
  },
2559
2601
  },
2560
- # "file_extract": {
2561
- # "description": "文档解析能力 [beta]",
2562
- # "type": "object",
2563
- # "items": {
2564
- # "provider_settings.file_extract.enable": {
2565
- # "description": "启用文档解析能力",
2566
- # "type": "bool",
2567
- # },
2568
- # "provider_settings.file_extract.provider": {
2569
- # "description": "文档解析提供商",
2570
- # "type": "string",
2571
- # "options": ["moonshotai"],
2572
- # "condition": {
2573
- # "provider_settings.file_extract.enable": True,
2574
- # },
2575
- # },
2576
- # "provider_settings.file_extract.moonshotai_api_key": {
2577
- # "description": "Moonshot AI API Key",
2578
- # "type": "string",
2579
- # "condition": {
2580
- # "provider_settings.file_extract.provider": "moonshotai",
2581
- # "provider_settings.file_extract.enable": True,
2582
- # },
2583
- # },
2584
- # },
2585
- # "condition": {
2586
- # "provider_settings.agent_runner_type": "local",
2587
- # "provider_settings.enable": True,
2588
- # },
2589
- # },
2590
- "sandbox": {
2591
- "description": "Agent 沙箱环境",
2602
+ "agent_computer_use": {
2603
+ "description": "Agent Computer Use",
2592
2604
  "hint": "",
2593
2605
  "type": "object",
2594
2606
  "items": {
2595
- "provider_settings.sandbox.enable": {
2596
- "description": "启用沙箱环境",
2597
- "type": "bool",
2598
- "hint": "启用后,Agent 可以使用沙箱环境中的工具和资源,如 Python 代码执行、Shell 等。",
2607
+ "provider_settings.computer_use_runtime": {
2608
+ "description": "Computer Use Runtime",
2609
+ "type": "string",
2610
+ "options": ["none", "local", "sandbox"],
2611
+ "labels": ["无", "本地", "沙箱"],
2612
+ "hint": "选择 Computer Use 运行环境。",
2599
2613
  },
2600
2614
  "provider_settings.sandbox.booter": {
2601
2615
  "description": "沙箱环境驱动器",
@@ -2603,7 +2617,7 @@ CONFIG_METADATA_3 = {
2603
2617
  "options": ["shipyard"],
2604
2618
  "labels": ["Shipyard"],
2605
2619
  "condition": {
2606
- "provider_settings.sandbox.enable": True,
2620
+ "provider_settings.computer_use_runtime": "sandbox",
2607
2621
  },
2608
2622
  },
2609
2623
  "provider_settings.sandbox.shipyard_endpoint": {
@@ -2611,7 +2625,7 @@ CONFIG_METADATA_3 = {
2611
2625
  "type": "string",
2612
2626
  "hint": "Shipyard 服务的 API 访问地址。",
2613
2627
  "condition": {
2614
- "provider_settings.sandbox.enable": True,
2628
+ "provider_settings.computer_use_runtime": "sandbox",
2615
2629
  "provider_settings.sandbox.booter": "shipyard",
2616
2630
  },
2617
2631
  "_special": "check_shipyard_connection",
@@ -2621,7 +2635,7 @@ CONFIG_METADATA_3 = {
2621
2635
  "type": "string",
2622
2636
  "hint": "用于访问 Shipyard 服务的访问令牌。",
2623
2637
  "condition": {
2624
- "provider_settings.sandbox.enable": True,
2638
+ "provider_settings.computer_use_runtime": "sandbox",
2625
2639
  "provider_settings.sandbox.booter": "shipyard",
2626
2640
  },
2627
2641
  },
@@ -2630,7 +2644,7 @@ CONFIG_METADATA_3 = {
2630
2644
  "type": "int",
2631
2645
  "hint": "Shipyard 会话的生存时间(秒)。",
2632
2646
  "condition": {
2633
- "provider_settings.sandbox.enable": True,
2647
+ "provider_settings.computer_use_runtime": "sandbox",
2634
2648
  "provider_settings.sandbox.booter": "shipyard",
2635
2649
  },
2636
2650
  },
@@ -2639,7 +2653,7 @@ CONFIG_METADATA_3 = {
2639
2653
  "type": "int",
2640
2654
  "hint": "Shipyard 最大会话数量。",
2641
2655
  "condition": {
2642
- "provider_settings.sandbox.enable": True,
2656
+ "provider_settings.computer_use_runtime": "sandbox",
2643
2657
  "provider_settings.sandbox.booter": "shipyard",
2644
2658
  },
2645
2659
  },
@@ -2649,16 +2663,45 @@ CONFIG_METADATA_3 = {
2649
2663
  "provider_settings.enable": True,
2650
2664
  },
2651
2665
  },
2652
- "skills": {
2653
- "description": "Skills",
2666
+ # "file_extract": {
2667
+ # "description": "文档解析能力 [beta]",
2668
+ # "type": "object",
2669
+ # "items": {
2670
+ # "provider_settings.file_extract.enable": {
2671
+ # "description": "启用文档解析能力",
2672
+ # "type": "bool",
2673
+ # },
2674
+ # "provider_settings.file_extract.provider": {
2675
+ # "description": "文档解析提供商",
2676
+ # "type": "string",
2677
+ # "options": ["moonshotai"],
2678
+ # "condition": {
2679
+ # "provider_settings.file_extract.enable": True,
2680
+ # },
2681
+ # },
2682
+ # "provider_settings.file_extract.moonshotai_api_key": {
2683
+ # "description": "Moonshot AI API Key",
2684
+ # "type": "string",
2685
+ # "condition": {
2686
+ # "provider_settings.file_extract.provider": "moonshotai",
2687
+ # "provider_settings.file_extract.enable": True,
2688
+ # },
2689
+ # },
2690
+ # },
2691
+ # "condition": {
2692
+ # "provider_settings.agent_runner_type": "local",
2693
+ # "provider_settings.enable": True,
2694
+ # },
2695
+ # },
2696
+ "proactive_capability": {
2697
+ "description": "主动型 Agent",
2698
+ "hint": "https://docs.astrbot.app/use/proactive-agent.html",
2654
2699
  "type": "object",
2655
2700
  "items": {
2656
- "provider_settings.skills.runtime": {
2657
- "description": "Skill Runtime",
2658
- "type": "string",
2659
- "options": ["local", "sandbox"],
2660
- "labels": ["本地", "沙箱"],
2661
- "hint": "选择 Skills 运行环境。使用沙箱时需先启用沙箱环境。",
2701
+ "provider_settings.proactive_capability.add_cron_tools": {
2702
+ "description": "启用",
2703
+ "type": "bool",
2704
+ "hint": "启用后,将会传递给 Agent 相关工具来实现主动型 Agent。你可以告诉 AstrBot 未来某个时间要做的事情,它将被定时触发然后执行任务。",
2662
2705
  },
2663
2706
  },
2664
2707
  "condition": {
@@ -2667,6 +2710,7 @@ CONFIG_METADATA_3 = {
2667
2710
  },
2668
2711
  },
2669
2712
  "truncate_and_compress": {
2713
+ "hint": "",
2670
2714
  "description": "上下文管理策略",
2671
2715
  "type": "object",
2672
2716
  "items": {
@@ -3253,6 +3297,36 @@ CONFIG_METADATA_3_SYSTEM = {
3253
3297
  "hint": "控制台输出日志的级别。",
3254
3298
  "options": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
3255
3299
  },
3300
+ "log_file_enable": {
3301
+ "description": "启用文件日志",
3302
+ "type": "bool",
3303
+ "hint": "开启后会将日志写入指定文件。",
3304
+ },
3305
+ "log_file_path": {
3306
+ "description": "日志文件路径",
3307
+ "type": "string",
3308
+ "hint": "相对路径以 data 目录为基准,例如 logs/astrbot.log;支持绝对路径。",
3309
+ },
3310
+ "log_file_max_mb": {
3311
+ "description": "日志文件大小上限 (MB)",
3312
+ "type": "int",
3313
+ "hint": "超过大小后自动轮转,默认 20MB。",
3314
+ },
3315
+ "trace_log_enable": {
3316
+ "description": "启用 Trace 文件日志",
3317
+ "type": "bool",
3318
+ "hint": "将 Trace 事件写入独立文件(不影响控制台输出)。",
3319
+ },
3320
+ "trace_log_path": {
3321
+ "description": "Trace 日志文件路径",
3322
+ "type": "string",
3323
+ "hint": "相对路径以 data 目录为基准,例如 logs/astrbot.trace.log;支持绝对路径。",
3324
+ },
3325
+ "trace_log_max_mb": {
3326
+ "description": "Trace 日志大小上限 (MB)",
3327
+ "type": "int",
3328
+ "hint": "超过大小后自动轮转,默认 20MB。",
3329
+ },
3256
3330
  "pip_install_arg": {
3257
3331
  "description": "pip 安装额外参数",
3258
3332
  "type": "string",
@@ -17,10 +17,11 @@ import traceback
17
17
  from asyncio import Queue
18
18
 
19
19
  from astrbot.api import logger, sp
20
- from astrbot.core import LogBroker
20
+ from astrbot.core import LogBroker, LogManager
21
21
  from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
22
22
  from astrbot.core.config.default import VERSION
23
23
  from astrbot.core.conversation_mgr import ConversationManager
24
+ from astrbot.core.cron import CronJobManager
24
25
  from astrbot.core.db import BaseDatabase
25
26
  from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager
26
27
  from astrbot.core.persona_mgr import PersonaManager
@@ -31,6 +32,7 @@ from astrbot.core.provider.manager import ProviderManager
31
32
  from astrbot.core.star import PluginManager
32
33
  from astrbot.core.star.context import Context
33
34
  from astrbot.core.star.star_handler import EventType, star_handlers_registry, star_map
35
+ from astrbot.core.subagent_orchestrator import SubAgentOrchestrator
34
36
  from astrbot.core.umop_config_router import UmopConfigRouter
35
37
  from astrbot.core.updator import AstrBotUpdator
36
38
  from astrbot.core.utils.llm_metadata import update_llm_metadata
@@ -53,6 +55,9 @@ class AstrBotCoreLifecycle:
53
55
  self.astrbot_config = astrbot_config # 初始化配置
54
56
  self.db = db # 初始化数据库
55
57
 
58
+ self.subagent_orchestrator: SubAgentOrchestrator | None = None
59
+ self.cron_manager: CronJobManager | None = None
60
+
56
61
  # 设置代理
57
62
  proxy_config = self.astrbot_config.get("http_proxy", "")
58
63
  if proxy_config != "":
@@ -72,6 +77,24 @@ class AstrBotCoreLifecycle:
72
77
  del os.environ["no_proxy"]
73
78
  logger.debug("HTTP proxy cleared")
74
79
 
80
+ async def _init_or_reload_subagent_orchestrator(self) -> None:
81
+ """Create (if needed) and reload the subagent orchestrator from config.
82
+
83
+ This keeps lifecycle wiring in one place while allowing the orchestrator
84
+ to manage enable/disable and tool registration details.
85
+ """
86
+ try:
87
+ if self.subagent_orchestrator is None:
88
+ self.subagent_orchestrator = SubAgentOrchestrator(
89
+ self.provider_manager.llm_tools,
90
+ self.persona_mgr,
91
+ )
92
+ await self.subagent_orchestrator.reload_from_config(
93
+ self.astrbot_config.get("subagent_orchestrator", {}),
94
+ )
95
+ except Exception as e:
96
+ logger.error(f"Subagent orchestrator init failed: {e}", exc_info=True)
97
+
75
98
  async def initialize(self) -> None:
76
99
  """初始化 AstrBot 核心生命周期管理类.
77
100
 
@@ -80,9 +103,13 @@ class AstrBotCoreLifecycle:
80
103
  # 初始化日志代理
81
104
  logger.info("AstrBot v" + VERSION)
82
105
  if os.environ.get("TESTING", ""):
83
- logger.setLevel("DEBUG") # 测试模式下设置日志级别为 DEBUG
106
+ LogManager.configure_logger(
107
+ logger, self.astrbot_config, override_level="DEBUG"
108
+ )
109
+ LogManager.configure_trace_logger(self.astrbot_config)
84
110
  else:
85
- logger.setLevel(self.astrbot_config["log_level"]) # 设置日志级别
111
+ LogManager.configure_logger(logger, self.astrbot_config)
112
+ LogManager.configure_trace_logger(self.astrbot_config)
86
113
 
87
114
  await self.db.initialize()
88
115
 
@@ -137,6 +164,12 @@ class AstrBotCoreLifecycle:
137
164
  # 初始化知识库管理器
138
165
  self.kb_manager = KnowledgeBaseManager(self.provider_manager)
139
166
 
167
+ # 初始化 CronJob 管理器
168
+ self.cron_manager = CronJobManager(self.db)
169
+
170
+ # Dynamic subagents (handoff tools) from config.
171
+ await self._init_or_reload_subagent_orchestrator()
172
+
140
173
  # 初始化提供给插件的上下文
141
174
  self.star_context = Context(
142
175
  self.event_queue,
@@ -149,6 +182,8 @@ class AstrBotCoreLifecycle:
149
182
  self.persona_mgr,
150
183
  self.astrbot_config_mgr,
151
184
  self.kb_manager,
185
+ self.cron_manager,
186
+ self.subagent_orchestrator,
152
187
  )
153
188
 
154
189
  # 初始化插件管理器
@@ -197,13 +232,21 @@ class AstrBotCoreLifecycle:
197
232
  self.event_bus.dispatch(),
198
233
  name="event_bus",
199
234
  )
235
+ cron_task = None
236
+ if self.cron_manager:
237
+ cron_task = asyncio.create_task(
238
+ self.cron_manager.start(self.star_context),
239
+ name="cron_manager",
240
+ )
200
241
 
201
242
  # 把插件中注册的所有协程函数注册到事件总线中并执行
202
243
  extra_tasks = []
203
244
  for task in self.star_context._register_tasks:
204
245
  extra_tasks.append(asyncio.create_task(task, name=task.__name__)) # type: ignore
205
246
 
206
- tasks_ = [event_bus_task, *extra_tasks]
247
+ tasks_ = [event_bus_task, *(extra_tasks if extra_tasks else [])]
248
+ if cron_task:
249
+ tasks_.append(cron_task)
207
250
  for task in tasks_:
208
251
  self.curr_tasks.append(
209
252
  asyncio.create_task(self._task_wrapper(task), name=task.get_name()),
@@ -259,6 +302,9 @@ class AstrBotCoreLifecycle:
259
302
  for task in self.curr_tasks:
260
303
  task.cancel()
261
304
 
305
+ if self.cron_manager:
306
+ await self.cron_manager.shutdown()
307
+
262
308
  for plugin in self.plugin_manager.context.get_all_stars():
263
309
  try:
264
310
  await self.plugin_manager._terminate_plugin(plugin)
@@ -0,0 +1,3 @@
1
+ from .manager import CronJobManager
2
+
3
+ __all__ = ["CronJobManager"]
@@ -0,0 +1,67 @@
1
+ import time
2
+ import uuid
3
+ from typing import Any
4
+
5
+ from astrbot.core.message.components import Plain
6
+ from astrbot.core.message.message_event_result import MessageChain
7
+ from astrbot.core.platform.astr_message_event import AstrMessageEvent
8
+ from astrbot.core.platform.astrbot_message import AstrBotMessage, MessageMember
9
+ from astrbot.core.platform.message_session import MessageSession
10
+ from astrbot.core.platform.message_type import MessageType
11
+ from astrbot.core.platform.platform_metadata import PlatformMetadata
12
+
13
+
14
+ class CronMessageEvent(AstrMessageEvent):
15
+ """Synthetic event used when a cron job triggers the main agent loop."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ context,
21
+ session: MessageSession,
22
+ message: str,
23
+ sender_id: str = "astrbot",
24
+ sender_name: str = "Scheduler",
25
+ extras: dict[str, Any] | None = None,
26
+ message_type: MessageType = MessageType.FRIEND_MESSAGE,
27
+ ):
28
+ platform_meta = PlatformMetadata(
29
+ name="cron",
30
+ description="CronJob",
31
+ id=session.platform_id,
32
+ )
33
+
34
+ msg_obj = AstrBotMessage()
35
+ msg_obj.type = message_type
36
+ msg_obj.self_id = sender_id
37
+ msg_obj.session_id = session.session_id
38
+ msg_obj.message_id = uuid.uuid4().hex
39
+ msg_obj.sender = MessageMember(user_id=session.session_id, nickname=sender_name)
40
+ msg_obj.message = [Plain(message)]
41
+ msg_obj.message_str = message
42
+ msg_obj.raw_message = message
43
+ msg_obj.timestamp = int(time.time())
44
+
45
+ super().__init__(message, msg_obj, platform_meta, session.session_id)
46
+
47
+ # Ensure we use the original session for sending messages
48
+ self.session = session
49
+ self.context_obj = context
50
+ self.is_at_or_wake_command = True
51
+ self.is_wake = True
52
+
53
+ if extras:
54
+ self._extras.update(extras)
55
+
56
+ async def send(self, message: MessageChain):
57
+ if message is None:
58
+ return
59
+ await self.context_obj.send_message(self.session, message)
60
+ await super().send(message)
61
+
62
+ async def send_streaming(self, generator, use_fallback: bool = False):
63
+ async for chain in generator:
64
+ await self.send(chain)
65
+
66
+
67
+ __all__ = ["CronMessageEvent"]