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
astrbot/core/log.py CHANGED
@@ -27,13 +27,15 @@ import sys
27
27
  import time
28
28
  from asyncio import Queue
29
29
  from collections import deque
30
+ from logging.handlers import RotatingFileHandler
30
31
 
31
32
  import colorlog
32
33
 
33
34
  from astrbot.core.config.default import VERSION
35
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
34
36
 
35
37
  # 日志缓存大小
36
- CACHED_SIZE = 200
38
+ CACHED_SIZE = 500
37
39
  # 日志颜色配置
38
40
  log_color_config = {
39
41
  "DEBUG": "green",
@@ -163,6 +165,9 @@ class LogManager:
163
165
  提供了获取默认日志记录器logger和设置队列处理器的方法
164
166
  """
165
167
 
168
+ _FILE_HANDLER_FLAG = "_astrbot_file_handler"
169
+ _TRACE_FILE_HANDLER_FLAG = "_astrbot_trace_file_handler"
170
+
166
171
  @classmethod
167
172
  def GetLogger(cls, log_name: str = "default"):
168
173
  """获取指定名称的日志记录器logger
@@ -266,3 +271,186 @@ class LogManager:
266
271
  ),
267
272
  )
268
273
  logger.addHandler(handler)
274
+
275
+ @classmethod
276
+ def _default_log_path(cls) -> str:
277
+ return os.path.join(get_astrbot_data_path(), "logs", "astrbot.log")
278
+
279
+ @classmethod
280
+ def _resolve_log_path(cls, configured_path: str | None) -> str:
281
+ if not configured_path:
282
+ return cls._default_log_path()
283
+ if os.path.isabs(configured_path):
284
+ return configured_path
285
+ return os.path.join(get_astrbot_data_path(), configured_path)
286
+
287
+ @classmethod
288
+ def _get_file_handlers(cls, logger: logging.Logger) -> list[logging.Handler]:
289
+ return [
290
+ handler
291
+ for handler in logger.handlers
292
+ if getattr(handler, cls._FILE_HANDLER_FLAG, False)
293
+ ]
294
+
295
+ @classmethod
296
+ def _get_trace_file_handlers(cls, logger: logging.Logger) -> list[logging.Handler]:
297
+ return [
298
+ handler
299
+ for handler in logger.handlers
300
+ if getattr(handler, cls._TRACE_FILE_HANDLER_FLAG, False)
301
+ ]
302
+
303
+ @classmethod
304
+ def _remove_file_handlers(cls, logger: logging.Logger):
305
+ for handler in cls._get_file_handlers(logger):
306
+ logger.removeHandler(handler)
307
+ try:
308
+ handler.close()
309
+ except Exception:
310
+ pass
311
+
312
+ @classmethod
313
+ def _remove_trace_file_handlers(cls, logger: logging.Logger):
314
+ for handler in cls._get_trace_file_handlers(logger):
315
+ logger.removeHandler(handler)
316
+ try:
317
+ handler.close()
318
+ except Exception:
319
+ pass
320
+
321
+ @classmethod
322
+ def _add_file_handler(
323
+ cls,
324
+ logger: logging.Logger,
325
+ file_path: str,
326
+ max_mb: int | None = None,
327
+ backup_count: int = 3,
328
+ trace: bool = False,
329
+ ):
330
+ os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True)
331
+ max_bytes = 0
332
+ if max_mb and max_mb > 0:
333
+ max_bytes = max_mb * 1024 * 1024
334
+ if max_bytes > 0:
335
+ file_handler = RotatingFileHandler(
336
+ file_path,
337
+ maxBytes=max_bytes,
338
+ backupCount=backup_count,
339
+ encoding="utf-8",
340
+ )
341
+ else:
342
+ file_handler = logging.FileHandler(file_path, encoding="utf-8")
343
+ file_handler.setLevel(logger.level)
344
+ if trace:
345
+ formatter = logging.Formatter(
346
+ "[%(asctime)s] %(message)s",
347
+ datefmt="%Y-%m-%d %H:%M:%S",
348
+ )
349
+ else:
350
+ formatter = logging.Formatter(
351
+ "[%(asctime)s] %(plugin_tag)s [%(short_levelname)s]%(astrbot_version_tag)s [%(filename)s:%(lineno)d]: %(message)s",
352
+ datefmt="%Y-%m-%d %H:%M:%S",
353
+ )
354
+ file_handler.setFormatter(formatter)
355
+ setattr(
356
+ file_handler,
357
+ cls._TRACE_FILE_HANDLER_FLAG if trace else cls._FILE_HANDLER_FLAG,
358
+ True,
359
+ )
360
+ logger.addHandler(file_handler)
361
+
362
+ @classmethod
363
+ def configure_logger(
364
+ cls,
365
+ logger: logging.Logger,
366
+ config: dict | None,
367
+ override_level: str | None = None,
368
+ ):
369
+ """根据配置设置日志级别和文件日志。
370
+
371
+ Args:
372
+ logger: 需要配置的 logger
373
+ config: 配置字典
374
+ override_level: 若提供,将覆盖配置中的日志级别
375
+ """
376
+ if not config:
377
+ return
378
+
379
+ level = override_level or config.get("log_level")
380
+ if level:
381
+ try:
382
+ logger.setLevel(level)
383
+ except Exception:
384
+ logger.setLevel(logging.INFO)
385
+
386
+ # 兼容旧版嵌套配置
387
+ if "log_file" in config:
388
+ file_conf = config.get("log_file") or {}
389
+ enable_file = bool(file_conf.get("enable", False))
390
+ file_path = file_conf.get("path")
391
+ max_mb = file_conf.get("max_mb")
392
+ else:
393
+ enable_file = bool(config.get("log_file_enable", False))
394
+ file_path = config.get("log_file_path")
395
+ max_mb = config.get("log_file_max_mb")
396
+
397
+ file_path = cls._resolve_log_path(file_path)
398
+
399
+ existing = cls._get_file_handlers(logger)
400
+ if not enable_file:
401
+ cls._remove_file_handlers(logger)
402
+ return
403
+
404
+ # 如果已有文件处理器且路径一致,则仅同步级别
405
+ if existing:
406
+ handler = existing[0]
407
+ base = getattr(handler, "baseFilename", "")
408
+ if base and os.path.abspath(base) == os.path.abspath(file_path):
409
+ handler.setLevel(logger.level)
410
+ return
411
+ cls._remove_file_handlers(logger)
412
+
413
+ cls._add_file_handler(logger, file_path, max_mb=max_mb)
414
+
415
+ @classmethod
416
+ def configure_trace_logger(cls, config: dict | None):
417
+ """为 trace 事件配置独立的文件日志,不向控制台输出。"""
418
+ if not config:
419
+ return
420
+
421
+ enable = bool(
422
+ config.get("trace_log_enable")
423
+ or (config.get("log_file", {}) or {}).get("trace_enable", False)
424
+ )
425
+ path = config.get("trace_log_path")
426
+ max_mb = config.get("trace_log_max_mb")
427
+ if "log_file" in config:
428
+ legacy = config.get("log_file") or {}
429
+ path = path or legacy.get("trace_path")
430
+ max_mb = max_mb or legacy.get("trace_max_mb")
431
+
432
+ if not enable:
433
+ trace_logger = logging.getLogger("astrbot.trace")
434
+ cls._remove_trace_file_handlers(trace_logger)
435
+ return
436
+
437
+ file_path = cls._resolve_log_path(path or "logs/astrbot.trace.log")
438
+ trace_logger = logging.getLogger("astrbot.trace")
439
+ trace_logger.setLevel(logging.INFO)
440
+ trace_logger.propagate = False
441
+
442
+ existing = cls._get_trace_file_handlers(trace_logger)
443
+ if existing:
444
+ handler = existing[0]
445
+ base = getattr(handler, "baseFilename", "")
446
+ if base and os.path.abspath(base) == os.path.abspath(file_path):
447
+ handler.setLevel(trace_logger.level)
448
+ return
449
+ cls._remove_trace_file_handlers(trace_logger)
450
+
451
+ cls._add_file_handler(
452
+ trace_logger,
453
+ file_path,
454
+ max_mb=max_mb,
455
+ trace=True,
456
+ )
@@ -9,6 +9,7 @@ from astrbot.core.message.components import (
9
9
  AtAll,
10
10
  BaseMessageComponent,
11
11
  Image,
12
+ Json,
12
13
  Plain,
13
14
  )
