myagent-ai 1.24.5 → 1.25.0

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.
@@ -393,6 +393,7 @@ class AgentStorage:
393
393
  def migrate_from_json(self, agents_dir: Path, force: bool = False) -> dict:
394
394
  """
395
395
  从文件系统迁移 agent 配置到数据库。
396
+ 迁移完成后删除 config.json 防止重复迁移。
396
397
 
397
398
  Args:
398
399
  agents_dir: agents 数据目录路径(~/.myagent/data/agents/)
@@ -408,6 +409,13 @@ class AgentStorage:
408
409
  logger.info(f"Agent 数据目录不存在,跳过迁移: {agents_dir}")
409
410
  return result
410
411
 
412
+ # [v1.24.5] 检查迁移标记文件,如果已迁移过则跳过
413
+ _migrated_flag = agents_dir.parent / ".migrated_to_db"
414
+ if _migrated_flag.exists() and self.count() > 0 and not force:
415
+ logger.info(f"数据库已有 {self.count()} 个 agent,且迁移标记存在,跳过迁移")
416
+ result["skipped"] = self.count()
417
+ return result
418
+
411
419
  if self.count() > 0 and not force:
412
420
  logger.info(f"数据库已有 {self.count()} 个 agent,跳过迁移(使用 force 强制重新迁移)")
413
421
  result["skipped"] = self.count()
@@ -457,6 +465,13 @@ class AgentStorage:
457
465
  with self._lock:
458
466
  conn.commit()
459
467
 
468
+ # [v1.24.5] 迁移完成后写入标记文件,防止卸载重装后重复迁移
469
+ if result["migrated"] > 0:
470
+ try:
471
+ _migrated_flag.write_text(now, encoding="utf-8")
472
+ except Exception as e:
473
+ logger.warning(f"写入迁移标记失败: {e}")
474
+
460
475
  logger.info(f"Agent 配置迁移完成: {result['migrated']} 迁移, {result['skipped']} 跳过, {result['errors']} 错误")
461
476
  return result
462
477
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.24.5",
3
+ "version": "1.25.0",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -0,0 +1,560 @@
1
+ ---
2
+ name: admin-manager
3
+ description: |
4
+ 后台管理技能 — 赋能 AI 助手通过 API 完成所有后台管理操作,包括大模型管理、Agent 管理、组织架构管理、部门管理等。
5
+ 适用于配置助手和管理场景,让 AI 可以直接帮用户管理整个 MyAgent 系统。
6
+ metadata:
7
+ category: admin
8
+ version: "1.0.0"
9
+ ---
10
+
11
+ # 后台管理技能 (Admin Manager)
12
+
13
+ 你拥有完整的后台管理权限,可以通过调用 MyAgent 的 RESTful API 来管理整个系统。
14
+ 以下是你可用的所有管理操作指南。
15
+
16
+ ## 重要规则
17
+
18
+ - **所有管理操作必须通过 API 完成**,禁止直接修改数据库文件或配置文件。
19
+ - 操作前应先通过 GET 请求了解当前状态,确认操作安全后再执行修改。
20
+ - 对于删除操作,务必先确认用户意图,避免误删。
21
+ - 所有 API 的基础 URL 为 `http://localhost:8767`(即当前服务地址),以下省略前缀。
22
+ - 调用 API 使用 `requests` 库或 `httpx` 库,推荐使用 `requests`。
23
+
24
+ ---
25
+
26
+ ## 一、大模型管理 (Model Management)
27
+
28
+ 模型库管理支持添加、编辑、删除、测试大模型配置。每个模型可以独立配置 provider、API Key、base URL 等参数。
29
+
30
+ ### 1.1 列出所有模型
31
+
32
+ ```
33
+ GET /api/models
34
+ ```
35
+
36
+ 返回模型列表,每个模型包含: `id`, `name`, `provider`, `api_type`, `model`, `base_url`, `api_key`, `max_tokens`, `temperature`, `context_window`, `input_modes`, `reasoning`, `enabled`, `is_global_fallback`。
37
+
38
+ ### 1.2 添加模型
39
+
40
+ ```
41
+ POST /api/models
42
+ Content-Type: application/json
43
+
44
+ {
45
+ "id": "my-gpt4o", // 唯一标识符,必填
46
+ "name": "我的 GPT-4o", // 显示名称
47
+ "provider": "openai", // openai | anthropic | ollama | zhipu | deepseek | moonshot | qwen | modelscope | custom
48
+ "api_type": "openai-completions", // openai-completions | openai-chat | anthropic | ollama | custom
49
+ "model": "gpt-4o", // API 调用使用的实际模型字符串
50
+ "base_url": "https://api.openai.com/v1", // 自定义 API 地址
51
+ "api_key": "sk-...", // 专用 API Key(留空则使用全局默认)
52
+ "max_tokens": 4096,
53
+ "temperature": 0.1,
54
+ "context_window": 128000, // 上下文窗口大小
55
+ "input_modes": ["text", "image"], // 支持的输入模式: text, image, video, audio
56
+ "reasoning": false, // 是否支持推理(如 o1 系列)
57
+ "enabled": true, // 是否启用
58
+ "is_global_fallback": true // 是否作为全局兜底模型
59
+ }
60
+ ```
61
+
62
+ ### 1.3 更新模型
63
+
64
+ ```
65
+ PUT /api/models/{model_id}
66
+ Content-Type: application/json
67
+
68
+ { 同添加模型的字段,只需传要修改的字段 }
69
+ ```
70
+
71
+ ### 1.4 删除模型
72
+
73
+ ```
74
+ DELETE /api/models/{model_id}
75
+ ```
76
+
77
+ ### 1.5 测试模型连接
78
+
79
+ ```
80
+ POST /api/llm/test
81
+ Content-Type: application/json
82
+
83
+ {
84
+ "model": "gpt-4o",
85
+ "base_url": "https://api.openai.com/v1",
86
+ "provider": "openai",
87
+ "temperature": 0.1,
88
+ "api_key": "sk-...",
89
+ "input_modes": ["text", "image"]
90
+ }
91
+ ```
92
+
93
+ ### 常用 Provider 配置参考
94
+
95
+ | Provider | base_url | 说明 |
96
+ |----------|----------|------|
97
+ | openai | https://api.openai.com/v1 | OpenAI 官方 |
98
+ | custom (ModelScope) | https://api-inference.modelscope.cn/v1 | 阿里 ModelScope(推荐免费额度大)|
99
+ | custom (DeepSeek) | https://api.deepseek.com/v1 | DeepSeek |
100
+ | custom (Moonshot) | https://api.moonshot.cn/v1 | Kimi/Moonshot |
101
+ | zhipu | https://open.bigmodel.cn/api/paas/v4 | 智谱 GLM |
102
+ | ollama | http://localhost:11434 | 本地 Ollama |
103
+ | anthropic | - | Anthropic Claude |
104
+
105
+ ---
106
+
107
+ ## 二、Agent 管理 (Agent Management)
108
+
109
+ Agent 是 MyAgent 的核心概念,每个 Agent 拥有独立的人格、知识库、会话历史和权限配置。
110
+
111
+ ### 2.1 列出所有 Agent
112
+
113
+ ```
114
+ GET /api/agents
115
+ ```
116
+
117
+ 返回所有 Agent 的扁平列表,包含: `path`, `id`, `name`, `description`, `avatar_color`, `avatar_emoji`, `avatar_image`, `execution_mode`, `enabled`, `system`, `system_prompt`, `model`, `model_id`, `backup_model_ids`, `parent`, `platform`, `department`, `work_dir`, `created_at`, `updated_at`, `session_count`。
118
+
119
+ ### 2.2 获取单个 Agent 详情
120
+
121
+ ```
122
+ GET /api/agents/{agent_path}
123
+ ```
124
+
125
+ 除了基本信息外,还返回 `soul`, `identity`, `user` 等人格文件内容。
126
+
127
+ ### 2.3 创建 Agent
128
+
129
+ ```
130
+ POST /api/agents
131
+ Content-Type: application/json
132
+
133
+ {
134
+ "name": "翻译助手", // 名称,必填
135
+ "description": "专业翻译AI助手", // 描述
136
+ "avatar_emoji": "🌐", // 头像 emoji
137
+ "avatar_color": "#4f46e5", // 头像颜色
138
+ "execution_mode": "sandbox", // sandbox(沙盒) | local(本机)
139
+ "model_id": "gpt-4o", // 绑定的模型 ID(留空用全局默认)
140
+ "backup_model_ids": ["claude-3.5-sonnet"], // 备用模型列表
141
+ "system_prompt": "你是一个专业翻译助手...", // 系统提示词
142
+ "work_dir": "", // 自定义工作目录
143
+ "department": "技术部" // 所属部门路径
144
+ }
145
+ ```
146
+
147
+ ### 2.4 更新 Agent
148
+
149
+ ```
150
+ PUT /api/agents/{agent_path}
151
+ Content-Type: application/json
152
+
153
+ { 只需传要修改的字段,支持: name, description, avatar_emoji, avatar_color, avatar_image,
154
+ model_id, backup_model_ids, work_dir, system_prompt, execution_mode, enabled, department }
155
+ ```
156
+
157
+ ### 2.5 删除 Agent
158
+
159
+ ```
160
+ DELETE /api/agents/{agent_path}
161
+ ```
162
+
163
+ **注意**: 删除会同时清理工作目录、会话历史、记忆数据和所有子 Agent,不可撤销!
164
+
165
+ ### 2.6 创建子 Agent
166
+
167
+ ```
168
+ POST /api/agents/{parent_path}/children
169
+ Content-Type: application/json
170
+
171
+ {
172
+ "name": "子助手名称",
173
+ "description": "子助手描述",
174
+ "system_prompt": "子助手系统提示词"
175
+ }
176
+ ```
177
+
178
+ ### 2.7 管理 Agent 人格
179
+
180
+ ```
181
+ # 获取 soul.md
182
+ GET /api/agents/{agent_path}/soul
183
+
184
+ # 设置 soul.md
185
+ PUT /api/agents/{agent_path}/soul
186
+ Content-Type: application/json
187
+ { "soul": "# Agent名称\n\n## 性格\n..." }
188
+
189
+ # 获取 identity.md
190
+ GET /api/agents/{agent_path}/identity
191
+
192
+ # 设置 identity.md
193
+ PUT /api/agents/{agent_path}/identity
194
+ Content-Type: application/json
195
+ { "identity": "# 身份\n..." }
196
+
197
+ # 获取 user.md
198
+ GET /api/agents/{agent_path}/user
199
+
200
+ # 设置 user.md
201
+ PUT /api/agents/{agent_path}/user
202
+ Content-Type: application/json
203
+ { "user": "# 用户信息\n..." }
204
+ ```
205
+
206
+ ### 2.8 管理 Agent 知识库
207
+
208
+ ```
209
+ # 列出知识库文件
210
+ GET /api/agents/{agent_path}/knowledge
211
+
212
+ # 读取知识库文件
213
+ GET /api/agents/{agent_path}/knowledge/file?path=xxx.md
214
+
215
+ # 上传知识库文件
216
+ POST /api/agents/{agent_path}/knowledge/upload
217
+ Content-Type: multipart/form-data (files 字段)
218
+
219
+ # 删除知识库文件
220
+ DELETE /api/agents/{agent_path}/knowledge?path=xxx.md
221
+ ```
222
+
223
+ ### 2.9 查看 Agent 会话
224
+
225
+ ```
226
+ # 列出 Agent 的所有会话
227
+ GET /api/agents/{agent_path}/sessions
228
+
229
+ # 查看会话消息
230
+ GET /api/sessions/{session_id}/messages?limit=100
231
+ ```
232
+
233
+ ### 2.10 查看 Agent 权限
234
+
235
+ ```
236
+ # 获取 Agent 权限
237
+ GET /api/permissions/{agent_path}
238
+
239
+ # 设置 Agent 权限
240
+ PUT /api/permissions/{agent_path}
241
+ Content-Type: application/json
242
+ { "command": true, "web_control": false, ... }
243
+
244
+ # 重置为默认权限
245
+ DELETE /api/permissions/{agent_path}
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 三、组织管理 (Organization Management)
251
+
252
+ ### 3.1 获取/更新组织配置
253
+
254
+ ```
255
+ # 获取组织配置
256
+ GET /api/organization
257
+
258
+ # 更新组织配置
259
+ PUT /api/organization
260
+ Content-Type: application/json
261
+ { "enabled": true, "knowledge_admin": "manager" }
262
+ ```
263
+
264
+ ### 3.2 获取/更新组织信息
265
+
266
+ ```
267
+ # 获取组织信息
268
+ GET /api/organization/info
269
+
270
+ # 更新组织信息
271
+ PUT /api/organization/info
272
+ Content-Type: application/json
273
+ { "name": "我的公司", "description": "...", "contact": "...", "website": "..." }
274
+ ```
275
+
276
+ ### 3.3 管理组织知识库
277
+
278
+ ```
279
+ # 列出知识库文件
280
+ GET /api/organization/knowledge
281
+
282
+ # 读取知识库文件
283
+ GET /api/organization/knowledge/file?path=xxx.md
284
+
285
+ # 上传知识库文件
286
+ POST /api/organization/knowledge/upload
287
+ Content-Type: multipart/form-data
288
+
289
+ # 删除知识库文件
290
+ DELETE /api/organization/knowledge?path=xxx.md
291
+ ```
292
+
293
+ ---
294
+
295
+ ## 四、部门管理 (Department Management)
296
+
297
+ 部门支持无限层级嵌套,每个部门可以有成员(Agent)、负责人、知识库和自动创建的群聊。
298
+
299
+ ### 4.1 获取部门树
300
+
301
+ ```
302
+ GET /api/departments
303
+ ```
304
+
305
+ 返回树形结构,每个部门节点包含: `path`, `name`, `emoji`, `description`, `head`, `agents`, `agent_count`, `chat_group_id`, `children`。
306
+
307
+ ### 4.2 创建部门
308
+
309
+ ```
310
+ POST /api/departments
311
+ Content-Type: application/json
312
+
313
+ {
314
+ "name": "技术部", // 部门名称,必填
315
+ "emoji": "💻", // 部门 emoji
316
+ "description": "负责技术研发", // 描述
317
+ "parent": "" // 父部门路径(空字符串=顶级部门)
318
+ }
319
+ ```
320
+
321
+ ### 4.3 获取部门详情
322
+
323
+ ```
324
+ GET /api/departments/{dept_path}
325
+ ```
326
+
327
+ ### 4.4 更新部门基本信息
328
+
329
+ ```
330
+ PUT /api/departments/{dept_path}
331
+ Content-Type: application/json
332
+ { "name": "新名称", "emoji": "🚀" }
333
+ ```
334
+
335
+ ### 4.5 更新部门详细信息(描述、负责人等)
336
+
337
+ ```
338
+ PUT /api/departments/{dept_path}/info
339
+ Content-Type: application/json
340
+ { "description": "新的部门描述", "head_name": "agent_name" }
341
+ ```
342
+
343
+ ### 4.6 删除部门
344
+
345
+ ```
346
+ DELETE /api/departments/{dept_path}
347
+ ```
348
+
349
+ **注意**: 会递归删除所有子部门、自动解散关联的群聊,不可撤销!
350
+
351
+ ### 4.7 管理部门成员
352
+
353
+ ```
354
+ # 添加/移除成员
355
+ PUT /api/departments/{dept_path}/agents
356
+ Content-Type: application/json
357
+ { "agents": ["coder", "translator"], "action": "add" } // action: add | remove | set
358
+
359
+ # 设置部门负责人
360
+ PUT /api/departments/{dept_path}/head
361
+ Content-Type: application/json
362
+ { "head": "agent_name" }
363
+ ```
364
+
365
+ ### 4.8 管理部门知识库
366
+
367
+ ```
368
+ # 列出知识库文件
369
+ GET /api/departments/{dept_path}/knowledge
370
+
371
+ # 读取知识库文件
372
+ GET /api/departments/{dept_path}/knowledge/file?path=xxx.md
373
+
374
+ # 上传知识库文件
375
+ POST /api/departments/{dept_path}/knowledge/upload
376
+ Content-Type: multipart/form-data
377
+
378
+ # 删除知识库文件
379
+ DELETE /api/departments/{dept_path}/knowledge?path=xxx.md
380
+ ```
381
+
382
+ ---
383
+
384
+ ## 五、群聊管理 (Group Chat Management)
385
+
386
+ ### 5.1 列出所有群聊
387
+
388
+ ```
389
+ GET /api/groups
390
+ ```
391
+
392
+ ### 5.2 创建群聊
393
+
394
+ ```
395
+ POST /api/groups
396
+ Content-Type: application/json
397
+ {
398
+ "name": "群聊名称",
399
+ "description": "群聊描述",
400
+ "avatar_emoji": "👥",
401
+ "owner": "agent_path"
402
+ }
403
+ ```
404
+
405
+ ### 5.3 群成员管理
406
+
407
+ ```
408
+ # 添加成员
409
+ POST /api/groups/{group_id}/members
410
+ Content-Type: application/json
411
+ { "agent_path": "agent_name", "role": "member" }
412
+
413
+ # 移除成员
414
+ DELETE /api/groups/{group_id}/members/{agent_path}
415
+
416
+ # 设置成员角色
417
+ PUT /api/groups/{group_id}/members/{agent_path}/role
418
+ Content-Type: application/json
419
+ { "role": "owner" } // owner | admin | member
420
+
421
+ # 设置成员禁言
422
+ PUT /api/groups/{group_id}/members/{agent_path}/mute
423
+ Content-Type: application/json
424
+ { "muted": true }
425
+ ```
426
+
427
+ ---
428
+
429
+ ## 六、全局 LLM 配置 (Global LLM Config)
430
+
431
+ ### 6.1 获取当前 LLM 配置
432
+
433
+ ```
434
+ GET /api/llm
435
+ ```
436
+
437
+ ### 6.2 更新全局 LLM 配置
438
+
439
+ ```
440
+ PUT /api/llm
441
+ Content-Type: application/json
442
+ {
443
+ "provider": "openai",
444
+ "api_key": "sk-...",
445
+ "base_url": "https://api.openai.com/v1",
446
+ "model": "gpt-4o",
447
+ "temperature": 0.1,
448
+ "max_tokens": 4096,
449
+ "context_window": 128000
450
+ }
451
+ ```
452
+
453
+ ### 6.3 获取用量统计
454
+
455
+ ```
456
+ GET /api/llm/usage
457
+ ```
458
+
459
+ ---
460
+
461
+ ## 七、权限管理 (Permissions)
462
+
463
+ ### 7.1 获取全局权限配置
464
+
465
+ ```
466
+ GET /api/permissions
467
+ ```
468
+
469
+ ### 7.2 设置全局默认权限
470
+
471
+ ```
472
+ PUT /api/permissions/defaults
473
+ Content-Type: application/json
474
+ { "command": true, "web_control": false, ... }
475
+ ```
476
+
477
+ ---
478
+
479
+ ## 八、配置导入导出 (Config Import/Export)
480
+
481
+ ### 8.1 导出配置
482
+
483
+ ```
484
+ POST /api/config/export
485
+ Content-Type: application/json
486
+ { "include_secrets": false } // 是否包含 API Key 等敏感信息
487
+ ```
488
+
489
+ ### 8.2 导入配置
490
+
491
+ ```
492
+ POST /api/config/import
493
+ Content-Type: application/json
494
+ { ...config_data..., "overwrite": false } // false=合并, true=完全覆盖
495
+ ```
496
+
497
+ ### 8.3 热重载配置
498
+
499
+ ```
500
+ POST /api/config/reload
501
+ ```
502
+
503
+ ---
504
+
505
+ ## 九、记忆管理 (Memory)
506
+
507
+ ### 9.1 搜索记忆
508
+
509
+ ```
510
+ GET /api/memory/search?keyword=xxx&limit=20
511
+ ```
512
+
513
+ ### 9.2 获取记忆统计
514
+
515
+ ```
516
+ GET /api/memory/stats
517
+ ```
518
+
519
+ ### 9.3 清理过期记忆
520
+
521
+ ```
522
+ POST /api/memory/cleanup
523
+ ```
524
+
525
+ ---
526
+
527
+ ## 十、典型管理场景
528
+
529
+ ### 场景1: 首次配置 LLM
530
+
531
+ 1. 推荐使用 ModelScope 免费 API:
532
+ - provider: `custom`
533
+ - base_url: `https://api-inference.modelscope.cn/v1`
534
+ - 推荐模型: `stepfun-ai/Step-3.5-Flash`
535
+ 2. 添加模型到模型库: `POST /api/models`
536
+ 3. 设置为全局默认: `PUT /api/llm`
537
+ 4. 测试连接: `POST /api/llm/test`
538
+
539
+ ### 场景2: 创建多 Agent 团队
540
+
541
+ 1. 创建部门: `POST /api/departments` (如 "技术部"、"市场部")
542
+ 2. 创建多个 Agent: `POST /api/agents` (如 "代码专家"、"翻译助手")
543
+ 3. 分配 Agent 到部门: `PUT /api/departments/{path}/agents`
544
+ 4. 设置部门负责人: `PUT /api/departments/{path}/head`
545
+ 5. 为每个 Agent 配置独立的人格和知识库
546
+
547
+ ### 场景3: Agent 个性化配置
548
+
549
+ 1. 创建 Agent 并设置 system_prompt
550
+ 2. 通过 soul.md 定义 Agent 的性格特征
551
+ 3. 通过 identity.md 定义 Agent 的身份背景
552
+ 4. 上传专业知识到知识库
553
+ 5. 绑定专用模型以获得最佳效果
554
+
555
+ ### 场景4: 模型切换与故障转移
556
+
557
+ 1. 在模型库中添加多个模型
558
+ 2. 为关键 Agent 设置 model_id 和 backup_model_ids
559
+ 3. 设置 is_global_fallback 标记兜底模型
560
+ 4. 当主模型不可用时,系统自动切换到备用模型
package/web/api_server.py CHANGED
@@ -146,13 +146,72 @@ async def _read_multipart_files(request):
146
146
 
147
147
 
148
148
 
149
- CONFIG_HELPER_PROMPT = """你是 MyAgent 的智能配置助手,专门帮助用户完成初始配置和日常配置管理。你的名字叫"配置助手"。
149
+ CONFIG_HELPER_PROMPT = """你是 MyAgent 的智能配置助手,拥有完整的后台管理权限。你的名字叫"配置助手"。
150
150
 
151
151
  ## 你的核心职责
152
- 1. **引导新用户**:首次使用时,友好地介绍 MyAgent 的功能,帮助用户完成初始配置
153
- 2. **配置管理**:帮助用户修改配置(LLM模型、执行引擎、权限等)
154
- 3. **Agent 创建指导**:帮助用户创建和配置新的 Agent
155
- 4. **问题解答**:回答用户关于 MyAgent 使用的问题
152
+ 1. **后台管理**:通过 API 直接管理大模型、Agent、组织、部门、群聊等所有系统资源
153
+ 2. **引导新用户**:首次使用时,友好地介绍 MyAgent 的功能,帮助用户完成初始配置
154
+ 3. **配置管理**:帮助用户修改配置(LLM模型、执行引擎、权限等)
155
+ 4. **Agent 管理**:创建、编辑、删除 Agent,配置人格、知识库、权限等
156
+ 5. **组织架构**:管理部门体系,分配成员,维护组织知识库
157
+ 6. **问题解答**:回答用户关于 MyAgent 使用的问题
158
+
159
+ ## 管理权限
160
+ 你拥有完整的后台管理权限,可以直接通过调用本地 API 来管理整个系统。
161
+ 所有 API 基础地址: `http://localhost:8767`
162
+ 调用方式: 使用 Python `requests` 库发起 HTTP 请求。
163
+
164
+ ## 后台管理 API 速查
165
+
166
+ ### 大模型管理
167
+ - **列出模型**: `GET /api/models`
168
+ - **添加模型**: `POST /api/models` — 参数: `id`(必填), `name`, `provider`(openai/anthropic/ollama/zhipu/deepseek/moonshot/qwen/modelscope/custom), `api_type`, `model`, `base_url`, `api_key`, `max_tokens`, `temperature`, `context_window`, `input_modes`(["text","image","video","audio"]), `reasoning`, `enabled`, `is_global_fallback`
169
+ - **更新模型**: `PUT /api/models/{model_id}` — 同上字段,只需传修改项
170
+ - **删除模型**: `DELETE /api/models/{model_id}`
171
+ - **测试模型**: `POST /api/llm/test` — 参数: `model`, `base_url`, `provider`, `api_key`, `input_modes`
172
+ - **全局LLM配置**: `GET/PUT /api/llm` — 设置全局默认模型
173
+ - **用量统计**: `GET /api/llm/usage`
174
+
175
+ ### Agent 管理
176
+ - **列出Agent**: `GET /api/agents`
177
+ - **创建Agent**: `POST /api/agents` — 参数: `name`(必填), `description`, `avatar_emoji`, `avatar_color`, `execution_mode`(sandbox/local), `model_id`, `backup_model_ids`, `system_prompt`, `work_dir`, `department`
178
+ - **获取Agent**: `GET /api/agents/{path}`
179
+ - **更新Agent**: `PUT /api/agents/{path}` — 支持所有创建字段及 `enabled`
180
+ - **删除Agent**: `DELETE /api/agents/{path}` — 会同时删除子Agent、会话、工作目录,不可撤销!
181
+ - **创建子Agent**: `POST /api/agents/{parent}/children`
182
+ - **Agent人格**: `GET/PUT /api/agents/{path}/soul`, `identity`, `user`
183
+ - **Agent知识库**: `GET /api/agents/{path}/knowledge`, `POST .../knowledge/upload`, `DELETE .../knowledge?path=`
184
+ - **Agent会话**: `GET /api/agents/{path}/sessions`
185
+ - **Agent权限**: `GET/PUT/DELETE /api/permissions/{path}`
186
+
187
+ ### 组织管理
188
+ - **组织配置**: `GET/PUT /api/organization` — `enabled`, `knowledge_admin`
189
+ - **组织信息**: `GET/PUT /api/organization/info` — `name`, `description`, `contact`, `website`
190
+ - **组织知识库**: `GET /api/organization/knowledge`, `POST .../knowledge/upload`, `DELETE .../knowledge?path=`
191
+
192
+ ### 部门管理
193
+ - **部门树**: `GET /api/departments`
194
+ - **创建部门**: `POST /api/departments` — 参数: `name`(必填), `emoji`, `description`, `parent`
195
+ - **部门详情**: `GET /api/departments/{path}`
196
+ - **更新部门**: `PUT /api/departments/{path}` — `name`, `emoji`
197
+ - **更新部门详情**: `PUT /api/departments/{path}/info` — `description`, `head_name`
198
+ - **删除部门**: `DELETE /api/departments/{path}` — 递归删除子部门和群聊,不可撤销!
199
+ - **部门成员**: `PUT /api/departments/{path}/agents` — `agents`(list), `action`(add/remove/set)
200
+ - **部门负责人**: `PUT /api/departments/{path}/head` — `head`(agent_name)
201
+ - **部门知识库**: `GET /api/departments/{path}/knowledge`, `POST .../knowledge/upload`, `DELETE .../knowledge?path=`
202
+
203
+ ### 群聊管理
204
+ - **列出群聊**: `GET /api/groups`
205
+ - **创建群聊**: `POST /api/groups` — `name`, `description`, `avatar_emoji`, `owner`
206
+ - **群成员管理**: `POST/DELETE /api/groups/{gid}/members/{agent_path}`
207
+ - **成员角色**: `PUT /api/groups/{gid}/members/{agent_path}/role`
208
+ - **成员禁言**: `PUT /api/groups/{gid}/members/{agent_path}/mute`
209
+
210
+ ### 系统配置
211
+ - **完整配置**: `GET /api/config`
212
+ - **热重载**: `POST /api/config/reload`
213
+ - **导出配置**: `POST /api/config/export` — `include_secrets`
214
+ - **导入配置**: `POST /api/config/import` — `overwrite`
156
215
 
157
216
  ## 工作方式
158
217
 
@@ -165,31 +224,30 @@ CONFIG_HELPER_PROMPT = """你是 MyAgent 的智能配置助手,专门帮助用
165
224
  - provider: custom
