-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathplugin.py
More file actions
618 lines (536 loc) · 25.2 KB
/
Copy pathplugin.py
File metadata and controls
618 lines (536 loc) · 25.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
"""麦麦自主规划插件 v4 - 主入口
完整 7 个组件外壳,业务逻辑下沉到 ``services/``。
组件总览:
- 4 个 ``@Tool`` :manage_goal_v4 / get_planning_status_v4 / generate_schedule_v4 / apply_schedule_v4
- 1 个 ``@Command`` :planning_v4 (``/plan`` 或 ``/规划``)
- 1 个 ``@EventHandler`` :autonomous_planner_v4 (ON_START)
- 1 个 ``@HookHandler`` :schedule_inject_v4 (maisaka.planner.before_request) ⭐ 注入入口
"""
from collections.abc import Mapping
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Tuple
import asyncio
import logging
from maibot_sdk import API, Command, EventHandler, HookHandler, MaiBotPlugin, PluginConfigBase, Tool
from maibot_sdk.types import EventType, HookMode, HookOrder, ToolParameterInfo, ToolParamType
from .config_models import AutonomousPlanningV4Config
from .services import CleanupService, CommandService, InjectService, ProactiveService, ToolsService
logger = logging.getLogger(__name__)
# ============================================================
# v4.0 → v4.1 配置迁移:把 [autonomous_planning.schedule.*] 搬到顶层 [schedule.*]
# ============================================================
_SCHEDULE_SUBKEY = "schedule"
_INJECT_SUBKEY = "inject"
def _migrate_v40_to_v41(raw: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
"""把旧 ``[autonomous_planning.schedule.xxx]`` 字段搬到顶层 ``[schedule.xxx]``。
SDK 只展开顶层 PluginConfigBase 子段为 UI section,旧版的三级嵌套结构
会让 schedule / inject 段在 WebUI 中不可见。本迁移把它们提到顶层,
并保留 ``[autonomous_planning]`` 段下的清理参数原位。
Args:
raw: 用户提供的原始配置字典。
Returns:
(新配置字典, 是否产生了迁移)。
"""
if not isinstance(raw, dict):
return raw, False
ap = raw.get("autonomous_planning")
if not isinstance(ap, dict):
return raw, False
legacy_schedule = ap.get(_SCHEDULE_SUBKEY)
if not isinstance(legacy_schedule, dict):
return raw, False
cfg = dict(raw)
new_ap = dict(ap)
new_ap.pop(_SCHEDULE_SUBKEY, None)
cfg["autonomous_planning"] = new_ap
# 把 schedule.inject 拆出来作为顶层 [inject]
legacy_schedule = dict(legacy_schedule)
legacy_inject = legacy_schedule.pop(_INJECT_SUBKEY, None)
# 顶层 [schedule] 合并(保留用户在新位置自定义的字段,冲突时优先新位置)
top_schedule = dict(cfg.get(_SCHEDULE_SUBKEY) or {})
schedule_conflicts: List[str] = []
for k, v in legacy_schedule.items():
if k in top_schedule and top_schedule[k] != v:
schedule_conflicts.append(k)
else:
top_schedule.setdefault(k, v)
cfg[_SCHEDULE_SUBKEY] = top_schedule
inject_conflicts: List[str] = []
if isinstance(legacy_inject, dict):
top_inject = dict(cfg.get(_INJECT_SUBKEY) or {})
for k, v in legacy_inject.items():
if k in top_inject and top_inject[k] != v:
inject_conflicts.append(k)
else:
top_inject.setdefault(k, v)
cfg[_INJECT_SUBKEY] = top_inject
# 当新旧位置同字段值不一致时,明确告诉用户保留了新位置值(避免静默丢失旧设置)
if schedule_conflicts:
logger.warning(
"v4.0→4.1 配置迁移:schedule 段下列字段在新旧位置都存在但值不同,"
"已保留新位置 [schedule.X] 的值,旧位置 [autonomous_planning.schedule.X] 被丢弃:%s",
schedule_conflicts,
)
if inject_conflicts:
logger.warning(
"v4.0→4.1 配置迁移:inject 段下列字段在新旧位置都存在但值不同,"
"已保留新位置 [inject.X] 的值,旧位置 [autonomous_planning.schedule.inject.X] 被丢弃:%s",
inject_conflicts,
)
return cfg, True
def _downgrade_traditional_inject_mode(raw: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
"""v4.1.1+:``inject_mode='traditional'`` 已移除,自动降级为 ``smart``。
避免 SDK 的 ``Literal["smart", "rule"]`` 校验拒绝旧用户的 config.toml。
traditional 模式的语义(固定模板)已被 smart 模式的关键词预判 + LLM
自判断覆盖,无功能损失。
Args:
raw: 用户提供的配置字典(可能已经过 ``_migrate_v40_to_v41``)。
Returns:
(新配置字典, 是否产生了降级)。
"""
if not isinstance(raw, dict):
return raw, False
inject_section = raw.get(_INJECT_SUBKEY)
if not isinstance(inject_section, dict):
return raw, False
if str(inject_section.get("inject_mode") or "").strip() != "traditional":
return raw, False
new_inject = dict(inject_section)
new_inject["inject_mode"] = "smart"
cfg = dict(raw)
cfg[_INJECT_SUBKEY] = new_inject
logger.warning(
"inject_mode='traditional' 在 v4.1.1+ 已移除(语义被 smart 模式覆盖),"
"已自动降级为 'smart'"
)
return cfg, True
class AutonomousPlanningPluginV4(MaiBotPlugin):
"""麦麦自主规划插件 v4"""
config_model: ClassVar[type[PluginConfigBase]] = AutonomousPlanningV4Config
def __init__(self) -> None:
"""初始化插件基础字段,service 实例延迟到 ``on_load`` 创建。"""
super().__init__()
self._plugin_root: Path = Path(__file__).resolve().parent
self._tools_svc: ToolsService | None = None
self._cmd_svc: CommandService | None = None
self._inject_svc: InjectService | None = None
self._cleanup_svc: CleanupService | None = None
self._proactive_svc: ProactiveService | None = None
self._bg_tasks: List[asyncio.Task] = []
# v4 新增:bot 全局配置缓存(on_load 时一次性拉取)
self._bot_profile: Dict[str, str] = {}
# ============================================================
# 配置迁移 Hook(SDK 在校验前调用)
# ============================================================
def normalize_plugin_config(
self,
config_data: Mapping[str, Any] | None,
) -> tuple[dict[str, Any], bool]:
"""v4.0 → v4.1 配置迁移 + traditional 模式降级 + 委托 SDK 默认归一化。
SDK 会在 config_data 与默认配置之间 merge,且 extra="ignore" 会丢弃未声明字段。
所以这里必须**在 super 之前**把旧 ``[autonomous_planning.schedule.*]`` 搬到顶层,
并把 v4.1.1 前已移除的 ``inject_mode='traditional'`` 降级到 ``smart``。
"""
raw = dict(config_data) if isinstance(config_data, Mapping) else {}
migrated, did_migrate = _migrate_v40_to_v41(raw) if raw else (raw, False)
if did_migrate:
logger.info("检测到 v4.0 旧配置,已迁移 [autonomous_planning.schedule.*] 到顶层 [schedule.*] / [inject.*]")
# 单独的内部归一化(不依赖 v4.0→4.1 迁移触发)
migrated, did_downgrade = _downgrade_traditional_inject_mode(migrated)
normalized, default_changed = super().normalize_plugin_config(migrated)
return normalized, did_migrate or did_downgrade or default_changed
# ============================================================
# 生命周期
# ============================================================
async def on_load(self) -> None:
"""插件加载完成:建立数据目录、初始化 services、启动后台任务。"""
# 仅当插件被启用时才初始化 service 与启动后台任务
if not self.config.plugin.enabled:
logger.warning("[v4] 插件已禁用(plugin.enabled=False),跳过初始化")
return
data_dir = self._plugin_root / "data"
data_dir.mkdir(exist_ok=True)
db_path = str(data_dir / "goals.db")
# v4 新增:预拉取 bot 全局配置(personality / bot.nickname 等)一次性缓存
# PromptBuilder 不再运行时调用 config_api.get_global_config
self._bot_profile = await self._prefetch_bot_profile()
# 初始化 services(依赖注入:plugin 自身 + 数据库路径)
self._tools_svc = ToolsService(self, db_path=db_path)
self._cmd_svc = CommandService(self)
self._inject_svc = InjectService(self)
self._cleanup_svc = CleanupService(self)
self._proactive_svc = ProactiveService(self)
# 启动后台任务(清理循环 + 自动调度循环 + 注入缓存预热 + v4.4 主动行为循环)
self._bg_tasks.append(asyncio.create_task(self._cleanup_svc.run_cleanup_loop()))
self._bg_tasks.append(asyncio.create_task(self._cleanup_svc.run_scheduler_loop()))
self._bg_tasks.append(asyncio.create_task(self._inject_svc.preheat_cache()))
self._bg_tasks.append(asyncio.create_task(self._proactive_svc.run_loop()))
logger.info("[v4] 自主规划插件 v4 已加载,data_dir=%s", data_dir)
async def _prefetch_bot_profile(self) -> Dict[str, str]:
"""预拉取 bot 全局配置(人设、回复风格、兴趣、昵称)。
失败时返回空字典,PromptBuilder 会使用内置默认值。
Returns:
``{"personality": str, "reply_style": str, "interest": str, "bot_name": str}``
"""
profile: Dict[str, str] = {}
try:
personality = await self.ctx.config.get("personality.personality", "")
reply_style = await self.ctx.config.get("personality.reply_style", "")
interest = await self.ctx.config.get("personality.interest", "")
bot_name = await self.ctx.config.get("bot.nickname", "")
profile = {
"personality": str(personality or ""),
"reply_style": str(reply_style or ""),
"interest": str(interest or ""),
"bot_name": str(bot_name or ""),
}
logger.debug("[v4] bot_profile 已预拉取: %s", profile)
except Exception as exc: # noqa: BLE001
logger.warning("[v4] 预拉取 bot_profile 失败,使用空值: %s", exc)
return profile
# ============================================================
# 对外暴露:ScheduleGenerator 配置构建(单一来源)
# ============================================================
def build_schedule_config(self) -> Dict[str, Any]:
"""构建供 ``ScheduleGenerator`` 使用的 dict 配置。
统一所有 ScheduleGenerator 构造入口(``ToolsService`` /
``ScheduleAutoScheduler`` / ``CommandService``)的配置来源,避免出现
"部分入口漏带 bot_profile"等路径不一致问题(v4.4.4 修复的 bug 即源于此:
``auto_scheduler`` 原本用 ``self.plugin.config.schedule.model_dump()``
构造 config,而 pydantic 模型里不存在 ``bot_profile`` 字段,导致 prompt
生成时人设缺失)。
Returns:
包含 schedule 段全部强类型字段,以及插件层缓存的 ``bot_profile`` 的
可变 dict。调用方拿到后可临时覆盖 ``custom_prompt`` 等字段而不影响下次调用。
"""
cfg = self.config.schedule
return {
"use_multi_round": cfg.use_multi_round,
"max_rounds": cfg.max_rounds,
"quality_threshold": cfg.quality_threshold,
"min_activities": cfg.min_activities,
"max_activities": cfg.max_activities,
"enable_detailed_description": cfg.enable_detailed_description,
"min_description_length": cfg.min_description_length,
"max_description_length": cfg.max_description_length,
"max_tokens": cfg.max_tokens,
"custom_prompt": cfg.custom_prompt,
"timezone": cfg.timezone,
"llm_task_name": cfg.llm_task_name,
"recent_schedule_days": cfg.recent_schedule_days,
"bot_profile": dict(self._bot_profile) if self._bot_profile else {},
}
async def on_unload(self) -> None:
"""插件卸载:通知所有循环停止 → cancel 后台任务 → 等待退出 → 关闭数据库。"""
if self._cleanup_svc is not None:
await self._cleanup_svc.stop()
if self._proactive_svc is not None:
await self._proactive_svc.stop()
for task in self._bg_tasks:
if not task.done():
task.cancel()
# 等待全部任务退出(异常吞掉,避免阻塞卸载)
for task in self._bg_tasks:
try:
await task
except (asyncio.CancelledError, Exception) as exc: # noqa: BLE001
logger.debug("后台任务退出: %s", exc)
self._bg_tasks.clear()
# 释放 GoalManager 持有的 SQLite 线程本地连接池
from .planner.goal_manager import close_goal_manager
close_goal_manager()
logger.info("[v4] 自主规划插件 v4 已卸载")
async def on_config_update(self, scope: str, config_data: dict[str, Any], version: str) -> None:
"""处理配置热重载事件。
Args:
scope: 变更范围 (``self`` / ``bot`` / ``model``)
config_data: 当前范围最新配置数据
version: 配置版本号
"""
del config_data
logger.info("[v4] 配置热更新 scope=%s version=%s", scope, version)
# services 持有 plugin 引用,配置通过 self.config.xxx 动态读取,无需特殊处理
# ============================================================
# Tool 组件(4 个,对应 v3 的 4 个 BaseTool 子类)
# ============================================================
@Tool(
"manage_goal_v4",
description="管理长期目标,支持创建、查看、更新、暂停、恢复、完成、取消、删除目标",
parameters=[
ToolParameterInfo(
name="action",
param_type=ToolParamType.STRING,
description="操作类型: create / list / get / update / pause / resume / complete / cancel / delete",
required=True,
),
ToolParameterInfo(
name="goal_id",
param_type=ToolParamType.STRING,
description="目标 ID(除 create 和 list 外都需要)",
required=False,
),
ToolParameterInfo(
name="name",
param_type=ToolParamType.STRING,
description="目标名称(create 时必需)",
required=False,
),
ToolParameterInfo(
name="description",
param_type=ToolParamType.STRING,
description="目标描述(create 时必需)",
required=False,
),
ToolParameterInfo(
name="goal_type",
param_type=ToolParamType.STRING,
description="目标类型: health_check / social_maintenance / learn_topic / custom",
required=False,
),
ToolParameterInfo(
name="priority",
param_type=ToolParamType.STRING,
description="优先级: high / medium / low",
required=False,
),
ToolParameterInfo(
name="time_window",
param_type=ToolParamType.STRING,
description="时间窗口,格式 'HH:MM-HH:MM'(如 '09:00-10:30')",
required=False,
),
ToolParameterInfo(
name="deadline_hours",
param_type=ToolParamType.FLOAT,
description="截止时间(从现在开始的小时数)",
required=False,
),
ToolParameterInfo(
name="parameters",
param_type=ToolParamType.STRING,
description="目标参数(JSON 字符串)",
required=False,
),
],
)
async def handle_manage_goal(self, **kwargs: Any) -> Dict[str, Any]:
"""目标管理工具入口。"""
if self._tools_svc is None:
return {"type": "error", "content": "插件未启用"}
return await self._tools_svc.manage_goal(**kwargs)
@Tool(
"get_planning_status_v4",
description="查看今日日程安排,按时间顺序显示正在进行和即将到来的活动",
parameters=[
ToolParameterInfo(
name="detailed",
param_type=ToolParamType.BOOLEAN,
description="是否显示详细信息",
required=False,
),
],
)
async def handle_get_planning_status(self, **kwargs: Any) -> Dict[str, Any]:
"""规划状态查询工具入口。"""
if self._tools_svc is None:
return {"type": "error", "content": "插件未启用"}
return await self._tools_svc.get_planning_status(**kwargs)
@Tool(
"generate_schedule_v4",
description="自动生成并应用全局每日/每周/每月计划,使用 LLM 根据 bot 人设智能生成",
parameters=[
ToolParameterInfo(
name="schedule_type",
param_type=ToolParamType.STRING,
description="日程类型: daily / weekly / monthly",
required=True,
),
ToolParameterInfo(
name="auto_apply",
param_type=ToolParamType.BOOLEAN,
description="是否立即应用日程(默认 true)",
required=False,
),
],
)
async def handle_generate_schedule(self, **kwargs: Any) -> Dict[str, Any]:
"""日程生成工具入口。"""
if self._tools_svc is None:
return {"type": "error", "content": "插件未启用"}
return await self._tools_svc.generate_schedule(**kwargs)
@Tool(
"apply_schedule_v4",
description="应用之前生成的日程,将日程项转换为全局可执行的目标",
parameters=[
ToolParameterInfo(
name="schedule_data",
param_type=ToolParamType.STRING,
description="日程数据(从 generate_schedule 获取,JSON 字符串)",
required=True,
),
],
)
async def handle_apply_schedule(self, **kwargs: Any) -> Dict[str, Any]:
"""日程应用工具入口。"""
if self._tools_svc is None:
return {"type": "error", "content": "插件未启用"}
return await self._tools_svc.apply_schedule(**kwargs)
@Tool(
"update_schedule_v4",
description="根据自然语言请求维护日程:角色裁判决定接受/未来约定/拒绝;接受时调整今日日程,未来约定写入候选清单",
parameters=[
ToolParameterInfo(
name="description",
param_type=ToolParamType.STRING,
description="日程变更的自然语言描述(如\"下午两点一起学习\"、\"周末打游戏\")",
required=True,
),
],
)
async def handle_update_schedule(self, **kwargs: Any) -> Dict[str, Any]:
"""角色裁判式日程更新工具入口。"""
if self._tools_svc is None:
return {"type": "error", "content": "插件未启用"}
return await self._tools_svc.update_schedule(**kwargs)
# ============================================================
# Command 组件
# ============================================================
@Command(
"planning_v4",
description="日程规划系统管理命令(支持 /plan 与 /规划)",
pattern=r"(?P<planning_cmd>^/(plan|规划).*$)",
)
async def handle_planning_command(
self,
text: str = "",
stream_id: str = "",
user_id: str = "",
platform: str = "",
group_id: str = "",
matched_groups: Any = None,
**kwargs: Any,
) -> Tuple[bool, str, bool]:
"""``/plan`` 命令入口,转发给 CommandService。"""
del kwargs
if self._cmd_svc is None:
if stream_id:
await self.ctx.send.text("插件未启用", stream_id)
return False, "插件未启用", True
return await self._cmd_svc.execute(
text=text,
stream_id=stream_id,
user_id=user_id,
platform=platform,
group_id=group_id,
matched_groups=matched_groups if isinstance(matched_groups, dict) else {},
)
# ============================================================
# EventHandler 组件(ON_START 用作启动信号;POST_LLM 不再使用,见 POC_RESULT.md)
# ============================================================
@EventHandler(
"autonomous_planner_v4",
description="启动事件处理器:用于配合主程序生命周期发出启动日志",
event_type=EventType.ON_START,
intercept_message=False,
weight=10,
)
async def handle_on_start(self, **kwargs: Any) -> Tuple[bool, bool, Any, Any, Any]:
"""主程序 ON_START 事件回调。
所有后台任务已在 ``on_load`` 中启动,这里仅做日志确认。
"""
del kwargs
logger.info("[v4] 收到 ON_START 事件,后台任务运行中")
return True, True, None, None, None
# ============================================================
# API 组件(对外暴露,可被其他插件通过 ctx.api.call 调用)
# ============================================================
@API(
"get_current_activity",
description="返回当前时间段最新的日程活动快照(含描述、时间窗口、即将到来活动)",
version="1",
public=True,
)
async def api_get_current_activity(
self,
chat_id: str = "global",
**kwargs: Any,
) -> Dict[str, Any]:
"""对外 API:当前时间段最新的日程活动快照。
其他插件调用方式::
snapshot = await self.ctx.api.call(
"xuqian13.autonomous-planning-plugin-v4.get_current_activity",
chat_id="global",
)
if snapshot["has_activity"]:
print(snapshot["activity"]["name"]) # 例如 "睡前刷手机"
Args:
chat_id: 可选的会话 ID 过滤;默认 ``global``(与日程注入一致)。
**kwargs: 预留扩展,当前未使用。
Returns:
dict: 见 ``InjectService.get_current_activity_snapshot`` 的返回结构。
"""
del kwargs
if self._inject_svc is None:
return {
"has_activity": False,
"activity": None,
"next_activities": [],
"as_of": "",
"timezone": "",
"error": "plugin_not_initialized",
}
return await self._inject_svc.get_current_activity_snapshot(chat_id or "global")
# ============================================================
# HookHandler 组件(v4 注入入口,替代 v3 的 POST_LLM)
# ============================================================
@HookHandler(
"maisaka.planner.before_request",
name="schedule_inject_v4",
description="在 Maisaka 向 LLM 发起规划请求前注入当前日程信息",
mode=HookMode.BLOCKING,
order=HookOrder.NORMAL,
)
async def handle_inject_schedule(
self,
messages: List[Dict[str, Any]] | None = None,
session_id: str = "",
**kwargs: Any,
) -> Dict[str, Any]:
"""日程注入 Hook 入口,转发给 InjectService。"""
if self._inject_svc is None or not self.config.schedule.inject_schedule:
return {"action": "continue"}
return await self._inject_svc.inject_into_planner_messages(
messages=messages or [],
session_id=session_id,
**kwargs,
)
# v4.4.0 恢复:v4.3.1 误删此 HookHandler,但主程序 5/21 已通过 commit 478256f2
# (feat: 支持回复器 Hook 指定模型)补上 ``maisaka.replyer.before_request`` hook。
# 现在该 hook 实际可用,恢复 replyer 阶段注入。
@HookHandler(
"maisaka.replyer.before_request",
name="schedule_inject_replyer_v4",
description="在 Maisaka replyer 调 LLM 前把当前活动注入到 extra_prompt(突出活人感,不要主动提及)",
mode=HookMode.BLOCKING,
order=HookOrder.NORMAL,
)
async def handle_inject_replyer(
self,
session_id: str = "",
attempt: int = 1,
**kwargs: Any,
) -> Dict[str, Any]:
"""replyer 注入 Hook 入口,转发给 InjectService。"""
if self._inject_svc is None or not self.config.schedule.inject_into_replyer:
return {"action": "continue"}
return await self._inject_svc.inject_into_replyer_extra_prompt(
session_id=session_id,
attempt=attempt,
**kwargs,
)
def create_plugin() -> AutonomousPlanningPluginV4:
"""创建 v4 插件实例(Runner 加载入口)。
Returns:
AutonomousPlanningPluginV4: 新的 v4 插件实例
"""
return AutonomousPlanningPluginV4()