14
15
 
@@ -117,9 +118,26 @@ class MessageChain:
117
118
  self.use_t2i_ = use_t2i
118
119
  return self
119
120
 
120
- def get_plain_text(self) -> str:
121
- """获取纯文本消息。这个方法将获取 chain 中所有 Plain 组件的文本并拼接成一条消息。空格分隔。"""
122
- return " ".join([comp.text for comp in self.chain if isinstance(comp, Plain)])
121
+ def get_plain_text(self, with_other_comps_mark: bool = False) -> str:
122
+ """获取纯文本消息。这个方法将获取 chain 中所有 Plain 组件的文本并拼接成一条消息。空格分隔。
123
+
124
+ Args:
125
+ with_other_comps_mark (bool): 是否在纯文本中标记其他组件的位置
126
+ """
127
+ if not with_other_comps_mark:
128
+ return " ".join(
129
+ [comp.text for comp in self.chain if isinstance(comp, Plain)]
130
+ )
131
+ else:
132
+ texts = []
133
+ for comp in self.chain:
134
+ if isinstance(comp, Plain):
135
+ texts.append(comp.text)
136
+ elif isinstance(comp, Json):
137
+ texts.append(f"{comp.data}")
138
+ else:
139
+ texts.append(f"[{comp.__class__.__name__}]")
140
+ return " ".join(texts)
123
141
 
124
142
  def squash_plain(self):
125
143
  """将消息链中的所有 Plain 消息段聚合到第一个 Plain 消息段中。"""