166
225
  - base_url: https://api-inference.modelscope.cn/v1
167
226
  - 推荐模型:stepfun-ai/Step-3.5-Flash
168
- - 也支持 OpenAI、Anthropic、Ollama(本地)、智谱GLM 等
227
+ - 也支持 OpenAI、Anthropic、Ollama(本地)、智谱GLM、DeepSeek、Moonshot
169
228
  4. 配置完成后,建议用户先从单个 Agent 开始使用
170
229
  5. 介绍核心功能:代码执行、文件操作、网络搜索、技能系统
171
230
 
172
- ### 日常使用
173
- - 回答配置相关的问题
174
- - 帮助修改配置(你只能通过聊天引导用户去修改,或调用配置API)
175
- - 建议最佳实践
231
+ ### 日常管理
232
+ 当用户要求管理操作时,你应该:
233
+ 1. 先通过 GET 请求了解当前状态
234
+ 2. 确认用户意图后,通过 POST/PUT/DELETE 执行操作
235
+ 3. 操作完成后,向用户确认结果
236
+
237
+ ### 典型场景
238
+ - **用户说"帮我添加一个模型"**: 调用 `POST /api/models` 添加,然后 `POST /api/llm/test` 测试
239
+ - **用户说"创建一个翻译助手"**: 调用 `POST /api/agents` 创建,配置 system_prompt 和 model_id
240
+ - **用户说"建一个技术部"**: 调用 `POST /api/departments` 创建部门
241
+ - **用户说"把翻译助手加到技术部"**: 调用 `PUT /api/departments/{path}/agents` 分配成员
176
242
 
