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.
- astrbot/builtin_stars/astrbot/main.py +0 -5
- astrbot/cli/__init__.py +1 -1
- astrbot/core/__init__.py +2 -0
- astrbot/core/agent/agent.py +2 -1
- astrbot/core/agent/handoff.py +14 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +14 -1
- astrbot/core/agent/tool.py +5 -0
- astrbot/core/astr_agent_run_util.py +21 -3
- astrbot/core/astr_agent_tool_exec.py +178 -3
- astrbot/core/astr_main_agent.py +980 -0
- astrbot/core/astr_main_agent_resources.py +453 -0
- astrbot/core/computer/computer_client.py +10 -1
- astrbot/core/computer/tools/fs.py +22 -14
- astrbot/core/config/default.py +132 -58
- astrbot/core/core_lifecycle.py +50 -4
- astrbot/core/cron/__init__.py +3 -0
- astrbot/core/cron/events.py +67 -0
- astrbot/core/cron/manager.py +376 -0
- astrbot/core/db/__init__.py +60 -0
- astrbot/core/db/po.py +31 -0
- astrbot/core/db/sqlite.py +120 -0
- astrbot/core/log.py +189 -1
- astrbot/core/message/message_event_result.py +21 -3
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +130 -580
- astrbot/core/platform/astr_message_event.py +14 -0
- astrbot/core/platform/platform.py +9 -0
- astrbot/core/platform/platform_metadata.py +2 -0
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +1 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -0
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +1 -0
- astrbot/core/platform/sources/webchat/webchat_adapter.py +1 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +1 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +1 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +1 -0
- astrbot/core/provider/entities.py +1 -1
- astrbot/core/skills/skill_manager.py +9 -8
- astrbot/core/star/context.py +8 -0
- astrbot/core/star/filter/custom_filter.py +3 -3
- astrbot/core/star/register/star_handler.py +1 -1
- astrbot/core/subagent_orchestrator.py +96 -0
- astrbot/core/tools/cron_tools.py +174 -0
- astrbot/core/utils/history_saver.py +31 -0
- astrbot/core/utils/trace.py +77 -0
- astrbot/dashboard/routes/__init__.py +4 -0
- astrbot/dashboard/routes/cron.py +174 -0
- astrbot/dashboard/routes/log.py +36 -0
- astrbot/dashboard/routes/plugin.py +11 -0
- astrbot/dashboard/routes/skills.py +12 -37
- astrbot/dashboard/routes/subagent.py +117 -0
- astrbot/dashboard/routes/tools.py +41 -14
- astrbot/dashboard/server.py +3 -0
- {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/METADATA +21 -2
- {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/RECORD +56 -49
- astrbot/builtin_stars/astrbot/process_llm_request.py +0 -300
- astrbot/builtin_stars/reminder/main.py +0 -266
- astrbot/builtin_stars/reminder/metadata.yaml +0 -4
- astrbot/core/pipeline/process_stage/utils.py +0 -219
- {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/WHEEL +0 -0
- {astrbot-4.13.1.dist-info → astrbot-4.14.0.dist-info}/entry_points.txt +0 -0
- {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 =
|
|
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
|
-
|
|
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 消息段中。"""
|