Commit 417ac5a52ed870b6059d998f8c96717194b5352b
1 parent
c3242821
feat: display Agent API Endpoint in frontend and update templates
Showing
11 changed files
with
392 additions
and
31 deletions
docs/architecture-v5.md
| ... | ... | @@ -13,7 +13,7 @@ |
| 13 | 13 | > - 定时调度:使用 Claude Code 原生 CronCreate + cron.json 持久化 |
| 14 | 14 | > - **OKR 目标系统**:`data/goals/` 集中管理,点分层级 ID,全局可追溯 |
| 15 | 15 | > - **FDE 流水线**:Hunt→Digest→Evolving 三层训练架构,已经端到端验证通过 |
| 16 | -> - **双路径产出**:元 Agent 产出同时写入 workspace/ + agents/ 两个位置 | |
| 16 | +> - **双路径产出**:元 Agent 产出同时写入 train/ + agents/ 两个位置 | |
| 17 | 17 | > - **Skills 扫描注入**:buildSystemPrompt 扫描所有 .claude/skills/ 子目录 |
| 18 | 18 | |
| 19 | 19 | --- |
| ... | ... | @@ -448,7 +448,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 |
| 448 | 448 | - [x] 每个元 Agent 的 SKILL.md 泛化定义 (不硬编码领域知识) |
| 449 | 449 | - [x] L1/L2 依赖技能复制到各元 Agent 的 `.claude/skills/` |
| 450 | 450 | - [x] buildSystemPrompt 扫描所有 skills 子目录 (关键修复) |
| 451 | -- [x] 双路径产出 (workspace/ + agents/) | |
| 451 | +- [x] 双路径产出 (train/ + agents/) | |
| 452 | 452 | - [x] CEO Agent 端到端训练: Hunt(1461行) → Digest(1593行) → Evolve(92/100) |
| 453 | 453 | - [x] retrain-pipeline.js 对齐新 agent 名 |
| 454 | 454 | - 详见: `docs/system-design/21-fde-pipeline-architecture.md` |
| ... | ... | @@ -501,7 +501,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 |
| 501 | 501 | | 7 | OKR 数据结构 | 扁平 nodes Map (O(1) 查找) | 03-24 | |
| 502 | 502 | | 8 | OKR 进度算法 | 叶子节点完成率 | 03-24 | |
| 503 | 503 | | 9 | plan.md 更新 | 行级匹配 + 列拆分 (兼容粗体等格式) | 03-24 | |
| 504 | -| 10 | FDE 双路径产出 | workspace/(共享) + agents/(资产) 同时写入 | 03-24 | | |
| 504 | +| 10 | FDE 双路径产出 | train/(共享) + agents/(资产) 同时写入 | 03-24 | | |
| 505 | 505 | | 11 | 元 Agent 依赖技能加载 | buildSystemPrompt 扫描所有 .claude/skills/ 子目录 | 03-24 | |
| 506 | 506 | | 12 | retrain-pipeline 对齐 | l3-hunt→hunter, l2-digest→digester, innovator-evolver→evolver | 03-25 | |
| 507 | 507 | | 13 | Thinking 控制 | `--effort low` 禁用 extended thinking,config.json `cli.effort` 按 Agent 配置 | 03-27 | | ... | ... |
docs/open-api-manual.md
0 → 100644
| 1 | +# ArenaSK Open API 外部调用操作手册 | |
| 2 | + | |
| 3 | +本手册详细说明了如何通过外部系统(如第三方业务系统、自动化流、外部脚本)跨平台调用 LemonBoss 平台内部已经部署的任意 Agent。 | |
| 4 | + | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## 1. 认证配置 (Authentication) | |
| 8 | + | |
| 9 | +调用系统的任何开放接口前,必须在 HTTP 标头 (Headers) 中携带你申请/配置的 API Key 密钥。 | |
| 10 | + | |
| 11 | +**系统配置位置**: | |
| 12 | +当前系统支持的 API Key 配置位于项目根目录的 `.env` 文件内: | |
| 13 | +```ini | |
| 14 | +OPEN_API_KEYS=sk-test-key-001,sk-prod-key-... | |
| 15 | +``` | |
| 16 | + | |
| 17 | +**请求方式**: | |
| 18 | +无论你用什么语言发送请求,请确保 HTTP Headers 包含以下任意一种格式: | |
| 19 | +- `Authorization: Bearer <API_KEY>` (推荐) | |
| 20 | +- `X-API-Key: <API_KEY>` | |
| 21 | + | |
| 22 | +--- | |
| 23 | + | |
| 24 | +## 2. 核心调用流程 | |
| 25 | + | |
| 26 | +因为我们的 Agent 需要进行「思考与执行 (Plan & Execute)」,单次任务通常耗时数分钟。为了防止网络超时断连,我们的接口设计完全**异步化 (Asynchronous)**: | |
| 27 | + | |
| 28 | +> **完整调用链路:** | |
| 29 | +> 👉 外部发送请求 `invoke` ➔ 系统接管并**返回单号 jobId** ➔ 外部每隔几秒根据 jobId去查进度 `poll` ➔ 遇到 **`completed`** 则提取输出。 | |
| 30 | + | |
| 31 | +### 接口 ①:指派任务并获取 JobId (Invoke) | |
| 32 | + | |
| 33 | +**🔹 端点**:`POST /api/open/companies/:companyId/agents/:agentId/invoke` | |
| 34 | +**🔹 作用**:唤起指定公司和指定名称的 Agent 执行任务。 | |
| 35 | + | |
| 36 | +#### 请求参数 (Body) | |
| 37 | + | |
| 38 | +| 参数名 | 类型 | 必填 | 说明 |示例| | |
| 39 | +|---------|-------|------|------|------| | |
| 40 | +| `task` | String | ✅ | 需要 Agent 帮您执行的自然语言任务。 | `"帮我爬取并总结最新的 AI agent 趋势"`| | |
| 41 | +| `contextFiles` | Array | 选填 | 外部传递前置文件,Agent可以据此进行**记忆连接或文档问答**。|见下文详述| | |
| 42 | +| `webhookUrl` | String | 选填 | 回调地址。当任务完结时主动推送结果至该URL。| `"https://your-app/webhook"`| | |
| 43 | + | |
| 44 | +#### 代码示例 (cURL) | |
| 45 | +```bash | |
| 46 | +curl -X POST http://YOUR_SERVER_IP:3010/api/open/companies/test/agents/claude-youtube-skills/invoke \ | |
| 47 | + -H "Authorization: Bearer sk-test-key-001" \ | |
| 48 | + -H "Content-Type: application/json" \ | |
| 49 | + -d '{ | |
| 50 | + "task": "基于上次总结的结果,帮我提取其中所有的产品名称并生成CSV格式", | |
| 51 | + "contextFiles": [ | |
| 52 | + { | |
| 53 | + "name": "previous_result.md", | |
| 54 | + "content": "这是一段之前的调查结果......" | |
| 55 | + } | |
| 56 | + ] | |
| 57 | + }' | |
| 58 | +``` | |
| 59 | + | |
| 60 | +#### 返回值 | |
| 61 | +接口会光速返回 HTTP 202 的响应该结果: | |
| 62 | +```json | |
| 63 | +{ | |
| 64 | + "jobId": "job-1712461931-abcdef12", | |
| 65 | + "status": "queued", | |
| 66 | + "message": "Agent invocation job queued. Poll GET /api/open/jobs/job-1712461931-abcdef12 for status." | |
| 67 | +} | |
| 68 | +``` | |
| 69 | +此时你必须要把 `jobId` 记录下来! | |
| 70 | + | |
| 71 | +--- | |
| 72 | + | |
| 73 | +### 接口 ②:轮询任务执行结果 (Status Polling) | |
| 74 | + | |
| 75 | +我们推荐外部系统(例如你的脚本中)使用 `while` 循环或定时任务,**每隔 3~5 秒钟**调用一次该接口查询。 | |
| 76 | + | |
| 77 | +**🔹 端点**:`GET /api/open/jobs/:jobId` | |
| 78 | + | |
| 79 | +#### 代码示例 (cURL) | |
| 80 | +```bash | |
| 81 | +curl "http://YOUR_SERVER_IP:3010/api/open/jobs/job-1712461931-abcdef12" \ | |
| 82 | + -H "Authorization: Bearer sk-test-key-001" | |
| 83 | +``` | |
| 84 | + | |
| 85 | +#### 返回值 | |
| 86 | +你可以通过判断返回体中的 `status` 字段来得知进度: | |
| 87 | + | |
| 88 | +**1. 正在执行时(Running / Queued)** | |
| 89 | +```json | |
| 90 | +{ | |
| 91 | + "jobId": "job-1712461931-abcdef12", | |
| 92 | + "status": "running", | |
| 93 | + "progress": "Executing agent task...", | |
| 94 | + "createdAt": "2026-04-07T12:00:00Z" | |
| 95 | +} | |
| 96 | +``` | |
| 97 | + | |
| 98 | +**2. 任务已完成时(Completed)** 🌟 此刻可以提取最终结果: | |
| 99 | +```json | |
| 100 | +{ | |
| 101 | + "jobId": "job-1712461931-abcdef12", | |
| 102 | + "status": "completed", | |
| 103 | + "progress": "Invocation completed", | |
| 104 | + "result": { | |
| 105 | + "output": "你好!我已经完成了产品名称提取。这里是转换后的 CSV 格式内容...\n..." | |
| 106 | + } | |
| 107 | +} | |
| 108 | +``` | |
| 109 | + | |
| 110 | +**3. 任务失败异常时(Failed)** | |
| 111 | +```json | |
| 112 | +{ | |
| 113 | + "jobId": "job-1712461931-abcdef12", | |
| 114 | + "status": "failed", | |
| 115 | + "error": "Agent execution failed: process timeout." | |
| 116 | +} | |
| 117 | +``` | |
| 118 | + | |
| 119 | +--- | |
| 120 | + | |
| 121 | +## 3. 高级用法指南 | |
| 122 | + | |
| 123 | +### 如何保持“上下文记忆”或者“持续对话”? | |
| 124 | + | |
| 125 | +Agent 是没有自动长久记忆跨系统调用流的。你在下一次调用相同的这个 Agent(比如第二天再调一次)时,如果想让它知道前面发生了什么,或者传递新的数据报告给它,**一定要使用 `contextFiles` 数组结构!**。 | |
| 126 | + | |
| 127 | +外部系统提取了上次的 `result.output` 后,在下次调用的 Body 中: | |
| 128 | +```json | |
| 129 | +{ | |
| 130 | + "task": "基于我附加给你的日志总结,开展新一轮的数据清理", | |
| 131 | + "contextFiles": [ | |
| 132 | + { | |
| 133 | + "name": "logs-from-yesterday.txt", | |
| 134 | + "content": "....提取的昨日任务输出大段文本...." | |
| 135 | + }, | |
| 136 | + { | |
| 137 | + "name": "data.csv", | |
| 138 | + "content": "id,name\n1,apple" | |
| 139 | + } | |
| 140 | + ] | |
| 141 | +} | |
| 142 | +``` | |
| 143 | +系统底座会自动将这 2 个对象落地成文件,以文本阅读的视角送入 Agent 的大脑供其深度理解(支持上万字)。通过这种 “外部系统充当记忆保管员 + API 动态投喂上下文文件” 的组合拳,就可以把这些 Agent 变成可控、随时插拔的智能化函数库! | ... | ... |
docs/system-design/15-innovator-meta-agent.md
| ... | ... | @@ -95,12 +95,12 @@ Innovator 不在四层组织架构(Master/CXO/Role/Skill)内。它是四层 |
| 95 | 95 | → [CALL: meta_hunt] |
| 96 | 96 | → 搜索社交媒体爬虫的最佳实践、方法论 |
| 97 | 97 | → 爬取案例、API 文档 |
| 98 | - → 写入 data/agents/social_crawler/workspace/ | |
| 98 | + → 写入 data/agents/social_crawler/train/ | |
| 99 | 99 | │ |
| 100 | 100 | ▼ |
| 101 | 101 | 4️⃣ Digest (Claude CLI 执行型 Agent) |
| 102 | 102 | → [CALL: meta_digest] |
| 103 | - → 读取 workspace/ 调研数据 | |
| 103 | + → 读取 train/ 调研数据 | |
| 104 | 104 | → 提炼 knowledge/knowhow.md + knowledge/benchmark.md |
| 105 | 105 | → 生成 .claude/skills/main_skill/SKILL.md |
| 106 | 106 | │ |
| ... | ... | @@ -238,7 +238,8 @@ data/agents/{new-agent-id}/ |
| 238 | 238 | │ ├── meta.json |
| 239 | 239 | │ ├── history.json |
| 240 | 240 | │ └── {msgId}/messages.log |
| 241 | -├── workspace/ | |
| 241 | +├── workspace/ ← Agent 工作区 | |
| 242 | +├── train/ ← 训练过程数据 (Hunt/Digest/Evolve) | |
| 242 | 243 | ├── logs/ |
| 243 | 244 | └── config.json ← status: "ready" |
| 244 | 245 | ``` | ... | ... |
docs/system-design/20-scaffold-first-agent-creation.md
| ... | ... | @@ -40,10 +40,11 @@ data/agents/{agent_name}/ |
| 40 | 40 | │ ├── sessions/ |
| 41 | 41 | │ └── daily/ |
| 42 | 42 | ├── knowledge/ ← Digest 阶段写入 knowhow/benchmark |
| 43 | -├── workspace/ ← Hunt 阶段写入调研数据 | |
| 43 | +├── workspace/ ← 自治工作区 | |
| 44 | 44 | │ ├── code/ |
| 45 | 45 | │ ├── data/ |
| 46 | 46 | │ └── assets/ |
| 47 | +├── train/ ← Hunt 阶段写入的调研和训练数据 | |
| 47 | 48 | └── logs/ |
| 48 | 49 | ``` |
| 49 | 50 | |
| ... | ... | @@ -51,10 +52,10 @@ data/agents/{agent_name}/ |
| 51 | 52 | |
| 52 | 53 | **执行型子 Agent,有完整工具权限。** |
| 53 | 54 | |
| 54 | -meta_hunt 在新建 Agent 的目录下运行,将调研结果写入该 Agent 的 `workspace/` 目录: | |
| 55 | +meta_hunt 在新建 Agent 的目录下运行,将调研结果写入该 Agent 的 `train/` 目录: | |
| 55 | 56 | |
| 56 | 57 | ``` |
| 57 | -data/agents/{agent_name}/workspace/ | |
| 58 | +data/agents/{agent_name}/train/ | |
| 58 | 59 | ├── knowhow.md ← 方法论调研 |
| 59 | 60 | ├── toplist.md ← 标杆案例 |
| 60 | 61 | ├── agent_profile.md ← Agent 人格画像(思维、风格、工具、心跳、用户关系) |
| ... | ... | @@ -65,10 +66,10 @@ data/agents/{agent_name}/workspace/ |
| 65 | 66 | |
| 66 | 67 | ### 阶段 3: Digest(知识消化) |
| 67 | 68 | |
| 68 | -**执行型子 Agent,读取 workspace/ 的调研数据,提炼输出到 knowledge/ 和 SKILL.md,并基于 agent_profile 生成定制化 Bootstrap 文件。** | |
| 69 | +**执行型子 Agent,读取 train/ 的调研数据,提炼输出到 knowledge/ 和 SKILL.md,并基于 agent_profile 生成定制化 Bootstrap 文件。** | |
| 69 | 70 | |
| 70 | 71 | ``` |
| 71 | -输入: workspace/knowhow.md + workspace/toplist.md + workspace/agent_profile.md + workspace/crawl/ | |
| 72 | +输入: train/knowhow.md + train/toplist.md + train/agent_profile.md + train/crawl/ | |
| 72 | 73 | 输出: |
| 73 | 74 | ├── knowledge/knowhow.md ← 精炼后的方法论 |
| 74 | 75 | ├── knowledge/benchmark.md ← 评分标准(≥5个维度) |
| ... | ... | @@ -160,9 +161,9 @@ const effectiveWorkDir = agent.workDir || this.workDir; |
| 160 | 161 | 所有指向其它 Agent 的路径都必须使用 **`../{agent_name}/`** 格式(上一级的兄弟目录): |
| 161 | 162 | |
| 162 | 163 | ``` |
| 163 | -正确: ../{agent_name}/workspace/knowhow.md | |
| 164 | -错误: data/agents/{agent_name}/workspace/knowhow.md ← 会变成嵌套路径! | |
| 165 | -错误: workspace/knowhow.md ← 会写到 innovator-001 自己的目录 | |
| 164 | +正确: ../{agent_name}/train/knowhow.md | |
| 165 | +错误: data/agents/{agent_name}/train/knowhow.md ← 会变成嵌套路径! | |
| 166 | +错误: train/knowhow.md ← 会写到 innovator-001 自己的目录 | |
| 166 | 167 | ``` |
| 167 | 168 | |
| 168 | 169 | 目录结构关系: |
| ... | ... | @@ -172,7 +173,7 @@ data/agents/ |
| 172 | 173 | │ ├── .claude/skills/ ← l1/l2 知识文件 |
| 173 | 174 | │ └── workspace/ ← innovator 自己的工作区 |
| 174 | 175 | ├── poetry_writer/ ← ../ poetry_writer/ 可以访问到 |
| 175 | -│ ├── workspace/ ← Hunt 写入这里 | |
| 176 | +│ ├── train/ ← Hunt 写入这里 | |
| 176 | 177 | │ ├── knowledge/ ← Digest 写入这里 |
| 177 | 178 | │ └── .claude/skills/ ← Digest 生成 SKILL.md 到这里 |
| 178 | 179 | └── other_agent/ |
| ... | ... | @@ -224,11 +225,11 @@ Innovator (--tools "", 纯文本编排) |
| 224 | 225 | ├── [CALL: meta_hunt] ──▶ Claude CLI (cwd: innovator-001/) |
| 225 | 226 | │ 读取 .claude/skills/l1-*/l2-* 知识 |
| 226 | 227 | │ 用 Bash/curl 搜索调研 |
| 227 | - │ 写入 data/agents/poetry_writer/workspace/ | |
| 228 | + │ 写入 data/agents/poetry_writer/train/ | |
| 228 | 229 | │ └── knowhow.md + toplist.md + agent_profile.md |
| 229 | 230 | │ |
| 230 | 231 | ├── [CALL: meta_digest] ──▶ Claude CLI (cwd: innovator-001/) |
| 231 | - │ 读取 workspace/ (knowhow + toplist + agent_profile) | |
| 232 | + │ 读取 train/ (knowhow + toplist + agent_profile) | |
| 232 | 233 | │ 提炼写入 knowledge/knowhow.md + benchmark.md |
| 233 | 234 | │ 生成 .claude/skills/main_skill/SKILL.md |
| 234 | 235 | │ 生成 Bootstrap: agent.md, soul.md, tools.md, | ... | ... |
docs/system-design/21-fde-pipeline-architecture.md
| ... | ... | @@ -61,12 +61,12 @@ Hunter ────→ l1-search-engine, l1-web-fetch, l2-web-research, |
| 61 | 61 | |
| 62 | 62 | | 路径 | 用途 | 示例 | |
| 63 | 63 | |------|------|------| |
| 64 | -| `data/workspace/{targetAgentId}/{phase}/` | 共享工作区,元 Agent 间共享 | `workspace/ceo/hunt/` | | |
| 64 | +| `data/train/{targetAgentId}/{phase}/` | 综合训练区,元 Agent 间共享 | `train/ceo/hunt/` | | |
| 65 | 65 | | `data/agents/{targetAgentId}/{phase}/` | Agent 资产,归属目标 Agent | `agents/ceo/hunt/` | |
| 66 | 66 | |
| 67 | 67 | ``` |
| 68 | 68 | data/ |
| 69 | -├── {companyId}/workspace/{targetAgentId}/ ← 公司共享工作区 (元 Agent 间共享) | |
| 69 | +├── {companyId}/train/{targetAgentId}/ ← 训练数据区 (元 Agent 间共享) | |
| 70 | 70 | │ ├── hunt/ |
| 71 | 71 | │ ├── digest/ |
| 72 | 72 | │ └── evolve/ |
| ... | ... | @@ -80,7 +80,7 @@ data/ |
| 80 | 80 | > **注意**: 全局元 Agent (如 Hunter/Digester/Evolver) 位于 `system/agents/`。 |
| 81 | 81 | > 对于全局训练任务,路径前缀为 `system/`。 |
| 82 | 82 | |
| 83 | -**原则**: 先写到 workspace/,再用 `cp -r` 复制到 agents/。 | |
| 83 | +**原则**: 先写到 train/,再用 `cp -r` 复制到 agents/。 | |
| 84 | 84 | |
| 85 | 85 | --- |
| 86 | 86 | |
| ... | ... | @@ -157,7 +157,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ |
| 157 | 157 | -d '{ |
| 158 | 158 | "from": "innovator", |
| 159 | 159 | "to": "hunter", |
| 160 | - "task": "对 CEO 核心能力进行知识猎取。skillName: ceo_leadership, targetAgentId: ceo, domain: CEO战略管理与领导力, keywords: [CEO战略规划, OKR目标管理]。按 l3-hunt skill 执行。产出同时写入 ../../workspace/ceo/hunt/ 和 ../ceo/hunt/。", | |
| 160 | + "task": "对 CEO 核心能力进行知识猎取。skillName: ceo_leadership, targetAgentId: ceo, domain: CEO战略管理与领导力, keywords: [CEO战略规划, OKR目标管理]。按 l3-hunt skill 执行。产出同时写入 ../../train/ceo/hunt/ 和 ../ceo/hunt/。", | |
| 161 | 161 | "priority": "P0", |
| 162 | 162 | "autoExecute": true |
| 163 | 163 | }' |
| ... | ... | @@ -171,7 +171,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ |
| 171 | 171 | -d '{ |
| 172 | 172 | "from": "innovator", |
| 173 | 173 | "to": "digester", |
| 174 | - "task": "消化 CEO 调研数据。skillName: ceo_leadership, targetAgentId: ceo。输入: ../../workspace/ceo/hunt/。按 l2-digest skill 执行。产出同时写入 ../../workspace/ceo/digest/ 和 ../ceo/digest/。", | |
| 174 | + "task": "消化 CEO 调研数据。skillName: ceo_leadership, targetAgentId: ceo。输入: ../../train/ceo/hunt/。按 l2-digest skill 执行。产出同时写入 ../../train/ceo/digest/ 和 ../ceo/digest/。", | |
| 175 | 175 | "priority": "P0", |
| 176 | 176 | "autoExecute": true |
| 177 | 177 | }' |
| ... | ... | @@ -185,7 +185,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ |
| 185 | 185 | -d '{ |
| 186 | 186 | "from": "innovator", |
| 187 | 187 | "to": "evolver", |
| 188 | - "task": "训练 CEO 的 ceo_leadership 技能。targetAgentId: ceo。输入: ../../workspace/ceo/digest/。按 evolving skill 执行 Small Loop。passThreshold: 90, maxIterations: 5。产出同时写入 ../../workspace/ceo/evolve/ 和 ../ceo/evolve/。训练完成后安装到 ../ceo/.claude/skills/main_skill/SKILL.md。", | |
| 188 | + "task": "训练 CEO 的 ceo_leadership 技能。targetAgentId: ceo。输入: ../../train/ceo/digest/。按 evolving skill 执行 Small Loop。passThreshold: 90, maxIterations: 5。产出同时写入 ../../train/ceo/evolve/ 和 ../ceo/evolve/。训练完成后安装到 ../ceo/.claude/skills/main_skill/SKILL.md。", | |
| 189 | 189 | "priority": "P0", |
| 190 | 190 | "autoExecute": true |
| 191 | 191 | }' |
| ... | ... | @@ -264,9 +264,9 @@ benchmark_score: 92 |
| 264 | 264 | |
| 265 | 265 | | # | 决策 | 原因 | 日期 | |
| 266 | 266 | |---|------|------|------| |
| 267 | -| 1 | 双路径产出 (workspace + agents) | 元 Agent 间共享 + 目标 Agent 资产归属 | 03-24 | | |
| 267 | +| 1 | 双路径产出 (train + agents) | 元 Agent 间共享 + 目标 Agent 资产归属 | 03-24 | | |
| 268 | 268 | | 2 | 依赖技能放 `.claude/skills/` | Claude CLI 自动发现,buildSystemPrompt 自动注入 | 03-24 | |
| 269 | -| 3 | 先写 workspace 再 cp agents | workspace 为主源,agents 为副本 | 03-24 | | |
| 269 | +| 3 | 先写 train 再 cp agents | train 为主源,agents 为副本 | 03-24 | | |
| 270 | 270 | | 4 | Evolver 安装最终 SKILL.md | 训练完成后直接更新目标 Agent 的核心技能 | 03-24 | |
| 271 | 271 | | 5 | retrain-pipeline 对齐 agent 名 | `l3-hunt`→`hunter`, `l2-digest`→`digester`, `innovator-evolver`→`evolver` | 03-25 | |
| 272 | 272 | ... | ... |
server/routes/open-api.js
| ... | ... | @@ -278,6 +278,40 @@ router.get('/skills/:name/updates', h((req, res) => { |
| 278 | 278 | res.json({ skillName: req.params.name, latestVersion: skill.version, currentScore: skill.currentScore, hasUpdate: !!hasUpdate, changelog: hasUpdate ? changelog : undefined, downloadCount: skill.downloadCount || 0, updateCount: skill.updateCount || 0 }); |
| 279 | 279 | })); |
| 280 | 280 | |
| 281 | +// ── Company Agent Invocation ── | |
| 282 | +router.post('/companies/:companyId/agents/:agentId/invoke', h(async (req, res) => { | |
| 283 | + const { companyId, agentId } = req.params; | |
| 284 | + const { task, contextFiles, webhookUrl } = req.body; | |
| 285 | + | |
| 286 | + if (!task || typeof task !== 'string') { | |
| 287 | + return res.status(400).json({ error: 'Bad Request', message: 'The "task" field is required and must be a string.' }); | |
| 288 | + } | |
| 289 | + | |
| 290 | + // Create an async job | |
| 291 | + const job = jobManager.createJob({ | |
| 292 | + description: `Invoke Agent ${agentId} in Company ${companyId}`, | |
| 293 | + webhookUrl, | |
| 294 | + apiKey: req.apiKey, | |
| 295 | + user_id: req.user?.id | |
| 296 | + }); | |
| 297 | + | |
| 298 | + // Custom tracking fields | |
| 299 | + job.companyId = companyId; | |
| 300 | + job.agentId = agentId; | |
| 301 | + jobManager.updateJob(job.jobId, { status: 'queued', progress: 'Task is waiting for execution queue...' }); | |
| 302 | + | |
| 303 | + // Run in background without blocking | |
| 304 | + _runAgentInvocation(job, { companyId, agentId, task, contextFiles }).catch(err => { | |
| 305 | + jobManager.updateJob(job.jobId, { status: 'failed', error: err.message, progress: `Invocation failed: ${err.message}` }); | |
| 306 | + }); | |
| 307 | + | |
| 308 | + res.status(202).json({ | |
| 309 | + jobId: job.jobId, | |
| 310 | + status: job.status, | |
| 311 | + message: `Agent invocation job queued. Poll GET /api/open/jobs/${job.jobId} for status.` | |
| 312 | + }); | |
| 313 | +})); | |
| 314 | + | |
| 281 | 315 | // ── Pipelines (简化版) ── |
| 282 | 316 | // 读取 .data/pipelines.json |
| 283 | 317 | let pipelinesData = { pipelines: [] }; |
| ... | ... | @@ -497,7 +531,95 @@ async function _runSkillCreation(job, resumeFromStep = null) { |
| 497 | 531 | } |
| 498 | 532 | } |
| 499 | 533 | |
| 534 | +async function _runAgentInvocation(job, payload) { | |
| 535 | + const { companyId, agentId, task, contextFiles } = payload; | |
| 536 | + const path = require('path'); | |
| 537 | + const fs = require('fs'); | |
| 538 | + const { loadConfigFromSkills } = require('../src/core/skill-config-loader'); | |
| 539 | + const AgentManager = require('../src/core/agent-manager'); | |
| 540 | + const config = require('../config'); | |
| 541 | + | |
| 542 | + // Directory for company agent: data/companies/{companyId}/agents/{agentId} | |
| 543 | + const agentDir = path.resolve(__dirname, `../../data/companies/${companyId}/agents/${agentId}`); | |
| 544 | + if (!fs.existsSync(agentDir)) { | |
| 545 | + throw new Error(`Agent directory not found: companies/${companyId}/agents/${agentId}`); | |
| 546 | + } | |
| 547 | + | |
| 548 | + jobManager.updateJob(job.jobId, { status: 'running', progress: 'Initializing agent session...' }); | |
| 549 | + | |
| 550 | + let enrichedTask = task; | |
| 551 | + | |
| 552 | + // Process Context Files | |
| 553 | + if (Array.isArray(contextFiles) && contextFiles.length > 0) { | |
| 554 | + jobManager.updateJob(job.jobId, { progress: `Processing ${contextFiles.length} context files...` }); | |
| 555 | + | |
| 556 | + let contextBlock = '\n\n---\n📎 **附件上下文** (外部系统传入的数据):\n\n'; | |
| 557 | + const uploadDir = path.join(agentDir, 'uploads', job.jobId); | |
| 558 | + fs.mkdirSync(uploadDir, { recursive: true }); | |
| 559 | + | |
| 560 | + for (const file of contextFiles) { | |
| 561 | + if (file.name && file.content) { | |
| 562 | + // Sanitize file name | |
| 563 | + const safeName = file.name.replace(/[\\/:*?"<>|]/g, '_').slice(0, 100); | |
| 564 | + const filePath = path.join(uploadDir, safeName); | |
| 565 | + fs.writeFileSync(filePath, String(file.content), 'utf-8'); | |
| 566 | + | |
| 567 | + // Truncate for prompt injection if massive | |
| 568 | + const fileContent = String(file.content); | |
| 569 | + const displayContent = fileContent.length > 50000 | |
| 570 | + ? fileContent.substring(0, 50000) + '\\n... (截断)' | |
| 571 | + : fileContent; | |
| 572 | + | |
| 573 | + contextBlock += `### 📄 ${safeName}\n\`\`\`\n${displayContent}\n\`\`\`\n\n`; | |
| 574 | + } | |
| 575 | + } | |
| 576 | + | |
| 577 | + enrichedTask = contextBlock + '\n\n---\n' + enrichedTask; | |
| 578 | + } | |
| 579 | + | |
| 580 | + // Set up AgentManager | |
| 581 | + // We initialize the base config and instantiate manager | |
| 582 | + const backendsDir = path.join(__dirname, '../config/backends.json'); | |
| 583 | + const backends = fs.existsSync(backendsDir) ? JSON.parse(fs.readFileSync(backendsDir, 'utf-8')) : {}; | |
| 584 | + const skillsDir = config.skillsCodeDir; | |
| 585 | + const rootDir = path.resolve(__dirname, '../../'); | |
| 586 | + | |
| 587 | + const fullSystemConfig = loadConfigFromSkills(skillsDir, backends); | |
| 588 | + const manager = new AgentManager(fullSystemConfig, rootDir); | |
| 589 | + | |
| 590 | + const namespacedId = `${companyId}:${agentId}`; | |
| 591 | + | |
| 592 | + // Execute via AgentManager | |
| 593 | + jobManager.updateJob(job.jobId, { status: 'running', progress: 'Executing agent task...' }); | |
| 594 | + | |
| 595 | + let fullOutput = ''; | |
| 596 | + const onData = (agent) => (chunk) => { | |
| 597 | + if (chunk?.trim()) { | |
| 598 | + fullOutput += chunk; | |
| 599 | + } | |
| 600 | + }; | |
| 601 | + | |
| 602 | + try { | |
| 603 | + const result = await manager.callAgent(namespacedId, enrichedTask, { onData: onData(namespacedId) }); | |
| 604 | + | |
| 605 | + jobManager.updateJob(job.jobId, { | |
| 606 | + status: 'completed', | |
| 607 | + progress: 'Invocation completed', | |
| 608 | + result: { | |
| 609 | + output: result.output || fullOutput || '(Empty Output)' | |
| 610 | + } | |
| 611 | + }); | |
| 612 | + | |
| 613 | + try { await manager.closeAllSessions(); } catch {} | |
| 614 | + } catch (err) { | |
| 615 | + jobManager.updateJob(job.jobId, { status: 'failed', error: err.message, progress: `Agent execution failed: ${err.message}` }); | |
| 616 | + try { await manager.closeAllSessions(); } catch {} | |
| 617 | + throw err; | |
| 618 | + } | |
| 619 | +} | |
| 620 | + | |
| 500 | 621 | module.exports = router; |
| 501 | 622 | module.exports._jobManager = jobManager; |
| 502 | 623 | module.exports._runSkillCreation = _runSkillCreation; |
| 503 | 624 | module.exports._runFeedbackIteration = _runFeedbackIteration; |
| 625 | +module.exports._runAgentInvocation = _runAgentInvocation; | ... | ... |
system/agents/master/.claude/scheduled_tasks.json
| ... | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | "cron": "*/5 * * * *", |
| 6 | 6 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", |
| 7 | 7 | "createdAt": 1775231859266, |
| 8 | - "lastFiredAt": 1775237249762, | |
| 8 | + "lastFiredAt": 1775237849327, | |
| 9 | 9 | "recurring": true |
| 10 | 10 | }, |
| 11 | 11 | { |
| ... | ... | @@ -13,7 +13,7 @@ |
| 13 | 13 | "cron": "*/30 * * * *", |
| 14 | 14 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", |
| 15 | 15 | "createdAt": 1775231859272, |
| 16 | - "lastFiredAt": 1775236092651, | |
| 16 | + "lastFiredAt": 1775237892371, | |
| 17 | 17 | "recurring": true |
| 18 | 18 | }, |
| 19 | 19 | { |
| ... | ... | @@ -21,7 +21,7 @@ |
| 21 | 21 | "cron": "*/5 * * * *", |
| 22 | 22 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", |
| 23 | 23 | "createdAt": 1775235820776, |
| 24 | - "lastFiredAt": 1775237170680, | |
| 24 | + "lastFiredAt": 1775237771254, | |
| 25 | 25 | "recurring": true |
| 26 | 26 | }, |
| 27 | 27 | { |
| ... | ... | @@ -29,6 +29,7 @@ |
| 29 | 29 | "cron": "*/30 * * * *", |
| 30 | 30 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", |
| 31 | 31 | "createdAt": 1775235825837, |
| 32 | + "lastFiredAt": 1775237771254, | |
| 32 | 33 | "recurring": true |
| 33 | 34 | } |
| 34 | 35 | ] | ... | ... |
system/templates/claude-youtube-skills/config.json
web/src/views/AgentChat.vue
| ... | ... | @@ -265,6 +265,14 @@ |
| 265 | 265 | </div> |
| 266 | 266 | |
| 267 | 267 | <div class="detail-section"> |
| 268 | + <div class="detail-label">API 外部调用 Endpoint</div> | |
| 269 | + <div class="detail-value mono" style="word-break: break-all; margin-bottom: 8px;"> | |
| 270 | + {{ getAgentApiUrl(agentId) }} | |
| 271 | + </div> | |
| 272 | + <button class="action-btn" style="padding: 4px 10px; font-size: 11px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); color: #34d399; border-radius: 4px; cursor: pointer;" @click="copyText(getAgentApiUrl(agentId))">📋 复制 URL</button> | |
| 273 | + </div> | |
| 274 | + | |
| 275 | + <div class="detail-section"> | |
| 268 | 276 | <div class="detail-label">统计</div> |
| 269 | 277 | <div class="detail-stats"> |
| 270 | 278 | <div class="stat-item"><span class="stat-num">{{ fileTree?.totalFiles || 0 }}</span> 文件</div> |
| ... | ... | @@ -766,6 +774,28 @@ async function deleteConversation(convId) { |
| 766 | 774 | } catch (e) { console.error('Delete conv error:', e) } |
| 767 | 775 | } |
| 768 | 776 | |
| 777 | +function getAgentApiUrl(fullAgentId) { | |
| 778 | + if (!fullAgentId) return ''; | |
| 779 | + let companyId = "system"; | |
| 780 | + let targetAgentId = fullAgentId; | |
| 781 | + const parts = fullAgentId.split(':'); | |
| 782 | + if (parts.length > 1) { | |
| 783 | + companyId = parts[0]; | |
| 784 | + targetAgentId = parts.slice(1).join(':'); | |
| 785 | + } | |
| 786 | + const host = window.location.hostname; | |
| 787 | + return `http://${host}:3010/api/open/companies/${companyId}/agents/${targetAgentId}/invoke`; | |
| 788 | +} | |
| 789 | + | |
| 790 | +async function copyText(text) { | |
| 791 | + try { | |
| 792 | + await navigator.clipboard.writeText(text); | |
| 793 | + alert('✅ URL 已复制到剪贴板'); | |
| 794 | + } catch (e) { | |
| 795 | + console.error(e); | |
| 796 | + } | |
| 797 | +} | |
| 798 | + | |
| 769 | 799 | // ─── Chat (SSE) ─── |
| 770 | 800 | |
| 771 | 801 | async function handleSend() { | ... | ... |
web/src/views/AgentList.vue
| ... | ... | @@ -61,6 +61,14 @@ |
| 61 | 61 | </div> |
| 62 | 62 | </div> |
| 63 | 63 | |
| 64 | + <div class="detail-section"> | |
| 65 | + <div class="detail-label">API 外部调用 Endpoint</div> | |
| 66 | + <div class="detail-value mono" style="word-break: break-all; margin-bottom: 8px;"> | |
| 67 | + {{ getAgentApiUrl(detail.id) }} | |
| 68 | + </div> | |
| 69 | + <button class="action-btn" @click="copyText(getAgentApiUrl(detail.id))">📋 复制 URL</button> | |
| 70 | + </div> | |
| 71 | + | |
| 64 | 72 | <div class="detail-actions"> |
| 65 | 73 | <button class="action-btn" @click="$router.push(`/agent/${selectedAgent}`)">💬 对话</button> |
| 66 | 74 | </div> |
| ... | ... | @@ -165,6 +173,28 @@ onMounted(async () => { |
| 165 | 173 | await selectAgent(selectedAgent.value) |
| 166 | 174 | } |
| 167 | 175 | }) |
| 176 | + | |
| 177 | +function getAgentApiUrl(agentId) { | |
| 178 | + if (!agentId) return ''; | |
| 179 | + let companyId = "system"; | |
| 180 | + let targetAgentId = agentId; | |
| 181 | + const parts = agentId.split(':'); | |
| 182 | + if (parts.length > 1) { | |
| 183 | + companyId = parts[0]; | |
| 184 | + targetAgentId = parts.slice(1).join(':'); | |
| 185 | + } | |
| 186 | + const host = window.location.hostname; | |
| 187 | + return `http://${host}:3010/api/open/companies/${companyId}/agents/${targetAgentId}/invoke`; | |
| 188 | +} | |
| 189 | + | |
| 190 | +async function copyText(text) { | |
| 191 | + try { | |
| 192 | + await navigator.clipboard.writeText(text); | |
| 193 | + alert('✅ URL 已复制到剪贴板'); | |
| 194 | + } catch (e) { | |
| 195 | + console.error(e); | |
| 196 | + } | |
| 197 | +} | |
| 168 | 198 | </script> |
| 169 | 199 | |
| 170 | 200 | <!-- FileNode 子组件 (递归文件树) --> | ... | ... |
web/src/views/CompanyDashboard.vue
| ... | ... | @@ -190,6 +190,16 @@ |
| 190 | 190 | <div v-for="skill in agentSkills" :key="skill" class="skill-chip">⚡ {{ skill }}</div> |
| 191 | 191 | </div> |
| 192 | 192 | <div v-else class="empty-mini" style="padding:12px;">暂无技能(可通过 Innovator 训练获得)</div> |
| 193 | + | |
| 194 | + <div class="api-endpoint-section" style="margin-top: 16px; padding: 12px; border-top: 1px solid rgba(255,255,255,0.06);"> | |
| 195 | + <div style="font-size: 12px; font-weight: 600; color: rgba(255,255,255,0.5); margin-bottom: 8px;">🔗 外部调用 API Endpoint</div> | |
| 196 | + <div style="display: flex; gap: 8px; align-items: center;"> | |
| 197 | + <code style="flex:1; background: rgba(0,0,0,0.2); padding: 8px; border-radius: 6px; font-family: monospace; font-size: 11px; color: #34d399; word-break: break-all; border: 1px solid rgba(16,185,129,0.2);"> | |
| 198 | + {{ getAgentApiUrl(selectedCompany.id, agent.id) }} | |
| 199 | + </code> | |
| 200 | + <button class="btn-sm" @click="copyText(getAgentApiUrl(selectedCompany.id, agent.id))" style="padding: 6px 12px;">复制</button> | |
| 201 | + </div> | |
| 202 | + </div> | |
| 193 | 203 | </div> |
| 194 | 204 | </template> |
| 195 | 205 | </div> |
| ... | ... | @@ -1170,21 +1180,43 @@ function goToAgentChat(agentId) { |
| 1170 | 1180 | router.push(`/${selectedCompany.value.id}/agents/${agentId}`) |
| 1171 | 1181 | } |
| 1172 | 1182 | |
| 1173 | -async function toggleAgentSkills(agentId) { | |
| 1183 | +function toggleAgentSkills(agentId) { | |
| 1174 | 1184 | if (expandedAgent.value === agentId) { |
| 1175 | 1185 | expandedAgent.value = null |
| 1176 | 1186 | return |
| 1177 | 1187 | } |
| 1178 | 1188 | // Fetch skills from backend |
| 1179 | 1189 | try { |
| 1180 | - const { data } = await axios.get(`/api/company/${selectedCompany.value.id}/agents/${agentId}/skills`) | |
| 1181 | - agentSkills.value = data | |
| 1190 | + const { data } = axios.get(`/api/company/${selectedCompany.value.id}/agents/${agentId}/skills`).then(res => { | |
| 1191 | + agentSkills.value = res.data | |
| 1192 | + }).catch(e => { | |
| 1193 | + agentSkills.value = [] | |
| 1194 | + }); | |
| 1182 | 1195 | } catch { |
| 1183 | 1196 | agentSkills.value = [] |
| 1184 | 1197 | } |
| 1185 | 1198 | expandedAgent.value = agentId |
| 1186 | 1199 | } |
| 1187 | 1200 | |
| 1201 | +function getAgentApiUrl(companyId, agentId) { | |
| 1202 | + if (!agentId || !companyId) return ''; | |
| 1203 | + let targetAgentId = agentId; | |
| 1204 | + if (targetAgentId.startsWith(companyId + ':')) { | |
| 1205 | + targetAgentId = targetAgentId.substring(companyId.length + 1); | |
| 1206 | + } | |
| 1207 | + const host = window.location.hostname; | |
| 1208 | + return `http://${host}:3010/api/open/companies/${companyId}/agents/${targetAgentId}/invoke`; | |
| 1209 | +} | |
| 1210 | + | |
| 1211 | +async function copyText(text) { | |
| 1212 | + try { | |
| 1213 | + await navigator.clipboard.writeText(text); | |
| 1214 | + alert('✅ URL 已复制到剪贴板'); | |
| 1215 | + } catch (e) { | |
| 1216 | + console.error(e); | |
| 1217 | + } | |
| 1218 | +} | |
| 1219 | + | |
| 1188 | 1220 | function triggerTrain(agentId) { |
| 1189 | 1221 | if (!selectedCompany.value) return |
| 1190 | 1222 | targetTrainAgent.value = agentId | ... | ... |