177
243
  ## 重要规则
178
- - **禁止编写代码创建 Agent**:当用户要求创建 Agent 或部门时,必须调用 API 操作,禁止通过编写 `.py` 文件或修改 `__init__.py` 来尝试在源码层级创建。
179
- - **配置优先原则**:MyAgent 是配置驱动的。创建 Agent 应使用 `POST /api/agents`,创建部门应使用 `POST /api/departments`。
180
- - 修改配置时,系统会自动备份和校验,确保配置不会被改坏。
181
- - 如果配置校验失败,修改不会生效,你需要告知用户并帮助修正。
182
- - 建议用户从单 Agent 开始使用,熟悉后再扩展。
244
+ - **必须通过 API 操作**:所有管理操作必须通过 HTTP API 完成,禁止直接修改数据库文件或配置文件。
245
+ - **禁止编写代码创建 Agent/部门**:禁止通过编写 `.py` 文件或修改 `__init__.py` 来尝试在源码层级创建。
246
+ - **配置优先原则**:MyAgent 是配置驱动的。创建 Agent 用 `POST /api/agents`,创建部门用 `POST /api/departments`。
247
+ - **删除前确认**:删除 Agent 和部门是不可逆操作,执行前务必确认用户意图。
248
+ - **操作前先查询**:修改前先 GET 了解当前状态,避免误操作。
183
249
  - 使用中文回复,保持简洁友好。
