- 子智能体消息渲染为左侧对话气泡 - 多智能体通知细分为进度/完成/提问三类 - 修复多智能体会话存储到 mutiagents 目录并迁移数据 - 修复 token-statistics API 支持多智能体会话 - 修复 ma_debug 未定义报错 - 移除 .message-block content-visibility 修复滚动抖动
144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""把误存到常规对话目录的多智能体会话迁移到 mutiagents/conversations/。
|
|
|
|
用法:
|
|
python3 scripts/migrate_multi_agent_conversations.py [--dry-run]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import shutil
|
|
import sys
|
|
import time
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
# 加载项目配置以确定数据目录
|
|
project_root = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(project_root))
|
|
from config import DATA_DIR, IS_HOST_MODE
|
|
from modules.multi_agent.role_store import DEFAULT_MUTIAGENTS_DIR
|
|
|
|
|
|
def load_json(path: Path) -> dict:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def save_json(path: Path, data: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = path.with_suffix(f".tmp.{int(time.time() * 1000)}")
|
|
tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
shutil.move(str(tmp), str(path))
|
|
|
|
|
|
def migrate(dry_run: bool = False) -> dict:
|
|
regular_dir = Path(DATA_DIR) / "conversations" / "workspace"
|
|
ma_dir = Path(DEFAULT_MUTIAGENTS_DIR) / "conversations" / "workspace"
|
|
|
|
regular_index_path = regular_dir / "index.json"
|
|
ma_index_path = ma_dir / "index.json"
|
|
|
|
regular_index = load_json(regular_index_path) if regular_index_path.exists() else {}
|
|
ma_index = load_json(ma_index_path) if ma_index_path.exists() else {}
|
|
|
|
moved = []
|
|
skipped = []
|
|
errors = []
|
|
|
|
for f in sorted(regular_dir.glob("conv_*.json")):
|
|
if f.name == "index.json":
|
|
continue
|
|
try:
|
|
data = load_json(f)
|
|
except Exception as exc:
|
|
errors.append({"file": str(f), "error": f"读取失败: {exc}"})
|
|
continue
|
|
|
|
meta = data.get("metadata", {}) or {}
|
|
if not meta.get("multi_agent_mode"):
|
|
skipped.append(f.name)
|
|
continue
|
|
|
|
conv_id = data.get("id") or f.stem
|
|
target_file = ma_dir / f.name
|
|
|
|
# 目标已存在时跳过,避免覆盖
|
|
if target_file.exists():
|
|
errors.append({"file": str(f), "error": f"目标已存在: {target_file}"})
|
|
continue
|
|
|
|
if dry_run:
|
|
moved.append({"id": conv_id, "file": f.name, "target": str(target_file)})
|
|
continue
|
|
|
|
try:
|
|
# 移动文件
|
|
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.move(str(f), str(target_file))
|
|
|
|
# 更新索引
|
|
if conv_id in regular_index:
|
|
ma_index[conv_id] = regular_index.pop(conv_id)
|
|
else:
|
|
# 从文件重建索引条目
|
|
ma_index[conv_id] = {
|
|
"title": data.get("title") or "未命名对话",
|
|
"created_at": data.get("created_at"),
|
|
"updated_at": data.get("updated_at"),
|
|
"project_path": meta.get("project_path"),
|
|
"project_relative_path": meta.get("project_relative_path"),
|
|
"thinking_mode": meta.get("thinking_mode", False),
|
|
"run_mode": meta.get("run_mode") or ("thinking" if meta.get("thinking_mode") else "fast"),
|
|
"model_key": meta.get("model_key"),
|
|
"has_images": meta.get("has_images", False),
|
|
"has_videos": meta.get("has_videos", False),
|
|
"total_messages": meta.get("total_messages", 0),
|
|
"total_tools": meta.get("total_tools", 0),
|
|
"status": meta.get("status", "active"),
|
|
"multi_agent_mode": True,
|
|
}
|
|
|
|
moved.append({"id": conv_id, "file": f.name, "target": str(target_file)})
|
|
except Exception as exc:
|
|
errors.append({"file": str(f), "error": f"迁移失败: {exc}"})
|
|
|
|
if not dry_run:
|
|
# 保存索引
|
|
save_json(regular_index_path, regular_index)
|
|
save_json(ma_index_path, ma_index)
|
|
|
|
return {
|
|
"dry_run": dry_run,
|
|
"regular_dir": str(regular_dir),
|
|
"ma_dir": str(ma_dir),
|
|
"moved": moved,
|
|
"skipped_count": len(skipped),
|
|
"errors": errors,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="迁移多智能体会话到 mutiagents 目录")
|
|
parser.add_argument("--dry-run", action="store_true", help="只预览,不实际移动")
|
|
args = parser.parse_args()
|
|
|
|
result = migrate(dry_run=args.dry_run)
|
|
|
|
print(f"常规目录: {result['regular_dir']}")
|
|
print(f"目标目录: {result['ma_dir']}")
|
|
print(f"模式: {'预览' if args.dry_run else '实际迁移'}")
|
|
print(f"将迁移: {len(result['moved'])} 个对话")
|
|
print(f"跳过(非多智能体): {result['skipped_count']} 个")
|
|
print(f"错误: {len(result['errors'])}")
|
|
|
|
for item in result["moved"]:
|
|
print(f" → {item['id']} -> {item.get('target', item['file'])}")
|
|
|
|
for err in result["errors"]:
|
|
print(f" ⚠ {err['file']}: {err['error']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|