myagent-ai 1.27.3 → 1.27.5
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.
- package/core/agent_storage.py +4 -4
- package/core/utils.py +18 -4
- package/package.json +1 -1
- package/web/api_server.py +95 -179
package/core/agent_storage.py
CHANGED
|
@@ -23,7 +23,7 @@ from pathlib import Path
|
|
|
23
23
|
from typing import Any, Dict, List, Optional
|
|
24
24
|
|
|
25
25
|
from core.logger import get_logger
|
|
26
|
-
from core.utils import generate_id
|
|
26
|
+
from core.utils import next_agent_id, generate_id
|
|
27
27
|
|
|
28
28
|
logger = get_logger("myagent.agent_storage")
|
|
29
29
|
|
|
@@ -243,7 +243,7 @@ class AgentStorage:
|
|
|
243
243
|
conn = self._get_conn()
|
|
244
244
|
now = _now_iso()
|
|
245
245
|
if not cfg.id:
|
|
246
|
-
cfg.id =
|
|
246
|
+
cfg.id = next_agent_id()
|
|
247
247
|
if not cfg.created_at:
|
|
248
248
|
cfg.created_at = now
|
|
249
249
|
if not cfg.updated_at:
|
|
@@ -433,8 +433,8 @@ class AgentStorage:
|
|
|
433
433
|
rel_path = cfg_path.parent.relative_to(agents_dir).as_posix()
|
|
434
434
|
|
|
435
435
|
# 补全缺失字段
|
|
436
|
-
if "id" not in raw or not raw["id"]
|
|
437
|
-
raw["id"] =
|
|
436
|
+
if "id" not in raw or not raw["id"]:
|
|
437
|
+
raw["id"] = next_agent_id()
|
|
438
438
|
if "created_at" not in raw or not raw["created_at"]:
|
|
439
439
|
raw["created_at"] = now
|
|
440
440
|
if "updated_at" not in raw or not raw["updated_at"]:
|
package/core/utils.py
CHANGED
|
@@ -10,6 +10,7 @@ import time
|
|
|
10
10
|
import re
|
|
11
11
|
from datetime import datetime, timezone
|
|
12
12
|
from zoneinfo import ZoneInfo
|
|
13
|
+
from pathlib import Path
|
|
13
14
|
from typing import Any, Dict, Optional, TypeVar
|
|
14
15
|
|
|
15
16
|
T = TypeVar("T")
|
|
@@ -42,12 +43,25 @@ def timestamp_ms() -> int:
|
|
|
42
43
|
return int(time.time() * 1000)
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
_AGENT_ID_SEQ = Path.home() / ".myagent" / "data" / ".agent_id_seq"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def next_agent_id() -> int:
|
|
50
|
+
"""自增数字 Agent ID (1, 2, 3, ...)"""
|
|
51
|
+
_AGENT_ID_SEQ.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
try:
|
|
53
|
+
cur = int(_AGENT_ID_SEQ.read_text().strip())
|
|
54
|
+
except (FileNotFoundError, ValueError):
|
|
55
|
+
cur = 0
|
|
56
|
+
cur += 1
|
|
57
|
+
_AGENT_ID_SEQ.write_text(str(cur))
|
|
58
|
+
return cur
|
|
59
|
+
|
|
60
|
+
|
|
45
61
|
def generate_id(prefix: str = "") -> str:
|
|
46
|
-
"""
|
|
62
|
+
"""生成唯一 ID"""
|
|
47
63
|
import random
|
|
48
|
-
|
|
49
|
-
rand = str(random.randint(10000, 99999))
|
|
50
|
-
uid = f"{ts}{rand}"
|
|
64
|
+
uid = uuid.uuid4().hex[:12]
|
|
51
65
|
return f"{prefix}_{uid}" if prefix else uid
|
|
52
66
|
|
|
53
67
|
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -14,6 +14,7 @@ from core.llm import Message
|
|
|
14
14
|
from config import ModelEntry, ChatPlatformConfig
|
|
15
15
|
import datetime
|
|
16
16
|
import uuid
|
|
17
|
+
from core.utils import next_agent_id
|
|
17
18
|
from web.tts_handler import synthesize, preprocess_for_tts, AVAILABLE_VOICES
|
|
18
19
|
|
|
19
20
|
logger = get_logger("myagent.api")
|
|
@@ -1818,8 +1819,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
1818
1819
|
if not message:
|
|
1819
1820
|
return web.json_response({"error": "message is required"}, status=400)
|
|
1820
1821
|
|
|
1821
|
-
agent_name = data.get("agent_name", "
|
|
1822
|
-
# 支持 path 格式 (如 "
|
|
1822
|
+
agent_name = data.get("agent_name", "1") or "1"
|
|
1823
|
+
# 支持 path 格式 (如 "3")
|
|
1823
1824
|
agent_path = data.get("agent_path", agent_name)
|
|
1824
1825
|
# 获取数字 agent_id
|
|
1825
1826
|
agent_id = self.core.memory.get_agent_id(agent_path)
|
|
@@ -1983,7 +1984,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
1983
1984
|
if not message and not user_images and not user_files:
|
|
1984
1985
|
return web.Response(text="data: " + json.dumps({"error": "message is required"}) + "\n\n", content_type="text/event-stream")
|
|
1985
1986
|
|
|
1986
|
-
agent_path = data.get("agent_path", data.get("agent_name", "
|
|
1987
|
+
agent_path = data.get("agent_path", data.get("agent_name", "1")) or "1"
|
|
1987
1988
|
# [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
|
|
1988
1989
|
session_id = data.get("session_id", "") or "web_default"
|
|
1989
1990
|
chat_mode = data.get("mode", "")
|
|
@@ -2374,7 +2375,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
2374
2375
|
if not message:
|
|
2375
2376
|
return web.json_response({"error": "message is required"}, status=400)
|
|
2376
2377
|
|
|
2377
|
-
agent_path = data.get("agent_path", "
|
|
2378
|
+
agent_path = data.get("agent_path", "1") or "1"
|
|
2378
2379
|
# [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
|
|
2379
2380
|
session_id = data.get("session_id", "web_default")
|
|
2380
2381
|
choice = data.get("choice", "queue") # "continue" (插入后继续) 或 "queue" (排队)
|
|
@@ -2422,7 +2423,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
2422
2423
|
if not raw_text:
|
|
2423
2424
|
return web.json_response({"error": "text is required"}, status=400)
|
|
2424
2425
|
|
|
2425
|
-
agent_path = data.get("agent_path", "
|
|
2426
|
+
agent_path = data.get("agent_path", "1") or "1"
|
|
2426
2427
|
session_id = data.get("session_id", "")
|
|
2427
2428
|
chat_mode = data.get("mode", "")
|
|
2428
2429
|
|
|
@@ -3070,7 +3071,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3070
3071
|
优先使用 session_id 查找(与聊天流存储键一致),
|
|
3071
3072
|
回退到 agent_path 查找(手动创建的任务)。
|
|
3072
3073
|
"""
|
|
3073
|
-
agent_path = request.query.get("agent", "
|
|
3074
|
+
agent_path = request.query.get("agent", "1")
|
|
3074
3075
|
session_id = request.query.get("session", "")
|
|
3075
3076
|
# 优先按 session_id 查找(聊天流生成的任务列表存储在此键下)
|
|
3076
3077
|
if session_id:
|
|
@@ -3084,7 +3085,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3084
3085
|
async def handle_update_task_plan(self, request):
|
|
3085
3086
|
"""PUT /api/task-plan - Overwrite entire task list."""
|
|
3086
3087
|
data = await request.json()
|
|
3087
|
-
agent_path = data.get("agent", "
|
|
3088
|
+
agent_path = data.get("agent", "1")
|
|
3088
3089
|
session_id = data.get("session", "")
|
|
3089
3090
|
tasks = data.get("tasks", [])
|
|
3090
3091
|
# 优先按 session_id 存储(与聊天流一致),回退到 agent_path
|
|
@@ -3095,7 +3096,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3095
3096
|
async def handle_add_task_item(self, request):
|
|
3096
3097
|
"""POST /api/task-plan - Add a new task item."""
|
|
3097
3098
|
data = await request.json()
|
|
3098
|
-
agent_path = data.get("agent", "
|
|
3099
|
+
agent_path = data.get("agent", "1")
|
|
3099
3100
|
session_id = data.get("session", "")
|
|
3100
3101
|
text = data.get("text", "").strip()
|
|
3101
3102
|
if not text:
|
|
@@ -3110,7 +3111,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3110
3111
|
async def handle_delete_task_item(self, request):
|
|
3111
3112
|
"""DELETE /api/task-plan/{idx} - Delete task by index."""
|
|
3112
3113
|
idx = int(request.match_info["idx"])
|
|
3113
|
-
agent_path = request.query.get("agent", "
|
|
3114
|
+
agent_path = request.query.get("agent", "1")
|
|
3114
3115
|
session_id = request.query.get("session", "")
|
|
3115
3116
|
# 优先按 session_id 查找,回退到 agent_path
|
|
3116
3117
|
store_key = session_id or agent_path
|
|
@@ -3156,32 +3157,29 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3156
3157
|
return web.json_response({"ok": True})
|
|
3157
3158
|
|
|
3158
3159
|
# --- Agents (层级体系) ---
|
|
3159
|
-
# 目录结构: agents/
|
|
3160
|
-
# agents/
|
|
3161
|
-
#
|
|
3162
|
-
# agent path = 相对于 agents/ 的路径, 如 "default", "coder", "coder/python-expert"
|
|
3160
|
+
# 目录结构: agents/1/{config.json, soul.md, ...}
|
|
3161
|
+
# agents/2/{config.json, soul.md, ...}
|
|
3162
|
+
# agent path = agent ID, 如 "1", "2", "3"
|
|
3163
3163
|
|
|
3164
3164
|
def _agents_dir(self):
|
|
3165
3165
|
d = self.core.config_mgr.data_dir / "agents"
|
|
3166
3166
|
d.mkdir(parents=True, exist_ok=True)
|
|
3167
3167
|
return d
|
|
3168
3168
|
|
|
3169
|
-
def _agent_dir(self,
|
|
3170
|
-
"""根据 agent
|
|
3171
|
-
return self._agents_dir() /
|
|
3169
|
+
def _agent_dir(self, aid: str) -> Path:
|
|
3170
|
+
"""根据 agent ID 返回目录 (aid 如 '1', '2')"""
|
|
3171
|
+
return self._agents_dir() / aid
|
|
3172
3172
|
|
|
3173
3173
|
def _ensure_default_agent(self):
|
|
3174
|
-
"""确保默认 agent
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
if (ad / "config.json").exists():
|
|
3178
|
-
self._migrate_agent_config("default", "全权Agent")
|
|
3179
|
-
# 迁移后再次检查(损坏文件可能已被删除)
|
|
3174
|
+
"""确保默认 agent 存在(ID=1,名为「全权Agent」,目录名=1)"""
|
|
3175
|
+
aid = "1"
|
|
3176
|
+
ad = self._agent_dir(aid)
|
|
3180
3177
|
if not (ad / "config.json").exists():
|
|
3181
3178
|
ad.mkdir(parents=True, exist_ok=True)
|
|
3182
3179
|
now = _now_iso()
|
|
3183
3180
|
cfg = {
|
|
3184
|
-
"id":
|
|
3181
|
+
"id": 1,
|
|
3182
|
+
"path": aid,
|
|
3185
3183
|
"name": "全权Agent",
|
|
3186
3184
|
"description": "全权Agent - 拥有完整权限的本地助手",
|
|
3187
3185
|
"avatar_color": _agent_color("全权Agent"),
|
|
@@ -3202,49 +3200,26 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3202
3200
|
if not (ad / fn).exists():
|
|
3203
3201
|
(ad / fn).write_text(default, encoding="utf-8")
|
|
3204
3202
|
logger.info("已创建默认 Agent (全权Agent)")
|
|
3205
|
-
else:
|
|
3206
|
-
# 即使已存在也同步其基本配置(如系统提示词可能更新)
|
|
3207
|
-
self._migrate_agent_config("default")
|
|
3208
3203
|
|
|
3209
3204
|
self._ensure_config_helper()
|
|
3210
3205
|
|
|
3211
|
-
def _migrate_agent_config(self,
|
|
3212
|
-
"""为旧 agent config
|
|
3213
|
-
cfg_file = self._agent_dir(
|
|
3206
|
+
def _migrate_agent_config(self, aid: str):
|
|
3207
|
+
"""为旧 agent config 补全缺失字段"""
|
|
3208
|
+
cfg_file = self._agent_dir(aid) / "config.json"
|
|
3214
3209
|
if not cfg_file.exists():
|
|
3215
3210
|
return
|
|
3216
3211
|
try:
|
|
3217
3212
|
cfg = json.loads(cfg_file.read_text(encoding="utf-8"))
|
|
3218
3213
|
except (json.JSONDecodeError, ValueError):
|
|
3219
|
-
logger.warning(f"Agent config JSON
|
|
3220
|
-
try:
|
|
3221
|
-
cfg_file.unlink()
|
|
3222
|
-
except OSError:
|
|
3223
|
-
pass
|
|
3224
|
-
# 如果是 default 或配置助手,由 _ensure_default_agent / _ensure_config_helper 重建
|
|
3225
|
-
if path in ("default", "配置助手", "p"):
|
|
3226
|
-
return
|
|
3227
|
-
# 其他 agent:创建一个最小 config
|
|
3228
|
-
ad = self._agent_dir(path)
|
|
3229
|
-
if ad.exists():
|
|
3230
|
-
now = _now_iso()
|
|
3231
|
-
cfg = {
|
|
3232
|
-
"id": generate_id(),
|
|
3233
|
-
"name": path,
|
|
3234
|
-
"description": "",
|
|
3235
|
-
"avatar_emoji": "🤖",
|
|
3236
|
-
"execution_mode": "sandbox",
|
|
3237
|
-
"enabled": True,
|
|
3238
|
-
"created_at": now,
|
|
3239
|
-
"updated_at": now,
|
|
3240
|
-
}
|
|
3241
|
-
cfg_file.write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
3242
|
-
logger.info(f"已重建 Agent 配置: {path}")
|
|
3214
|
+
logger.warning(f"Agent config JSON 解析失败: {aid}")
|
|
3243
3215
|
return
|
|
3244
3216
|
changed = False
|
|
3245
3217
|
now = _now_iso()
|
|
3246
|
-
if "id" not in cfg
|
|
3247
|
-
cfg["id"] =
|
|
3218
|
+
if "id" not in cfg:
|
|
3219
|
+
cfg["id"] = int(aid) if aid.isdigit() else next_agent_id()
|
|
3220
|
+
changed = True
|
|
3221
|
+
if "path" not in cfg:
|
|
3222
|
+
cfg["path"] = aid
|
|
3248
3223
|
changed = True
|
|
3249
3224
|
if "created_at" not in cfg:
|
|
3250
3225
|
cfg["created_at"] = now
|
|
@@ -3252,25 +3227,19 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3252
3227
|
if "updated_at" not in cfg:
|
|
3253
3228
|
cfg["updated_at"] = now
|
|
3254
3229
|
changed = True
|
|
3255
|
-
# 修正旧 default agent 的名字
|
|
3256
|
-
if intended_name and cfg.get("name") == "default":
|
|
3257
|
-
cfg["name"] = intended_name
|
|
3258
|
-
changed = True
|
|
3259
3230
|
if changed:
|
|
3260
3231
|
cfg_file.write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
3261
3232
|
|
|
3262
3233
|
def _ensure_config_helper(self):
|
|
3263
|
-
"""确保系统级「配置助手」agent
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
if (ad / "config.json").exists():
|
|
3267
|
-
self._migrate_agent_config("配置助手")
|
|
3268
|
-
# 迁移后再次检查(损坏文件可能已被删除)
|
|
3234
|
+
"""确保系统级「配置助手」agent 存在(ID=2,目录名=2)"""
|
|
3235
|
+
aid = "2"
|
|
3236
|
+
ad = self._agent_dir(aid)
|
|
3269
3237
|
if not (ad / "config.json").exists():
|
|
3270
3238
|
ad.mkdir(parents=True, exist_ok=True)
|
|
3271
3239
|
now = _now_iso()
|
|
3272
3240
|
cfg = {
|
|
3273
|
-
"id":
|
|
3241
|
+
"id": 2,
|
|
3242
|
+
"path": aid,
|
|
3274
3243
|
"name": "配置助手",
|
|
3275
3244
|
"description": "MyAgent 智能配置助手 - 内置系统Agent,帮助用户完成初始配置和日常配置管理",
|
|
3276
3245
|
"avatar_color": "#4f46e5",
|
|
@@ -3303,21 +3272,12 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3303
3272
|
except Exception as e:
|
|
3304
3273
|
logger.warning(f"同步配置助手提示词失败: {e}")
|
|
3305
3274
|
|
|
3306
|
-
# 创建快捷方式 p -> 配置助手(符号链接)
|
|
3307
|
-
p_dir = self._agent_dir("p")
|
|
3308
|
-
if not p_dir.exists() and ad.exists():
|
|
3309
|
-
try:
|
|
3310
|
-
# 在 agents 目录下创建 p -> 配置助手 的符号链接
|
|
3311
|
-
p_dir.symlink_to(ad)
|
|
3312
|
-
logger.info("已创建配置助手快捷方式: p -> 配置助手")
|
|
3313
|
-
except OSError as e:
|
|
3314
|
-
logger.debug(f"创建快捷方式 p 失败(非关键): {e}")
|
|
3315
3275
|
# 自动绑定知识库:将 配置使用说明.md 复制到配置助手的知识库目录
|
|
3316
3276
|
self._bind_config_helper_kb()
|
|
3317
3277
|
|
|
3318
3278
|
def _bind_config_helper_kb(self):
|
|
3319
3279
|
"""将 docs/配置使用说明.md 绑定到配置助手的知识库目录"""
|
|
3320
|
-
kb_dir = self._get_agent_knowledge_dir("
|
|
3280
|
+
kb_dir = self._get_agent_knowledge_dir("2")
|
|
3321
3281
|
kb_dir.mkdir(parents=True, exist_ok=True)
|
|
3322
3282
|
target = kb_dir / "配置使用说明.md"
|
|
3323
3283
|
|
|
@@ -3350,8 +3310,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3350
3310
|
target.write_text(content, encoding="utf-8")
|
|
3351
3311
|
logger.info(f"配置助手知识库已绑定: {source} -> {target}")
|
|
3352
3312
|
# 刷新 RAG 索引
|
|
3353
|
-
if hasattr(self, '_agent_rags') and "
|
|
3354
|
-
self._agent_rags["
|
|
3313
|
+
if hasattr(self, '_agent_rags') and "2" in self._agent_rags:
|
|
3314
|
+
self._agent_rags["2"].build_index()
|
|
3355
3315
|
else:
|
|
3356
3316
|
logger.warning(f"未找到 配置使用说明.md 源文件,已搜索: {[str(c) for c in source_candidates]}")
|
|
3357
3317
|
|
|
@@ -3415,56 +3375,43 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3415
3375
|
|
|
3416
3376
|
return user_message, agent_system_prompt
|
|
3417
3377
|
|
|
3418
|
-
def _scan_agents_flat(self
|
|
3419
|
-
"""
|
|
3420
|
-
|
|
3421
|
-
base_dir = self._agents_dir()
|
|
3378
|
+
def _scan_agents_flat(self) -> list[dict]:
|
|
3379
|
+
"""扫描所有 agent 目录,返回扁平列表(目录名即为 agent ID)"""
|
|
3380
|
+
base_dir = self._agents_dir()
|
|
3422
3381
|
agents = []
|
|
3423
3382
|
if not base_dir.exists():
|
|
3424
3383
|
return agents
|
|
3425
3384
|
for d in sorted(base_dir.iterdir()):
|
|
3426
|
-
if not d.is_dir():
|
|
3385
|
+
if not d.is_dir() or d.is_symlink():
|
|
3427
3386
|
continue
|
|
3428
3387
|
cfg_file = d / "config.json"
|
|
3429
3388
|
if not cfg_file.exists():
|
|
3430
3389
|
continue
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
if
|
|
3390
|
+
aid = d.name
|
|
3391
|
+
self._migrate_agent_config(aid)
|
|
3392
|
+
if not cfg_file.exists():
|
|
3434
3393
|
continue
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
cfg = json.loads(cfg_file.read_text(encoding="utf-8"))
|
|
3443
|
-
except (json.JSONDecodeError, ValueError):
|
|
3444
|
-
logger.warning(f"Agent config JSON 解析失败,跳过: {agent_path}")
|
|
3445
|
-
continue
|
|
3446
|
-
agent = {"path": agent_path, "name": d.name, **cfg}
|
|
3447
|
-
agent["avatar_color"] = cfg.get("avatar_color") or _agent_color(d.name)
|
|
3448
|
-
agent["depth"] = agent_path.count("/")
|
|
3449
|
-
# [v1.20.13] 自动检测 avatar.png 文件,补全 avatar_image 字段
|
|
3394
|
+
try:
|
|
3395
|
+
cfg = json.loads(cfg_file.read_text(encoding="utf-8"))
|
|
3396
|
+
except (json.JSONDecodeError, ValueError):
|
|
3397
|
+
continue
|
|
3398
|
+
agent = {"path": aid, "id": int(aid) if aid.isdigit() else cfg.get("id", aid), "name": cfg.get("name", aid), **cfg}
|
|
3399
|
+
agent["avatar_color"] = cfg.get("avatar_color") or _agent_color(cfg.get("name", aid))
|
|
3400
|
+
# [v1.20.13] 自动检测 avatar.png
|
|
3450
3401
|
if not agent.get("avatar_image") and (d / "avatar.png").exists():
|
|
3451
|
-
agent["avatar_image"] = f"/api/agents/{
|
|
3402
|
+
agent["avatar_image"] = f"/api/agents/{aid}/avatar.png"
|
|
3452
3403
|
agents.append(agent)
|
|
3453
|
-
# 递归子目录
|
|
3454
|
-
agents.extend(self._scan_agents_flat(d, agent_path))
|
|
3455
3404
|
return agents
|
|
3456
3405
|
|
|
3457
3406
|
def _build_agent_tree(self, agents_flat: list[dict]) -> list[dict]:
|
|
3458
|
-
"""将扁平 agent
|
|
3459
|
-
|
|
3460
|
-
by_path = {a["path"]: {**a, "children": []} for a in agents_flat}
|
|
3407
|
+
"""将扁平 agent 列表构建为树结构(通过 parent 字段关联)"""
|
|
3408
|
+
by_id = {a["path"]: {**a, "children": []} for a in agents_flat}
|
|
3461
3409
|
roots = []
|
|
3462
3410
|
for a in agents_flat:
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
by_path[parent_path]["children"].append(node)
|
|
3411
|
+
parent = a.get("parent", "")
|
|
3412
|
+
node = by_id[a["path"]]
|
|
3413
|
+
if parent and parent in by_id:
|
|
3414
|
+
by_id[parent]["children"].append(node)
|
|
3468
3415
|
else:
|
|
3469
3416
|
roots.append(node)
|
|
3470
3417
|
return roots
|
|
@@ -3524,13 +3471,15 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3524
3471
|
if "/" in name or "\\" in name or name == "default":
|
|
3525
3472
|
return web.json_response({"error": "invalid name (no slashes, cannot be 'default')"}, status=400)
|
|
3526
3473
|
|
|
3527
|
-
|
|
3474
|
+
aid = str(next_agent_id())
|
|
3475
|
+
ad = self._agent_dir(aid)
|
|
3528
3476
|
if (ad / "config.json").exists():
|
|
3529
3477
|
return web.json_response({"error": f"Agent '{name}' already exists"}, status=409)
|
|
3530
3478
|
|
|
3531
3479
|
now = _now_iso()
|
|
3532
3480
|
cfg = {
|
|
3533
|
-
"id":
|
|
3481
|
+
"id": int(aid),
|
|
3482
|
+
"path": aid,
|
|
3534
3483
|
"name": name,
|
|
3535
3484
|
"description": data.get("description", ""),
|
|
3536
3485
|
"avatar_color": data.get("avatar_color") or _agent_color(name),
|
|
@@ -3575,13 +3524,13 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3575
3524
|
if data.get("department"):
|
|
3576
3525
|
try:
|
|
3577
3526
|
dm = self._get_dept_manager()
|
|
3578
|
-
dm.assign_agent(data["department"], agents=[
|
|
3527
|
+
dm.assign_agent(data["department"], agents=[aid], action="add")
|
|
3579
3528
|
logger.info(f"Agent {name} 已自动分配到部门: {data['department']}")
|
|
3580
3529
|
except Exception as e:
|
|
3581
3530
|
logger.warning(f"自动分配 Agent {name} 到部门失败: {e}")
|
|
3582
3531
|
|
|
3583
|
-
logger.info(f"创建 Agent: {name} (
|
|
3584
|
-
return web.json_response({"ok": True, "path":
|
|
3532
|
+
logger.info(f"创建 Agent: {name} (ID={aid})")
|
|
3533
|
+
return web.json_response({"ok": True, "path": aid, "id": int(aid), "name": name, "avatar_color": cfg["avatar_color"]})
|
|
3585
3534
|
|
|
3586
3535
|
async def handle_create_child(self, request):
|
|
3587
3536
|
"""POST /api/agents/{parent}/children - 创建子 agent"""
|
|
@@ -3600,11 +3549,14 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3600
3549
|
return web.json_response({"error": "invalid name (no slashes)"}, status=400)
|
|
3601
3550
|
|
|
3602
3551
|
child_path = f"{parent_path}/{name}"
|
|
3603
|
-
|
|
3552
|
+
child_aid = str(next_agent_id())
|
|
3553
|
+
ad = self._agent_dir(child_aid)
|
|
3604
3554
|
if (ad / "config.json").exists():
|
|
3605
|
-
return web.json_response({"error": f"Agent '{
|
|
3555
|
+
return web.json_response({"error": f"Agent '{name}' already exists"}, status=409)
|
|
3606
3556
|
|
|
3607
3557
|
cfg = {
|
|
3558
|
+
"id": int(child_aid),
|
|
3559
|
+
"path": child_aid,
|
|
3608
3560
|
"name": name,
|
|
3609
3561
|
"parent": parent_path,
|
|
3610
3562
|
"description": data.get("description", ""),
|
|
@@ -3640,30 +3592,18 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3640
3592
|
if not (ad / fn).exists():
|
|
3641
3593
|
(ad / fn).write_text(default, encoding="utf-8")
|
|
3642
3594
|
|
|
3643
|
-
logger.info(f"创建子 Agent: {
|
|
3644
|
-
return web.json_response({"ok": True, "path":
|
|
3595
|
+
logger.info(f"创建子 Agent: {name} (ID={child_aid}, parent={parent_path})")
|
|
3596
|
+
return web.json_response({"ok": True, "path": child_aid, "id": int(child_aid), "name": name, "parent": parent_path, "avatar_color": cfg["avatar_color"]})
|
|
3645
3597
|
|
|
3646
3598
|
async def handle_list_children(self, request):
|
|
3647
|
-
"""GET /api/agents/{parent}/children - 列出子 agent"""
|
|
3599
|
+
"""GET /api/agents/{parent}/children - 列出子 agent(通过 parent 字段)"""
|
|
3648
3600
|
parent_path = request.match_info["name"]
|
|
3649
|
-
|
|
3650
|
-
if not
|
|
3601
|
+
parent_cfg = self._read_agent_config(parent_path)
|
|
3602
|
+
if not parent_cfg:
|
|
3651
3603
|
return web.json_response({"error": f"Agent '{parent_path}' not found"}, status=404)
|
|
3652
3604
|
|
|
3653
|
-
|
|
3654
|
-
if
|
|
3655
|
-
for d in sorted(parent_dir.iterdir()):
|
|
3656
|
-
if d.is_dir() and (d / "config.json").exists():
|
|
3657
|
-
try:
|
|
3658
|
-
cfg = json.loads((d / "config.json").read_text(encoding="utf-8"))
|
|
3659
|
-
except (json.JSONDecodeError, ValueError):
|
|
3660
|
-
logger.warning(f"Agent config JSON 解析失败,跳过: {parent_path}/{d.name}")
|
|
3661
|
-
continue
|
|
3662
|
-
child_path = f"{parent_path}/{d.name}"
|
|
3663
|
-
child = {"path": child_path, "name": d.name, "parent": parent_path, **cfg}
|
|
3664
|
-
child["avatar_color"] = cfg.get("avatar_color") or _agent_color(d.name)
|
|
3665
|
-
child["depth"] = child_path.count("/")
|
|
3666
|
-
children.append(child)
|
|
3605
|
+
all_agents = self._scan_agents_flat()
|
|
3606
|
+
children = [a for a in all_agents if a.get("parent") == parent_path]
|
|
3667
3607
|
return web.json_response(children)
|
|
3668
3608
|
|
|
3669
3609
|
async def handle_get_agent(self, request):
|
|
@@ -3718,8 +3658,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3718
3658
|
return web.json_response({"error": "config.json 解析失败"}, status=500)
|
|
3719
3659
|
|
|
3720
3660
|
# 系统 Agent(内置 Agent)保护:核心字段不可修改
|
|
3721
|
-
|
|
3722
|
-
is_system = cfg.get("system") or path in ("p",)
|
|
3661
|
+
is_system = cfg.get("system") or path in ("1", "2")
|
|
3723
3662
|
if is_system:
|
|
3724
3663
|
blocked = [k for k in data if k in self._SYSTEM_AGENT_PROTECTED_FIELDS]
|
|
3725
3664
|
if blocked:
|
|
@@ -3771,39 +3710,16 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3771
3710
|
if "soul" in data and not is_system: (ad / "soul.md").write_text(data["soul"], encoding="utf-8")
|
|
3772
3711
|
if "identity" in data and not is_system: (ad / "identity.md").write_text(data["identity"], encoding="utf-8")
|
|
3773
3712
|
if "user" in data: (ad / "user.md").write_text(data["user"], encoding="utf-8")
|
|
3774
|
-
#
|
|
3775
|
-
new_name = data.get("name")
|
|
3776
|
-
renamed_to = None
|
|
3777
|
-
if new_name and new_name != path and not is_system:
|
|
3778
|
-
agents_dir = self._agents_dir()
|
|
3779
|
-
new_ad = agents_dir / new_name
|
|
3780
|
-
if not new_ad.exists():
|
|
3781
|
-
ad.rename(new_ad)
|
|
3782
|
-
logger.info(f"Agent 目录已重命名: {path} -> {new_name}")
|
|
3783
|
-
renamed_to = new_name
|
|
3784
|
-
# [v1.20.2] 重命名后更新 avatar_image URL 中的路径
|
|
3785
|
-
old_avatar = cfg.get("avatar_image", "")
|
|
3786
|
-
if old_avatar and f"/api/agents/{path}/" in old_avatar:
|
|
3787
|
-
cfg["avatar_image"] = old_avatar.replace(f"/api/agents/{path}/", f"/api/agents/{new_name}/")
|
|
3788
|
-
(new_ad / "config.json").write_text(
|
|
3789
|
-
json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
3790
|
-
)
|
|
3791
|
-
else:
|
|
3792
|
-
logger.warning(f"目标目录已存在,无法重命名: {new_name}")
|
|
3713
|
+
# 名字改变不再需要重命名目录(目录名是 agent ID,与名字无关)
|
|
3793
3714
|
logger.info(f"更新 Agent: {path}")
|
|
3794
|
-
|
|
3795
|
-
if renamed_to:
|
|
3796
|
-
result["renamed_to"] = renamed_to
|
|
3797
|
-
return web.json_response(result)
|
|
3715
|
+
return web.json_response({"ok": True, "hot_reload": True})
|
|
3798
3716
|
|
|
3799
3717
|
async def handle_delete_agent(self, request):
|
|
3800
3718
|
"""DELETE /api/agents/{path} - 删除 agent 及其所有子 agent"""
|
|
3801
3719
|
path = request.match_info["name"]
|
|
3802
|
-
if path
|
|
3803
|
-
return web.json_response({"error": "cannot delete default agent"}, status=403)
|
|
3804
|
-
if path in ("配置助手", "p"):
|
|
3720
|
+
if path in ("1", "2"):
|
|
3805
3721
|
return web.json_response({
|
|
3806
|
-
"error":
|
|
3722
|
+
"error": "系统 Agent 不可删除",
|
|
3807
3723
|
"agent_path": path,
|
|
3808
3724
|
}, status=403)
|
|
3809
3725
|
ad = self._agent_dir(path)
|
|
@@ -4317,7 +4233,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4317
4233
|
if agent:
|
|
4318
4234
|
# [v1.27.2] 先将 agent 路径转为数字 ID,用数字 ID 精确过滤
|
|
4319
4235
|
target_aid = self.core.memory.get_agent_id(agent) if agent else 0
|
|
4320
|
-
if agent == "
|
|
4236
|
+
if agent == "1":
|
|
4321
4237
|
# default agent: agent_id=0(未分配)或 agent_id=1(default 的数字 ID)或 旧格式
|
|
4322
4238
|
rows = conn.execute(
|
|
4323
4239
|
f"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last,
|
|
@@ -4386,7 +4302,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4386
4302
|
return web.json_response({"agent": name, "sessions": []})
|
|
4387
4303
|
# [v1.27.2] 使用数字 agent_id 精确过滤,同时兼容旧数据
|
|
4388
4304
|
target_aid = self.core.memory.get_agent_id(name)
|
|
4389
|
-
if name == "
|
|
4305
|
+
if name == "1":
|
|
4390
4306
|
# default agent: agent_id=0 或 agent_id=1 或 旧格式
|
|
4391
4307
|
rows = self.core.memory._get_conn().execute(
|
|
4392
4308
|
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
|
|
@@ -5471,7 +5387,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
5471
5387
|
else:
|
|
5472
5388
|
# 兜底到全局默认 LLM 配置
|
|
5473
5389
|
chain.append({
|
|
5474
|
-
"id": "
|
|
5390
|
+
"id": "1",
|
|
5475
5391
|
"name": llm_defaults.model,
|
|
5476
5392
|
"provider": llm_defaults.provider,
|
|
5477
5393
|
"api_type": llm_defaults.api_type,
|
|
@@ -5663,7 +5579,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
5663
5579
|
self.core.main_agent._agent_override_path = agent_path
|
|
5664
5580
|
# [v1.23.52] 为非默认 Agent 设置独立工作目录(executor 层面)
|
|
5665
5581
|
_original_exec_work_dir = None
|
|
5666
|
-
if agent_path and agent_path != "
|
|
5582
|
+
if agent_path and agent_path != "1" and self.core.main_agent and self.core.main_agent.executor:
|
|
5667
5583
|
from config import ConfigManager
|
|
5668
5584
|
_cm = ConfigManager()
|
|
5669
5585
|
_agent_wd = _cm.data_dir / "agents" / agent_path / "workspace"
|
|
@@ -5992,7 +5908,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
5992
5908
|
_original_exec_mode = agent.executor.execution_mode
|
|
5993
5909
|
agent.executor.set_execution_mode(_exec_mode)
|
|
5994
5910
|
# [v1.23.52] 为非默认 Agent 设置独立工作目录
|
|
5995
|
-
if agent_path and agent_path != "
|
|
5911
|
+
if agent_path and agent_path != "1" and agent.executor:
|
|
5996
5912
|
from config import ConfigManager
|
|
5997
5913
|
cm = ConfigManager()
|
|
5998
5914
|
agent_work_dir = cm.data_dir / "agents" / agent_path / "workspace"
|
|
@@ -7141,14 +7057,14 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7141
7057
|
needs_setup = not config.llm.api_key
|
|
7142
7058
|
# 确保配置助手存在
|
|
7143
7059
|
self._ensure_config_helper()
|
|
7144
|
-
helper_exists = (self._agent_dir("
|
|
7060
|
+
helper_exists = (self._agent_dir("2") / "config.json").exists()
|
|
7145
7061
|
return web.json_response({
|
|
7146
7062
|
"needs_setup": needs_setup,
|
|
7147
7063
|
"helper_exists": helper_exists,
|
|
7148
7064
|
"has_api_key": bool(config.llm.api_key),
|
|
7149
7065
|
"provider": config.llm.provider,
|
|
7150
7066
|
"model": config.llm.model,
|
|
7151
|
-
"default_agent": "
|
|
7067
|
+
"default_agent": "1",
|
|
7152
7068
|
})
|
|
7153
7069
|
|
|
7154
7070
|
async def handle_setup_complete(self, request):
|
|
@@ -7297,7 +7213,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7297
7213
|
def _check_org_admin(self, agent_path: str = "") -> bool:
|
|
7298
7214
|
"""检查 agent 是否是组织知识库管理员"""
|
|
7299
7215
|
org_cfg = self.core.config.organization
|
|
7300
|
-
admin = org_cfg.knowledge_admin or "
|
|
7216
|
+
admin = org_cfg.knowledge_admin or "1"
|
|
7301
7217
|
return agent_path == admin
|
|
7302
7218
|
|
|
7303
7219
|
async def handle_get_organization(self, request):
|
|
@@ -7357,7 +7273,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7357
7273
|
async def handle_upload_org_knowledge(self, request):
|
|
7358
7274
|
"""POST /api/organization/knowledge/upload - 上传文件到组织知识库(支持文件和文件夹上传)"""
|
|
7359
7275
|
# 权限检查
|
|
7360
|
-
agent = request.query.get("agent", "
|
|
7276
|
+
agent = request.query.get("agent", "1")
|
|
7361
7277
|
if not self._check_org_admin(agent):
|
|
7362
7278
|
return web.json_response(
|
|
7363
7279
|
{"ok": False, "error": "权限不足:只有知识库管理员才能上传"},
|
|
@@ -7385,7 +7301,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7385
7301
|
async def handle_delete_org_knowledge(self, request):
|
|
7386
7302
|
"""DELETE /api/organization/knowledge?path=xxx - 删除组织知识库文件"""
|
|
7387
7303
|
# 权限检查
|
|
7388
|
-
agent = request.query.get("agent", "
|
|
7304
|
+
agent = request.query.get("agent", "1")
|
|
7389
7305
|
if not self._check_org_admin(agent):
|
|
7390
7306
|
return web.json_response(
|
|
7391
7307
|
{"ok": False, "error": "权限不足:只有知识库管理员才能删除"},
|
|
@@ -7727,7 +7643,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7727
7643
|
name = data.get("name", "").strip()
|
|
7728
7644
|
if not name:
|
|
7729
7645
|
return web.json_response({"error": "群名不能为空"}, status=400)
|
|
7730
|
-
owner = data.get("owner", "
|
|
7646
|
+
owner = data.get("owner", "1")
|
|
7731
7647
|
description = data.get("description", "")
|
|
7732
7648
|
avatar_emoji = data.get("avatar_emoji", "👥")
|
|
7733
7649
|
avatar_color = data.get("avatar_color", "")
|
|
@@ -9041,7 +8957,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
9041
8957
|
return web.json_response({"error": "invalid JSON"}, status=400)
|
|
9042
8958
|
|
|
9043
8959
|
text = data.get("text", "")
|
|
9044
|
-
agent_path = data.get("agent_path", "
|
|
8960
|
+
agent_path = data.get("agent_path", "1") or "1"
|
|
9045
8961
|
filename = data.get("filename", "").strip()
|
|
9046
8962
|
|
|
9047
8963
|
if not text:
|