184
250
 
185
- ## 组织与 Agent 管理 API 指南
186
- 当你需要帮助用户管理组织结构时,请优先使用以下 API(通过 `requests` 或 `http` 调用):
187
- - **创建 Agent**: `POST /api/agents` (参数: `name`, `description`, `system_prompt`, `model_id`)
188
- - **创建子 Agent**: `POST /api/agents/{parent}/children` (参数: `name`, `description`, `system_prompt`)
189
- - **创建部门**: `POST /api/departments` (参数: `name`, `parent`)
190
- - **配置 Agent 灵魂/身份**: `PUT /api/agents/{name}/soul` 和 `PUT /api/agents/{name}/identity`
191
- - **分配 Agent 到部门**: `PUT /api/departments/{path}/agents` (参数: `agents`: list of names)
192
-
193
251
  ## 知识库
194
252
  你内置了 MyAgent 完整的配置使用说明文档。当用户询问时不确定的功能时,必须先参考知识库。
195
253
  """
@@ -484,6 +542,8 @@ class ApiServer:
484
542
  r.add_get("/api/agent-chat/pairs", self.handle_get_agent_chat_pairs)
485
543
  r.add_get("/api/agent-chat/messages", self.handle_get_agent_chat_messages)
486
544
  r.add_delete("/api/agent-chat/messages", self.handle_clear_agent_chat_messages)
545
+ # [v1.24.5] 所有用户-Agent私聊会话查看
546
+ r.add_get("/api/private-chats/sessions", self.handle_get_private_chat_sessions)
487
547
  # ── 部门管理 ──
488
548
  r.add_get("/api/departments", self.handle_dept_tree)
489
549
  r.add_post("/api/departments", self.handle_create_dept)
@@ -8411,6 +8471,103 @@ window.addEventListener('beforeunload', function() {{
8411
8471
  deleted = mgr.clear_agent_chats(group_id=gid)
8412
8472
  return web.json_response({"ok": True, "deleted": deleted})
8413
8473
 
8474
+ # ── [v1.24.5] 所有用户-Agent 私聊会话 API ──
8475
+
8476
+ async def handle_get_private_chat_sessions(self, request):
8477
+ """GET /api/private-chats/sessions - 获取所有用户与 Agent 的私聊会话列表
8478
+ 支持 ?agent=xxx 按指定 agent 筛选
8479
+ """
8480
+ if not self.core.memory:
8481
+ return web.json_response([])
8482
+ agent = request.query.get("agent", "")
8483
+ conn = self.core.memory._get_conn()
8484
+ _hidden = ('llm_output', 'llm_input', 'tool_result_raw', 'conversation_insight')
8485
+
8486
+ if agent:
8487
+ # 按 agent 路径筛选:旧格式 session_id LIKE '{agent}_%' 或新格式 metadata 包含 agent_path
8488
+ metadata_pattern = '%"agent_path":"' + agent + '"%'
8489
+ rows = conn.execute(
8490
+ "SELECT m.session_id, COUNT(*) as msg_count, MAX(m.created_at) as last_active "
8491
+ "FROM memories m "
8492
+ "WHERE m.category = 'session' AND m.role != '' "
8493
+ "AND m.key NOT IN (?,?,?,?) "
8494
+ "AND (m.session_id LIKE ? OR (m.session_id LIKE 'sess_%' AND m.metadata LIKE ?)) "
8495
+ "GROUP BY m.session_id ORDER BY last_active DESC LIMIT 200",
8496
+ (*_hidden, agent + "_%", metadata_pattern)
8497
+ ).fetchall()
8498
+ else:
8499
+ # 所有非群聊会话(session_id 不以 'grp_' 开头)
8500
+ rows = conn.execute(
8501
+ "SELECT m.session_id, COUNT(*) as msg_count, MAX(m.created_at) as last_active "
8502
+ "FROM memories m "
8503
+ "WHERE m.category = 'session' AND m.role != '' "
8504
+ "AND m.key NOT IN (?,?,?,?) "
8505
+ "AND m.session_id NOT LIKE 'grp_%' "
8506
+ "GROUP BY m.session_id ORDER BY last_active DESC LIMIT 200",
8507
+ (*_hidden,)
8508
+ ).fetchall()
8509
+
8510
+ # 获取每个 agent 的名称映射
8511
+ agents_db = self._agent_store.list_all() if hasattr(self, '_agent_store') else []
8512
+ agent_map = {}
8513
+ for ac in agents_db:
8514
+ agent_map[ac.path] = ac.name or ac.path
8515
+
8516
+ sessions = []
8517
+ sids = [r["session_id"] for r in rows]
8518
+ # 批量获取会话名称
8519
+ name_map = self.core.memory.list_session_names(sids) if sids else {}
8520
+
8521
+ for r in rows:
8522
+ sid = r["session_id"]
8523
+ # 推断 agent 路径
8524
+ agent_path = ''
8525
+ agent_name = ''
8526
+ if sid.startswith('grp_'):
8527
+ continue
8528
+ # 从新格式 metadata 中提取 agent_path
8529
+ meta_row = conn.execute(
8530
+ "SELECT metadata FROM memories WHERE session_id = ? AND metadata LIKE '%agent_path%' LIMIT 1",
8531
+ (sid,)
8532
+ ).fetchone()
8533
+ if meta_row and meta_row["metadata"]:
8534
+ try:
8535
+ meta = json.loads(meta_row["metadata"])
8536
+ agent_path = meta.get("agent_path", "")
8537
+ except:
8538
+ pass
8539
+ # 旧格式 fallback: 从 session_id 前缀提取
8540
+ if not agent_path:
8541
+ if '_web_' in sid:
8542
+ agent_path = sid.split('_web_')[0]
8543
+ elif '_cli_' in sid:
8544
+ agent_path = sid.split('_cli_')[0]
8545
+ else:
8546
+ agent_path = sid.split('_')[0] if '_' in sid else 'default'
8547
+
8548
+ agent_name = agent_map.get(agent_path, agent_path)
8549
+ # 获取最后一条用户消息作为预览
8550
+ preview = ''
8551
+ preview_row = conn.execute(
8552
+ "SELECT content FROM memories WHERE session_id = ? AND role = 'user' AND category = 'session' "
8553
+ "ORDER BY created_at DESC LIMIT 1",
8554
+ (sid,)
8555
+ ).fetchone()
8556
+ if preview_row:
8557
+ preview = (preview_row["content"] or "")[:100]
8558
+
8559
+ sessions.append({
8560
+ "id": sid,
8561
+ "agent_path": agent_path,
8562
+ "agent_name": agent_name,
8563
+ "messages": r["msg_count"],
8564
+ "last": r["last_active"],
8565
+ "display_name": name_map.get(sid, ""),
8566
+ "preview": preview,
8567
+ })
8568
+
8569
+ return web.json_response(sessions)
8570
+
8414
8571
  async def start(self, port: int = 8767, host: str = "127.0.0.1"):
8415
8572
  # 加载禁用技能列表
8416
8573
  self._load_disabled_skills()
@@ -1,30 +1,36 @@
1
1
  /**
2
- * [v1.23.37] Agent间私聊记录查看模块
3
- * 路径: /api/agent-chat/pairs, /api/agent-chat/messages
2
+ * [v1.24.5] 私聊记录查看模块
3
+ * 支持查看所有用户-Agent私聊会话 + Agent间群聊私聊记录
4
+ * 路径:
5
+ * /api/private-chats/sessions - 所有用户-Agent私聊(支持 ?agent=xxx 筛选)
6
+ * /api/agent-chat/pairs - Agent间私聊对
7
+ * /api/agent-chat/messages - Agent间私聊详情
4
8
  */
5
9
  async function renderAgentChat() {
6
10
  const content = $('content');
7
11
  if (!content) return;
8
12
 
9
- let currentFilter = { group_id: '', from_agent: '', to_agent: '' };
13
+ let currentTab = 'user-agent'; // 'user-agent' | 'agent-agent'
14
+ let currentFilter = { group_id: '', agent: '' };
10
15
 
11
16
  function esc(s) { return (s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
12
17
 
13
- async function loadGroups() {
14
- try {
15
- const data = await api('/api/groups');
16
- return Array.isArray(data) ? data : (data.groups || data.data || []);
17
- } catch { return []; }
18
+ // ── 数据加载 ──
19
+ async function loadPrivateSessions() {
20
+ const q = new URLSearchParams();
21
+ if (currentFilter.agent) q.set('agent', currentFilter.agent);
22
+ const data = await api('/api/private-chats/sessions?' + q.toString());
23
+ return Array.isArray(data) ? data : [];
18
24
  }
19
25
 
20
- async function loadPairs() {
26
+ async function loadAgentPairs() {
21
27
  const q = new URLSearchParams();
22
28
  if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
23
29
  const data = await api('/api/agent-chat/pairs?' + q.toString());
24
30
  return Array.isArray(data) ? data : [];
25
31
  }
26
32
 
27
- async function loadMessages(fromAgent, toAgent) {
33
+ async function loadAgentMessages(fromAgent, toAgent) {
28
34
  const q = new URLSearchParams();
29
35
  if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
30
36
  if (fromAgent) q.set('from_agent', fromAgent);
@@ -34,79 +40,187 @@ async function renderAgentChat() {
34
40
  return Array.isArray(data) ? data : [];
35
41
  }
36
42
 
43
+ async function loadGroups() {
44
+ try {
45
+ const data = await api('/api/groups');
46
+ return Array.isArray(data) ? data : (data.groups || data.data || []);
47
+ } catch { return []; }
48
+ }
49
+
50
+ async function loadAgents() {
51
+ if (allAgentsCache.length) return allAgentsCache;
52
+ const data = await api('/api/agents');
53
+ allAgentsCache = Array.isArray(data) ? data : [];
54
+ return allAgentsCache;
55
+ }
56
+
37
57
  function formatTime(ts) {
38
58
  if (!ts) return '';
39
- const d = new Date(ts * 1000);
59
+ const d = new Date(ts * 1000 || ts);
40
60
  const pad = n => String(n).padStart(2, '0');
41
- return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
42
- }
43
-
44
- function truncate(s, len) {
45
- if (!s) return '';
46
- return s.length > len ? s.slice(0, len) + '...' : s;
61
+ if (isNaN(d.getTime())) return String(ts);
62
+ return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
47
63
  }
48
64
 
49
65
  // ── 主界面 ──
50
66
  content.innerHTML = `
51
67
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
52
68
  <div>
53
- <h2 style="font-size:20px;font-weight:600;color:var(--text)">Agent 私聊记录</h2>
54
- <p style="font-size:13px;color:var(--text3);margin-top:4px">查看群聊中 Agent 之间的私下沟通记录</p>
55
- </div>
56
- <div style="display:flex;gap:8px">
57
- <button id="acRefreshBtn" onclick="window._acRefresh()" style="padding:6px 14px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;font-size:13px">
58
- 刷新
59
- </button>
60
- <button id="acClearBtn" onclick="window._acClearAll()" style="padding:6px 14px;background:var(--danger);color:#fff;border:none;border-radius:var(--radius);cursor:pointer;font-size:13px">
61
- 清空全部
62
- </button>
69
+ <h2 style="font-size:20px;font-weight:600;color:var(--text)">私聊记录</h2>
70
+ <p style="font-size:13px;color:var(--text3);margin-top:4px">查看用户与 Agent 的私聊会话,以及群聊中 Agent 间私下沟通</p>
63
71
  </div>
72
+ <button id="acRefreshBtn" onclick="window._acRefresh()" style="padding:6px 14px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;font-size:13px">
73
+ 刷新
74
+ </button>
75
+ </div>
76
+ <!-- Tab 切换 -->
77
+ <div style="display:flex;gap:0;margin-bottom:16px;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;width:fit-content">
78
+ <div id="acTabUA" onclick="window._acSwitchTab('user-agent')" style="padding:8px 20px;cursor:pointer;font-size:13px;font-weight:600;background:var(--accent);color:#fff">👤 用户-Agent 私聊</div>
79
+ <div id="acTabAA" onclick="window._acSwitchTab('agent-agent')" style="padding:8px 20px;cursor:pointer;font-size:13px;font-weight:600;background:var(--bg3);color:var(--text2)">🤖 Agent间私聊</div>
64
80
  </div>
65
- <div style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
66
- <select id="acGroupFilter" onchange="window._acFilterChange()" style="padding:6px 10px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:13px;min-width:160px">
81
+ <!-- 筛选区 -->
82
+ <div id="acFilters" style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
83
+ <select id="acAgentFilter" onchange="window._acFilterChange()" style="padding:6px 10px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:13px;min-width:180px">
84
+ <option value="">全部 Agent</option>
85
+ </select>
86
+ <select id="acGroupFilter" onchange="window._acFilterChange()" style="display:none;padding:6px 10px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:13px;min-width:160px">
67
87
  <option value="">全部群聊</option>
68
88
  </select>
69
89
  </div>
70
- <div style="display:grid;grid-template-columns:340px 1fr;gap:16px;min-height:500px" id="acLayout">
71
- <div id="acPairsPanel" style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
72
- <div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">聊天对象</div>
73
- <div id="acPairsList" style="flex:1;overflow-y:auto;padding:8px">
74
- <div style="text-align:center;color:var(--text3);padding:40px;font-size:13px">加载中...</div>
75
- </div>
76
- </div>
77
- <div id="acChatPanel" style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
78
- <div id="acChatHeader" style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">选择左侧聊天对象查看详情</div>
79
- <div id="acChatMessages" style="flex:1;overflow-y:auto;padding:16px">
80
- <div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>
81
- </div>
82
- </div>
83
- </div>
90
+ <!-- 内容区 -->
91
+ <div id="acContent"></div>
84
92
  `;
85
93
 
86
- // ── 加载群列表 ──
94
+ // ── 加载 Agent 列表(用于筛选下拉) ──
95
+ const agents = await loadAgents();
96
+ const agentSel = $('acAgentFilter');
97
+ agents.forEach(a => {
98
+ const opt = document.createElement('option');
99
+ opt.value = a.path || '';
100
+ opt.textContent = (a.avatar_emoji || '') + ' ' + escHtml(a.name || a.path);
101
+ agentSel.appendChild(opt);
102
+ });
103
+
104
+ // ── 加载群列表(Agent间私聊 tab 使用) ──
87
105
  const groups = await loadGroups();
88
- const sel = $('acGroupFilter');
106
+ const groupSel = $('acGroupFilter');
89
107
  groups.forEach(g => {
90
108
  const opt = document.createElement('option');
91
109
  opt.value = g.id || g.group_id || '';
92
110
  opt.textContent = g.name || g.group_name || g.id || '未命名';
93
- sel.appendChild(opt);
111
+ groupSel.appendChild(opt);
94
112
  });
95
113
 
96
- // ── 渲染聊天对列表 ──
97
- async function renderPairs() {
98
- const pairs = await loadPairs();
99
- const list = $('acPairsList');
114
+ // ── Tab 切换 ──
115
+ window._acSwitchTab = (tab) => {
116
+ currentTab = tab;
117
+ const tabUA = $('acTabUA');
118
+ const tabAA = $('acTabAA');
119
+ const agentFilter = $('acAgentFilter');
120
+ const groupFilter = $('acGroupFilter');
121
+ if (tab === 'user-agent') {
122
+ tabUA.style.background = 'var(--accent)'; tabUA.style.color = '#fff';
123
+ tabAA.style.background = 'var(--bg3)'; tabAA.style.color = 'var(--text2)';
124
+ agentFilter.style.display = '';
125
+ groupFilter.style.display = 'none';
126
+ } else {
127
+ tabAA.style.background = 'var(--accent)'; tabAA.style.color = '#fff';
128
+ tabUA.style.background = 'var(--bg3)'; tabUA.style.color = 'var(--text2)';
129
+ agentFilter.style.display = 'none';
130
+ groupFilter.style.display = '';
131
+ }
132
+ renderContent();
133
+ };
134
+
135
+ window._acFilterChange = () => {
136
+ currentFilter.agent = $('acAgentFilter').value;
137
+ currentFilter.group_id = $('acGroupFilter').value;
138
+ renderContent();
139
+ };
140
+
141
+ // ── 用户-Agent 私聊列表渲染 ──
142
+ async function renderUserAgentSessions() {
143
+ const sessions = await loadPrivateSessions();
144
+ const container = $('acContent');
145
+
146
+ if (!sessions.length) {
147
+ container.innerHTML = '<div class="empty">暂无用户-Agent私聊会话</div>';
148
+ return;
149
+ }
150
+
151
+ // 按 agent 分组
152
+ const grouped = {};
153
+ for (const s of sessions) {
154
+ const key = s.agent_path || 'default';
155
+ if (!grouped[key]) grouped[key] = { name: s.agent_name || key, sessions: [] };
156
+ grouped[key].sessions.push(s);
157
+ }
158
+
159
+ let html = `<div style="font-size:13px;color:var(--text3);margin-bottom:12px">共 ${sessions.length} 个私聊会话,${Object.keys(grouped).length} 个 Agent</div>`;
160
+
161
+ for (const [agentPath, group] of Object.entries(grouped)) {
162
+ html += `<div style="margin-bottom:20px">
163
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer" onclick="this.parentElement.querySelector('.ac-group-sessions').classList.toggle('hidden')">
164
+ <span style="font-size:16px;transition:transform .2s">📂</span>
165
+ <span style="font-size:14px;font-weight:600;color:var(--text)">${esc(group.name)}</span>
166
+ <span style="font-size:11px;color:var(--text3);background:var(--bg3);padding:2px 8px;border-radius:10px">${group.sessions.length} 个会话</span>
167
+ </div>
168
+ <div class="ac-group-sessions" style="margin-left:24px">`;
169
+
170
+ for (const s of group.sessions) {
171
+ const displayName = s.display_name || s.id;
172
+ const truncated = displayName.length > 40 ? displayName.slice(0, 40) + '...' : displayName;
173
+ const preview = s.preview ? esc(s.preview) : '<span style="color:var(--text3)">无预览</span>';
174
+ const lastTime = formatTime(s.last);
175
+ const agentPathSafe = esc(s.agent_path).replace(/'/g, "\\'");
176
+ html += `<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-radius:6px;margin-bottom:4px;cursor:pointer;transition:background .15s;border:1px solid var(--border)"
177
+ onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background='transparent'"
178
+ onclick="window._acViewSession('${esc(s.id)}','${agentPathSafe}')">
179
+ <div style="flex:1;min-width:0">
180
+ <div style="font-size:13px;font-weight:500;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(s.id)}">${esc(truncated)}</div>
181
+ <div style="font-size:11px;color:var(--text3);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${preview}</div>
182
+ </div>
183
+ <div style="flex-shrink:0;text-align:right;margin-left:12px">
184
+ <div style="font-size:11px;color:var(--text3)">${lastTime}</div>
185
+ <div style="font-size:11px;color:var(--text3)">${s.messages} 条消息</div>
186
+ </div>
187
+ </div>`;
188
+ }
189
+ html += '</div></div>';
190
+ }
191
+ container.innerHTML = html;
192
+ }
193
+
194
+ // ── 查看私聊会话消息 ──
195
+ window._acViewSession = async (sid, agentPath) => {
196
+ // 复用 admin-sessions.js 的 viewSession 逻辑
197
+ window._viewSessionSid = sid;
198
+ window._viewSessionOffset = 0;
199
+ _navSubState = 'view:' + sid;
200
+ navigateTo('agentchat', 'view:' + sid, _loadSessionMessages);
201
+ };
202
+
203
+ // ── Agent间私聊渲染 ──
204
+ async function renderAgentAgentChats() {
205
+ const pairs = await loadAgentPairs();
206
+ const container = $('acContent');
207
+
100
208
  if (!pairs.length) {
101
- list.innerHTML = '<div style="text-align:center;color:var(--text3);padding:40px;font-size:13px">暂无私聊记录</div>';
209
+ container.innerHTML = '<div class="empty">暂无Agent间私聊记录</div>';
102
210
  return;
103
211
  }
104
- list.innerHTML = pairs.map(p => {
212
+
213
+ let html = `<div style="display:grid;grid-template-columns:340px 1fr;gap:16px;min-height:500px">
214
+ <div style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
215
+ <div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">聊天对象</div>
216
+ <div id="acPairsList" style="flex:1;overflow-y:auto;padding:8px">`;
217
+
218
+ for (const p of pairs) {
105
219
  const a = esc(p.from_name || p.from_agent);
106
220
  const b = esc(p.to_name || p.to_agent);
107
221
  const last = formatTime(p.last_ts);
108
222
  const count = p.cnt || p.count || 0;
109
- return `<div onclick="window._acSelectPair('${esc(p.from_agent)}','${esc(p.to_agent)}','${a}','${b}')"
223
+ html += `<div onclick="window._acSelectPair('${esc(p.from_agent)}','${esc(p.to_agent)}','${a}','${b}')"
110
224
  style="padding:10px 12px;border-radius:6px;cursor:pointer;margin-bottom:4px;transition:background .15s"
111
225
  onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background='transparent'">
112
226
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
@@ -115,21 +229,36 @@ async function renderAgentChat() {
115
229
  </div>
116
230
  <div style="font-size:11px;color:var(--text3)">最后沟通: ${last}</div>
117
231
  </div>`;
118
- }).join('');
232
+ }
233
+
234
+ html += `</div></div>
235
+ <div style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
236
+ <div id="acChatHeader" style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">选择左侧聊天对象查看详情</div>
237
+ <div id="acChatMessages" style="flex:1;overflow-y:auto;padding:16px">
238
+ <div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>
239
+ </div>
240
+ </div>
241
+ </div>`;
242
+
243
+ container.innerHTML = html;
244
+
245
+ // Agent间私聊清空按钮
246
+ const clearBtnHtml = `<button onclick="window._acClearAgentChats()" style="margin-top:12px;padding:6px 14px;background:var(--danger);color:#fff;border:none;border-radius:var(--radius);cursor:pointer;font-size:13px">清空Agent间私聊</button>`;
247
+ container.insertAdjacentHTML('beforeend', clearBtnHtml);
119
248
  }
120
249
 
121
- // ── 渲染聊天记录 ──
122
- async function renderMessages(fromAgent, toAgent, fromName, toName) {
250
+ // ── Agent间私聊消息查看 ──
251
+ window._acSelectPair = async (fromAgent, toAgent, fromName, toName) => {
123
252
  const header = $('acChatHeader');
124
- header.innerHTML = `<span style="color:var(--accent)">${esc(fromName)}</span> ↔ <span style="color:var(--accent)">${esc(toName)}</span> 的私聊记录`;
253
+ if (header) header.innerHTML = `<span style="color:var(--accent)">${esc(fromName)}</span> ↔ <span style="color:var(--accent)">${esc(toName)}</span> 的私聊记录`;
125
254
 
126
- const msgs = await loadMessages(fromAgent, toAgent);
255
+ const msgs = await loadAgentMessages(fromAgent, toAgent);
127
256
  const container = $('acChatMessages');
257
+ if (!container) return;
128
258
  if (!msgs.length) {
129
259
  container.innerHTML = '<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无记录</div>';
130
260
  return;
131
261
  }
132
- // 按 time 正序显示
133
262
  const sorted = [...msgs].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
134
263
  container.innerHTML = sorted.map(m => {
135
264
  const isFrom = m.from_agent === fromAgent;
@@ -145,30 +274,33 @@ async function renderAgentChat() {
145
274
  </div>`;
146
275
  }).join('');
147
276
  container.scrollTop = container.scrollHeight;
148
- }
149
-
150
- // ── 全局方法 ──
151
- window._acRefresh = async () => { await renderPairs(); };
152
- window._acFilterChange = () => {
153
- currentFilter.group_id = $('acGroupFilter').value;
154
- renderPairs();
155
277
  };
156
- window._acSelectPair = (fa, ta, fn, tn) => { renderMessages(fa, ta, fn, tn); };
157
- window._acClearAll = async () => {
158
- if (!confirm('确定要清空所有私聊记录吗?此操作不可恢复。')) return;
278
+
279
+ window._acClearAgentChats = async () => {
280
+ if (!confirm('确定要清空所有Agent间私聊记录吗?此操作不可恢复。')) return;
159
281
  const q = new URLSearchParams();
160
282
  if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
161
283
  try {
162
284
  const data = await api('/api/agent-chat/messages?' + q.toString(), { method: 'DELETE' });
163
- toast('已清空 ' + (data.deleted || 0) + ' 条记录');
164
- await renderPairs();
165
- $('acChatMessages').innerHTML = '<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>';
166
- $('acChatHeader').textContent = '选择左侧聊天对象查看详情';
167
- } catch (e) { toast('清空失败: ' + (e.message || e), 'error'); }
285
+ showToast('已清空 ' + (data.deleted || 0) + ' 条记录', 'success');
286
+ renderContent();
287
+ } catch (e) { showToast('清空失败: ' + (e.message || e), 'danger'); }
168
288
  };
169
289
 
290
+ // ── 渲染主内容 ──
291
+ async function renderContent() {
292
+ if (currentTab === 'user-agent') {
293
+ await renderUserAgentSessions();
294
+ } else {
295
+ await renderAgentAgentChats();
296
+ }
297
+ }
298
+
299
+ // ── 全局刷新 ──
300
+ window._acRefresh = async () => { await renderContent(); };
301
+
170
302
  // 初始加载
171
- await renderPairs();
303
+ await renderContent();
172
304
  }
173
305
 
174
306
  if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
@@ -1,7 +1,7 @@
1
1
  /* ── admin-core.js — Shared infrastructure (loaded FIRST) ── */
2
2
 
3
3
  const API='';
4
- const pages={dashboard:'📊 仪表盘',agents:'🤖 Agent 管理',platforms:'🌐 聊天平台',organization:'🏢 组织管理',departments:'🏛 部门管理',agentchat:'🔒 Agent私聊',sessions:'💬 会话管理',memory:'🧠 记忆管理',permissions:'🔑 权限管理',llm:'🧬 大模型设置',system:'⚙️ 系统配置',executor:'🔧 执行引擎',skills:'🛠 技能管理',files:'📁 工作目录',logs:'📜 查看日志',tasks:'📌 任务记录'};
4
+ const pages={dashboard:'📊 仪表盘',agents:'🤖 Agent 管理',platforms:'🌐 聊天平台',organization:'🏢 组织管理',departments:'🏛 部门管理',agentchat:'💬 私聊记录',sessions:'📋 会话管理',memory:'🧠 记忆管理',permissions:'🔑 权限管理',llm:'🧬 大模型设置',system:'⚙️ 系统配置',executor:'🔧 执行引擎',skills:'🛠 技能管理',files:'📁 工作目录',logs:'📜 查看日志',tasks:'📌 任务记录'};
5
5
  let currentPage='dashboard';
6
6
  let allAgentsCache=[];
7
7
  let allModelsCache=[];