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,7 +13,7 @@ | ||
| 13 | > - 定时调度:使用 Claude Code 原生 CronCreate + cron.json 持久化 | 13 | > - 定时调度:使用 Claude Code 原生 CronCreate + cron.json 持久化 |
| 14 | > - **OKR 目标系统**:`data/goals/` 集中管理,点分层级 ID,全局可追溯 | 14 | > - **OKR 目标系统**:`data/goals/` 集中管理,点分层级 ID,全局可追溯 |
| 15 | > - **FDE 流水线**:Hunt→Digest→Evolving 三层训练架构,已经端到端验证通过 | 15 | > - **FDE 流水线**:Hunt→Digest→Evolving 三层训练架构,已经端到端验证通过 |
| 16 | -> - **双路径产出**:元 Agent 产出同时写入 workspace/ + agents/ 两个位置 | 16 | +> - **双路径产出**:元 Agent 产出同时写入 train/ + agents/ 两个位置 |
| 17 | > - **Skills 扫描注入**:buildSystemPrompt 扫描所有 .claude/skills/ 子目录 | 17 | > - **Skills 扫描注入**:buildSystemPrompt 扫描所有 .claude/skills/ 子目录 |
| 18 | 18 | ||
| 19 | --- | 19 | --- |
| @@ -448,7 +448,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 | @@ -448,7 +448,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 | ||
| 448 | - [x] 每个元 Agent 的 SKILL.md 泛化定义 (不硬编码领域知识) | 448 | - [x] 每个元 Agent 的 SKILL.md 泛化定义 (不硬编码领域知识) |
| 449 | - [x] L1/L2 依赖技能复制到各元 Agent 的 `.claude/skills/` | 449 | - [x] L1/L2 依赖技能复制到各元 Agent 的 `.claude/skills/` |
| 450 | - [x] buildSystemPrompt 扫描所有 skills 子目录 (关键修复) | 450 | - [x] buildSystemPrompt 扫描所有 skills 子目录 (关键修复) |
| 451 | -- [x] 双路径产出 (workspace/ + agents/) | 451 | +- [x] 双路径产出 (train/ + agents/) |
| 452 | - [x] CEO Agent 端到端训练: Hunt(1461行) → Digest(1593行) → Evolve(92/100) | 452 | - [x] CEO Agent 端到端训练: Hunt(1461行) → Digest(1593行) → Evolve(92/100) |
| 453 | - [x] retrain-pipeline.js 对齐新 agent 名 | 453 | - [x] retrain-pipeline.js 对齐新 agent 名 |
| 454 | - 详见: `docs/system-design/21-fde-pipeline-architecture.md` | 454 | - 详见: `docs/system-design/21-fde-pipeline-architecture.md` |
| @@ -501,7 +501,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 | @@ -501,7 +501,7 @@ Task 进入 → 海马体检索 → 命中? → 小脑快速执行 | ||
| 501 | | 7 | OKR 数据结构 | 扁平 nodes Map (O(1) 查找) | 03-24 | | 501 | | 7 | OKR 数据结构 | 扁平 nodes Map (O(1) 查找) | 03-24 | |
| 502 | | 8 | OKR 进度算法 | 叶子节点完成率 | 03-24 | | 502 | | 8 | OKR 进度算法 | 叶子节点完成率 | 03-24 | |
| 503 | | 9 | plan.md 更新 | 行级匹配 + 列拆分 (兼容粗体等格式) | 03-24 | | 503 | | 9 | plan.md 更新 | 行级匹配 + 列拆分 (兼容粗体等格式) | 03-24 | |
| 504 | -| 10 | FDE 双路径产出 | workspace/(共享) + agents/(资产) 同时写入 | 03-24 | | 504 | +| 10 | FDE 双路径产出 | train/(共享) + agents/(资产) 同时写入 | 03-24 | |
| 505 | | 11 | 元 Agent 依赖技能加载 | buildSystemPrompt 扫描所有 .claude/skills/ 子目录 | 03-24 | | 505 | | 11 | 元 Agent 依赖技能加载 | buildSystemPrompt 扫描所有 .claude/skills/ 子目录 | 03-24 | |
| 506 | | 12 | retrain-pipeline 对齐 | l3-hunt→hunter, l2-digest→digester, innovator-evolver→evolver | 03-25 | | 506 | | 12 | retrain-pipeline 对齐 | l3-hunt→hunter, l2-digest→digester, innovator-evolver→evolver | 03-25 | |
| 507 | | 13 | Thinking 控制 | `--effort low` 禁用 extended thinking,config.json `cli.effort` 按 Agent 配置 | 03-27 | | 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,12 +95,12 @@ Innovator 不在四层组织架构(Master/CXO/Role/Skill)内。它是四层 | ||
| 95 | → [CALL: meta_hunt] | 95 | → [CALL: meta_hunt] |
| 96 | → 搜索社交媒体爬虫的最佳实践、方法论 | 96 | → 搜索社交媒体爬虫的最佳实践、方法论 |
| 97 | → 爬取案例、API 文档 | 97 | → 爬取案例、API 文档 |
| 98 | - → 写入 data/agents/social_crawler/workspace/ | 98 | + → 写入 data/agents/social_crawler/train/ |
| 99 | │ | 99 | │ |
| 100 | ▼ | 100 | ▼ |
| 101 | 4️⃣ Digest (Claude CLI 执行型 Agent) | 101 | 4️⃣ Digest (Claude CLI 执行型 Agent) |
| 102 | → [CALL: meta_digest] | 102 | → [CALL: meta_digest] |
| 103 | - → 读取 workspace/ 调研数据 | 103 | + → 读取 train/ 调研数据 |
| 104 | → 提炼 knowledge/knowhow.md + knowledge/benchmark.md | 104 | → 提炼 knowledge/knowhow.md + knowledge/benchmark.md |
| 105 | → 生成 .claude/skills/main_skill/SKILL.md | 105 | → 生成 .claude/skills/main_skill/SKILL.md |
| 106 | │ | 106 | │ |
| @@ -238,7 +238,8 @@ data/agents/{new-agent-id}/ | @@ -238,7 +238,8 @@ data/agents/{new-agent-id}/ | ||
| 238 | │ ├── meta.json | 238 | │ ├── meta.json |
| 239 | │ ├── history.json | 239 | │ ├── history.json |
| 240 | │ └── {msgId}/messages.log | 240 | │ └── {msgId}/messages.log |
| 241 | -├── workspace/ | 241 | +├── workspace/ ← Agent 工作区 |
| 242 | +├── train/ ← 训练过程数据 (Hunt/Digest/Evolve) | ||
| 242 | ├── logs/ | 243 | ├── logs/ |
| 243 | └── config.json ← status: "ready" | 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,10 +40,11 @@ data/agents/{agent_name}/ | ||
| 40 | │ ├── sessions/ | 40 | │ ├── sessions/ |
| 41 | │ └── daily/ | 41 | │ └── daily/ |
| 42 | ├── knowledge/ ← Digest 阶段写入 knowhow/benchmark | 42 | ├── knowledge/ ← Digest 阶段写入 knowhow/benchmark |
| 43 | -├── workspace/ ← Hunt 阶段写入调研数据 | 43 | +├── workspace/ ← 自治工作区 |
| 44 | │ ├── code/ | 44 | │ ├── code/ |
| 45 | │ ├── data/ | 45 | │ ├── data/ |
| 46 | │ └── assets/ | 46 | │ └── assets/ |
| 47 | +├── train/ ← Hunt 阶段写入的调研和训练数据 | ||
| 47 | └── logs/ | 48 | └── logs/ |
| 48 | ``` | 49 | ``` |
| 49 | 50 | ||
| @@ -51,10 +52,10 @@ data/agents/{agent_name}/ | @@ -51,10 +52,10 @@ data/agents/{agent_name}/ | ||
| 51 | 52 | ||
| 52 | **执行型子 Agent,有完整工具权限。** | 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 | ├── knowhow.md ← 方法论调研 | 59 | ├── knowhow.md ← 方法论调研 |
| 59 | ├── toplist.md ← 标杆案例 | 60 | ├── toplist.md ← 标杆案例 |
| 60 | ├── agent_profile.md ← Agent 人格画像(思维、风格、工具、心跳、用户关系) | 61 | ├── agent_profile.md ← Agent 人格画像(思维、风格、工具、心跳、用户关系) |
| @@ -65,10 +66,10 @@ data/agents/{agent_name}/workspace/ | @@ -65,10 +66,10 @@ data/agents/{agent_name}/workspace/ | ||
| 65 | 66 | ||
| 66 | ### 阶段 3: Digest(知识消化) | 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 | ├── knowledge/knowhow.md ← 精炼后的方法论 | 74 | ├── knowledge/knowhow.md ← 精炼后的方法论 |
| 74 | ├── knowledge/benchmark.md ← 评分标准(≥5个维度) | 75 | ├── knowledge/benchmark.md ← 评分标准(≥5个维度) |
| @@ -160,9 +161,9 @@ const effectiveWorkDir = agent.workDir || this.workDir; | @@ -160,9 +161,9 @@ const effectiveWorkDir = agent.workDir || this.workDir; | ||
| 160 | 所有指向其它 Agent 的路径都必须使用 **`../{agent_name}/`** 格式(上一级的兄弟目录): | 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,7 +173,7 @@ data/agents/ | ||
| 172 | │ ├── .claude/skills/ ← l1/l2 知识文件 | 173 | │ ├── .claude/skills/ ← l1/l2 知识文件 |
| 173 | │ └── workspace/ ← innovator 自己的工作区 | 174 | │ └── workspace/ ← innovator 自己的工作区 |
| 174 | ├── poetry_writer/ ← ../ poetry_writer/ 可以访问到 | 175 | ├── poetry_writer/ ← ../ poetry_writer/ 可以访问到 |
| 175 | -│ ├── workspace/ ← Hunt 写入这里 | 176 | +│ ├── train/ ← Hunt 写入这里 |
| 176 | │ ├── knowledge/ ← Digest 写入这里 | 177 | │ ├── knowledge/ ← Digest 写入这里 |
| 177 | │ └── .claude/skills/ ← Digest 生成 SKILL.md 到这里 | 178 | │ └── .claude/skills/ ← Digest 生成 SKILL.md 到这里 |
| 178 | └── other_agent/ | 179 | └── other_agent/ |
| @@ -224,11 +225,11 @@ Innovator (--tools "", 纯文本编排) | @@ -224,11 +225,11 @@ Innovator (--tools "", 纯文本编排) | ||
| 224 | ├── [CALL: meta_hunt] ──▶ Claude CLI (cwd: innovator-001/) | 225 | ├── [CALL: meta_hunt] ──▶ Claude CLI (cwd: innovator-001/) |
| 225 | │ 读取 .claude/skills/l1-*/l2-* 知识 | 226 | │ 读取 .claude/skills/l1-*/l2-* 知识 |
| 226 | │ 用 Bash/curl 搜索调研 | 227 | │ 用 Bash/curl 搜索调研 |
| 227 | - │ 写入 data/agents/poetry_writer/workspace/ | 228 | + │ 写入 data/agents/poetry_writer/train/ |
| 228 | │ └── knowhow.md + toplist.md + agent_profile.md | 229 | │ └── knowhow.md + toplist.md + agent_profile.md |
| 229 | │ | 230 | │ |
| 230 | ├── [CALL: meta_digest] ──▶ Claude CLI (cwd: innovator-001/) | 231 | ├── [CALL: meta_digest] ──▶ Claude CLI (cwd: innovator-001/) |
| 231 | - │ 读取 workspace/ (knowhow + toplist + agent_profile) | 232 | + │ 读取 train/ (knowhow + toplist + agent_profile) |
| 232 | │ 提炼写入 knowledge/knowhow.md + benchmark.md | 233 | │ 提炼写入 knowledge/knowhow.md + benchmark.md |
| 233 | │ 生成 .claude/skills/main_skill/SKILL.md | 234 | │ 生成 .claude/skills/main_skill/SKILL.md |
| 234 | │ 生成 Bootstrap: agent.md, soul.md, tools.md, | 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,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 | | `data/agents/{targetAgentId}/{phase}/` | Agent 资产,归属目标 Agent | `agents/ceo/hunt/` | | 65 | | `data/agents/{targetAgentId}/{phase}/` | Agent 资产,归属目标 Agent | `agents/ceo/hunt/` | |
| 66 | 66 | ||
| 67 | ``` | 67 | ``` |
| 68 | data/ | 68 | data/ |
| 69 | -├── {companyId}/workspace/{targetAgentId}/ ← 公司共享工作区 (元 Agent 间共享) | 69 | +├── {companyId}/train/{targetAgentId}/ ← 训练数据区 (元 Agent 间共享) |
| 70 | │ ├── hunt/ | 70 | │ ├── hunt/ |
| 71 | │ ├── digest/ | 71 | │ ├── digest/ |
| 72 | │ └── evolve/ | 72 | │ └── evolve/ |
| @@ -80,7 +80,7 @@ data/ | @@ -80,7 +80,7 @@ data/ | ||
| 80 | > **注意**: 全局元 Agent (如 Hunter/Digester/Evolver) 位于 `system/agents/`。 | 80 | > **注意**: 全局元 Agent (如 Hunter/Digester/Evolver) 位于 `system/agents/`。 |
| 81 | > 对于全局训练任务,路径前缀为 `system/`。 | 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,7 +157,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ | ||
| 157 | -d '{ | 157 | -d '{ |
| 158 | "from": "innovator", | 158 | "from": "innovator", |
| 159 | "to": "hunter", | 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 | "priority": "P0", | 161 | "priority": "P0", |
| 162 | "autoExecute": true | 162 | "autoExecute": true |
| 163 | }' | 163 | }' |
| @@ -171,7 +171,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ | @@ -171,7 +171,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ | ||
| 171 | -d '{ | 171 | -d '{ |
| 172 | "from": "innovator", | 172 | "from": "innovator", |
| 173 | "to": "digester", | 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 | "priority": "P0", | 175 | "priority": "P0", |
| 176 | "autoExecute": true | 176 | "autoExecute": true |
| 177 | }' | 177 | }' |
| @@ -185,7 +185,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ | @@ -185,7 +185,7 @@ curl -s -X POST http://localhost:3000/api/agent/task/dispatch \ | ||
| 185 | -d '{ | 185 | -d '{ |
| 186 | "from": "innovator", | 186 | "from": "innovator", |
| 187 | "to": "evolver", | 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 | "priority": "P0", | 189 | "priority": "P0", |
| 190 | "autoExecute": true | 190 | "autoExecute": true |
| 191 | }' | 191 | }' |
| @@ -264,9 +264,9 @@ benchmark_score: 92 | @@ -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 | | 2 | 依赖技能放 `.claude/skills/` | Claude CLI 自动发现,buildSystemPrompt 自动注入 | 03-24 | | 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 | | 4 | Evolver 安装最终 SKILL.md | 训练完成后直接更新目标 Agent 的核心技能 | 03-24 | | 270 | | 4 | Evolver 安装最终 SKILL.md | 训练完成后直接更新目标 Agent 的核心技能 | 03-24 | |
| 271 | | 5 | retrain-pipeline 对齐 agent 名 | `l3-hunt`→`hunter`, `l2-digest`→`digester`, `innovator-evolver`→`evolver` | 03-25 | | 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,6 +278,40 @@ router.get('/skills/:name/updates', h((req, res) => { | ||
| 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 }); | 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 | // ── Pipelines (简化版) ── | 315 | // ── Pipelines (简化版) ── |
| 282 | // 读取 .data/pipelines.json | 316 | // 读取 .data/pipelines.json |
| 283 | let pipelinesData = { pipelines: [] }; | 317 | let pipelinesData = { pipelines: [] }; |
| @@ -497,7 +531,95 @@ async function _runSkillCreation(job, resumeFromStep = null) { | @@ -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 | module.exports = router; | 621 | module.exports = router; |
| 501 | module.exports._jobManager = jobManager; | 622 | module.exports._jobManager = jobManager; |
| 502 | module.exports._runSkillCreation = _runSkillCreation; | 623 | module.exports._runSkillCreation = _runSkillCreation; |
| 503 | module.exports._runFeedbackIteration = _runFeedbackIteration; | 624 | module.exports._runFeedbackIteration = _runFeedbackIteration; |
| 625 | +module.exports._runAgentInvocation = _runAgentInvocation; |
system/agents/master/.claude/scheduled_tasks.json
| @@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
| 5 | "cron": "*/5 * * * *", | 5 | "cron": "*/5 * * * *", |
| 6 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", | 6 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", |
| 7 | "createdAt": 1775231859266, | 7 | "createdAt": 1775231859266, |
| 8 | - "lastFiredAt": 1775237249762, | 8 | + "lastFiredAt": 1775237849327, |
| 9 | "recurring": true | 9 | "recurring": true |
| 10 | }, | 10 | }, |
| 11 | { | 11 | { |
| @@ -13,7 +13,7 @@ | @@ -13,7 +13,7 @@ | ||
| 13 | "cron": "*/30 * * * *", | 13 | "cron": "*/30 * * * *", |
| 14 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", | 14 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", |
| 15 | "createdAt": 1775231859272, | 15 | "createdAt": 1775231859272, |
| 16 | - "lastFiredAt": 1775236092651, | 16 | + "lastFiredAt": 1775237892371, |
| 17 | "recurring": true | 17 | "recurring": true |
| 18 | }, | 18 | }, |
| 19 | { | 19 | { |
| @@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
| 21 | "cron": "*/5 * * * *", | 21 | "cron": "*/5 * * * *", |
| 22 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", | 22 | "prompt": "执行系统巡检:读取 plan.md 和 todo.md,检查所有任务状态。已完成的更新标记,卡住的分析原因后自主处理(重新分发或修复),未开始的按优先级立即分发。不要请示用户,直接行动。", |
| 23 | "createdAt": 1775235820776, | 23 | "createdAt": 1775235820776, |
| 24 | - "lastFiredAt": 1775237170680, | 24 | + "lastFiredAt": 1775237771254, |
| 25 | "recurring": true | 25 | "recurring": true |
| 26 | }, | 26 | }, |
| 27 | { | 27 | { |
| @@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
| 29 | "cron": "*/30 * * * *", | 29 | "cron": "*/30 * * * *", |
| 30 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", | 30 | "prompt": "生成系统状态日报,统计所有 Agent 任务完成率和异常情况,写入 memory/daily/ 今日文件。", |
| 31 | "createdAt": 1775235825837, | 31 | "createdAt": 1775235825837, |
| 32 | + "lastFiredAt": 1775237771254, | ||
| 32 | "recurring": true | 33 | "recurring": true |
| 33 | } | 34 | } |
| 34 | ] | 35 | ] |
system/templates/claude-youtube-skills/config.json
web/src/views/AgentChat.vue
| @@ -265,6 +265,14 @@ | @@ -265,6 +265,14 @@ | ||
| 265 | </div> | 265 | </div> |
| 266 | 266 | ||
| 267 | <div class="detail-section"> | 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 | <div class="detail-label">统计</div> | 276 | <div class="detail-label">统计</div> |
| 269 | <div class="detail-stats"> | 277 | <div class="detail-stats"> |
| 270 | <div class="stat-item"><span class="stat-num">{{ fileTree?.totalFiles || 0 }}</span> 文件</div> | 278 | <div class="stat-item"><span class="stat-num">{{ fileTree?.totalFiles || 0 }}</span> 文件</div> |
| @@ -766,6 +774,28 @@ async function deleteConversation(convId) { | @@ -766,6 +774,28 @@ async function deleteConversation(convId) { | ||
| 766 | } catch (e) { console.error('Delete conv error:', e) } | 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 | // ─── Chat (SSE) ─── | 799 | // ─── Chat (SSE) ─── |
| 770 | 800 | ||
| 771 | async function handleSend() { | 801 | async function handleSend() { |
web/src/views/AgentList.vue
| @@ -61,6 +61,14 @@ | @@ -61,6 +61,14 @@ | ||
| 61 | </div> | 61 | </div> |
| 62 | </div> | 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 | <div class="detail-actions"> | 72 | <div class="detail-actions"> |
| 65 | <button class="action-btn" @click="$router.push(`/agent/${selectedAgent}`)">💬 对话</button> | 73 | <button class="action-btn" @click="$router.push(`/agent/${selectedAgent}`)">💬 对话</button> |
| 66 | </div> | 74 | </div> |
| @@ -165,6 +173,28 @@ onMounted(async () => { | @@ -165,6 +173,28 @@ onMounted(async () => { | ||
| 165 | await selectAgent(selectedAgent.value) | 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 | </script> | 198 | </script> |
| 169 | 199 | ||
| 170 | <!-- FileNode 子组件 (递归文件树) --> | 200 | <!-- FileNode 子组件 (递归文件树) --> |
web/src/views/CompanyDashboard.vue
| @@ -190,6 +190,16 @@ | @@ -190,6 +190,16 @@ | ||
| 190 | <div v-for="skill in agentSkills" :key="skill" class="skill-chip">⚡ {{ skill }}</div> | 190 | <div v-for="skill in agentSkills" :key="skill" class="skill-chip">⚡ {{ skill }}</div> |
| 191 | </div> | 191 | </div> |
| 192 | <div v-else class="empty-mini" style="padding:12px;">暂无技能(可通过 Innovator 训练获得)</div> | 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 | </div> | 203 | </div> |
| 194 | </template> | 204 | </template> |
| 195 | </div> | 205 | </div> |
| @@ -1170,21 +1180,43 @@ function goToAgentChat(agentId) { | @@ -1170,21 +1180,43 @@ function goToAgentChat(agentId) { | ||
| 1170 | router.push(`/${selectedCompany.value.id}/agents/${agentId}`) | 1180 | router.push(`/${selectedCompany.value.id}/agents/${agentId}`) |
| 1171 | } | 1181 | } |
| 1172 | 1182 | ||
| 1173 | -async function toggleAgentSkills(agentId) { | 1183 | +function toggleAgentSkills(agentId) { |
| 1174 | if (expandedAgent.value === agentId) { | 1184 | if (expandedAgent.value === agentId) { |
| 1175 | expandedAgent.value = null | 1185 | expandedAgent.value = null |
| 1176 | return | 1186 | return |
| 1177 | } | 1187 | } |
| 1178 | // Fetch skills from backend | 1188 | // Fetch skills from backend |
| 1179 | try { | 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 | } catch { | 1195 | } catch { |
| 1183 | agentSkills.value = [] | 1196 | agentSkills.value = [] |
| 1184 | } | 1197 | } |
| 1185 | expandedAgent.value = agentId | 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 | function triggerTrain(agentId) { | 1220 | function triggerTrain(agentId) { |
| 1189 | if (!selectedCompany.value) return | 1221 | if (!selectedCompany.value) return |
| 1190 | targetTrainAgent.value = agentId | 1222 | targetTrainAgent.value = agentId |