feat(engine): 添加MCP连接管理和工具集成功能

- 集成MCP连接管理器,支持MCP服务器连接
- 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、
  PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、
  TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等
- 实现工具注册和装配功能
- 添加技能选择上下文参数
- 支持思考模式控制参数thinking_enabled

feat(coordinator): 重构任务执行计划器参数命名

- 将learning_candidate_enabled重命名为allow_candidate_generation
- 更新TeamGraphScheduler中的参数传递
- 修改LocalAgentRunner中的相关参数处理
- 更新README文档中的相应描述

refactor(context): 标准化工具调用参数格式

- 添加_json导入用于参数序列化
- 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷
- 修复工具调用中参数非字符串类型的序列化问题

refactor(session): 优化消息历史记录过滤逻辑

- 修改get_messages_as_conversation为基于运行状态过滤消息
- 排除未完成、失败或错误结束的运行记录
- 改进对话历史的可见性控制机制

fix(store): 修复FTS索引重建逻辑

- 添加异常处理防止FTS索引创建失败
- 实现_rebuild_fts_index方法重新构建全文搜索索引
- 优化索引触发器和表的维护流程
This commit is contained in:
2026-05-14 09:43:48 +08:00
parent 8a12c30141
commit 30ab74ffb2
149 changed files with 12293 additions and 2812 deletions

View File

@ -298,8 +298,29 @@ def test_skill_learning_service_generates_candidates_and_retire_draft(tmp_path:
ended_at=recent,
success=True,
finish_reason="stop",
feedback={"feedback_type": "satisfied"},
activated_skills=[],
task_id=f"task-new-{index}",
attempt_index=1,
validation_result={"accepted": True, "score": 0.9},
)
)
for index in range(2):
run_store.append_run_record(
RunRecord(
run_id=f"simple-chat-{index}",
session_id="session-simple",
task_text="你是谁",
started_at=recent,
ended_at=recent,
success=True,
finish_reason="stop",
feedback={},
activated_skills=[],
task_id=None,
attempt_index=None,
validation_result=None,
)
)
@ -329,8 +350,11 @@ def test_skill_learning_service_generates_candidates_and_retire_draft(tmp_path:
ended_at=recent,
success=True,
finish_reason="stop",
feedback={},
feedback={"feedback_type": "satisfied"},
activated_skills=receipts,
task_id=f"task-merge-{index}",
attempt_index=1,
validation_result={"accepted": True, "score": 0.9},
)
)
for receipt in receipts:
@ -382,6 +406,9 @@ def test_skill_learning_service_generates_candidates_and_retire_draft(tmp_path:
kinds = {candidate.kind for candidate in candidates}
assert {"revise_skill", "new_skill", "merge_skills", "retire_skill"} <= kinds
new_candidates = [candidate for candidate in candidates if candidate.kind == "new_skill"]
assert new_candidates
assert all("simple-chat" not in run_id for candidate in new_candidates for run_id in candidate.source_run_ids)
retire_candidate = next(candidate for candidate in candidates if candidate.kind == "retire_skill")
retire_draft = asyncio.run(
@ -396,6 +423,100 @@ def test_skill_learning_service_generates_candidates_and_retire_draft(tmp_path:
assert store.read_draft("svn-migration", retire_draft.draft_id) is not None
def test_skill_learning_service_generates_task_scoped_candidates(tmp_path: Path) -> None:
store = SkillSpecStore(tmp_path)
run_store = RunMemoryStore(tmp_path / "memory" / "runs")
learning_store = SkillLearningStore(tmp_path / "memory" / "skills")
service = SkillLearningService(
run_store=run_store,
learning_store=learning_store,
draft_service=DraftService(store),
evidence_selector=EvidenceSelector(run_store),
)
now = datetime.now(timezone.utc).isoformat()
receipt = _receipt(
run_id="task-run-1",
session_id="session-task",
skill_name="api-review",
skill_version="v0001",
activated_at=now,
)
run_store.append_run_record(
RunRecord(
run_id="task-run-1",
session_id="session-task",
task_id="task-1",
attempt_index=1,
task_text="Review API compatibility",
started_at=now,
ended_at=now,
success=True,
finish_reason="stop",
feedback={"feedback_type": "satisfied"},
activated_skills=[receipt],
validation_result={"accepted": True, "score": 0.9},
)
)
run_store.append_run_record(
RunRecord(
run_id="other-task-run",
session_id="session-other",
task_id="task-2",
attempt_index=1,
task_text="Review API compatibility",
started_at=now,
ended_at=now,
success=True,
finish_reason="stop",
feedback={"feedback_type": "satisfied"},
activated_skills=[],
validation_result={"accepted": True, "score": 0.9},
)
)
candidates = service.build_learning_candidates_for_task("task-1", trigger_run_id="task-run-1")
assert [candidate.candidate_id for candidate in candidates] == ["revise:api-review:v0001:task:task-1"]
assert candidates[0].source_run_ids == ["task-run-1"]
assert candidates[0].related_skill_names == ["api-review"]
assert candidates[0].evidence["task_id"] == "task-1"
def test_skill_learning_service_generates_new_skill_for_task_without_published_skills(tmp_path: Path) -> None:
store = SkillSpecStore(tmp_path)
run_store = RunMemoryStore(tmp_path / "memory" / "runs")
learning_store = SkillLearningStore(tmp_path / "memory" / "skills")
service = SkillLearningService(
run_store=run_store,
learning_store=learning_store,
draft_service=DraftService(store),
evidence_selector=EvidenceSelector(run_store),
)
now = datetime.now(timezone.utc).isoformat()
run_store.append_run_record(
RunRecord(
run_id="task-run-1",
session_id="session-task",
task_id="task-1",
attempt_index=1,
task_text="Generate migration checklist",
started_at=now,
ended_at=now,
success=True,
finish_reason="stop",
feedback={"feedback_type": "satisfied"},
activated_skills=[],
validation_result={"accepted": True, "score": 0.9},
)
)
candidates = service.build_learning_candidates_for_task("task-1", trigger_run_id="task-run-1")
assert [candidate.candidate_id for candidate in candidates] == ["new:task:task-1"]
assert candidates[0].kind == "new_skill"
assert candidates[0].source_run_ids == ["task-run-1"]
def test_agent_loop_records_skill_receipts_and_effects(tmp_path: Path) -> None:
skill = SkillContext(
name="docker-debug",
@ -446,7 +567,7 @@ def test_agent_loop_records_skill_receipts_and_effects(tmp_path: Path) -> None:
skill_effects = next(event for event in events if event.event_type == "skill_effects_snapshotted")
assert skill_effects.event_payload["run_record"]["activated_skills"][0]["skill_version"] == "v0007"
assert skill_effects.event_payload["skill_effects"][0]["skill_name"] == "docker-debug"
assert skill_effects.event_payload["learning_candidate_enabled"] is False
assert skill_effects.event_payload["candidate_generation_allowed"] is False
assert skill_effects.event_payload["learning_candidates"] == []
run_records = loaded.run_memory_store.list_runs()