- 集成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方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""File-backed run receipt store."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from .models import RunRecord, SkillEffectRecord
|
|
|
|
|
|
class RunMemoryStore:
|
|
def __init__(self, root: str | Path) -> None:
|
|
self.root = Path(root)
|
|
self.root.mkdir(parents=True, exist_ok=True)
|
|
self.runs_path = self.root / "runs.jsonl"
|
|
self.effects_path = self.root / "skill-effects.jsonl"
|
|
|
|
def append_run_record(self, record: RunRecord) -> None:
|
|
self._append_jsonl(self.runs_path, record.to_dict())
|
|
|
|
def update_run_record(self, run_id: str, **updates: object) -> RunRecord | None:
|
|
records = self.list_runs()
|
|
updated: RunRecord | None = None
|
|
for index, record in enumerate(records):
|
|
if record.run_id != run_id:
|
|
continue
|
|
payload = record.to_dict()
|
|
payload.update(updates)
|
|
updated = RunRecord.from_dict(payload)
|
|
records[index] = updated
|
|
break
|
|
if updated is None:
|
|
return None
|
|
self.runs_path.parent.mkdir(parents=True, exist_ok=True)
|
|
self.runs_path.write_text(
|
|
"".join(
|
|
json.dumps(record.to_dict(), ensure_ascii=False, sort_keys=True) + "\n"
|
|
for record in records
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
return updated
|
|
|
|
def append_skill_effect(self, effect: SkillEffectRecord) -> None:
|
|
self._append_jsonl(self.effects_path, effect.to_dict())
|
|
|
|
def update_skill_effects_for_run(self, run_id: str, **updates: object) -> list[SkillEffectRecord]:
|
|
effects = [SkillEffectRecord.from_dict(item) for item in self._read_jsonl(self.effects_path)]
|
|
updated: list[SkillEffectRecord] = []
|
|
for index, effect in enumerate(effects):
|
|
if effect.run_id != run_id:
|
|
continue
|
|
payload = effect.to_dict()
|
|
payload.update(updates)
|
|
next_effect = SkillEffectRecord.from_dict(payload)
|
|
effects[index] = next_effect
|
|
updated.append(next_effect)
|
|
if not updated:
|
|
return []
|
|
self.effects_path.parent.mkdir(parents=True, exist_ok=True)
|
|
self.effects_path.write_text(
|
|
"".join(
|
|
json.dumps(effect.to_dict(), ensure_ascii=False, sort_keys=True) + "\n"
|
|
for effect in effects
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
return updated
|
|
|
|
def list_runs(self) -> list[RunRecord]:
|
|
return [RunRecord.from_dict(item) for item in self._read_jsonl(self.runs_path)]
|
|
|
|
def list_runs_by_skill(self, skill_name: str, version: str | None = None, limit: int | None = None) -> list[RunRecord]:
|
|
results: list[RunRecord] = []
|
|
for record in self.list_runs():
|
|
matched = False
|
|
for receipt in record.activated_skills:
|
|
if receipt.skill_name != skill_name:
|
|
continue
|
|
if version is not None and receipt.skill_version != version:
|
|
continue
|
|
matched = True
|
|
break
|
|
if matched:
|
|
results.append(record)
|
|
if limit is not None:
|
|
return results[-limit:]
|
|
return results
|
|
|
|
def list_skill_effects(self, skill_name: str, version: str | None = None, limit: int | None = None) -> list[SkillEffectRecord]:
|
|
results: list[SkillEffectRecord] = []
|
|
for payload in self._read_jsonl(self.effects_path):
|
|
effect = SkillEffectRecord.from_dict(payload)
|
|
if effect.skill_name != skill_name:
|
|
continue
|
|
if version is not None and effect.skill_version != version:
|
|
continue
|
|
results.append(effect)
|
|
if limit is not None:
|
|
return results[-limit:]
|
|
return results
|
|
|
|
@staticmethod
|
|
def _append_jsonl(path: Path, payload: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
handle.write(json.dumps(payload, ensure_ascii=False, sort_keys=True) + "\n")
|
|
|
|
@staticmethod
|
|
def _read_jsonl(path: Path) -> list[dict]:
|
|
if not path.exists():
|
|
return []
|
|
results: list[dict] = []
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
cleaned = line.strip()
|
|
if not cleaned:
|
|
continue
|
|
payload = json.loads(cleaned)
|
|
if isinstance(payload, dict):
|
|
results.append(payload)
|
|
return results
|