Commit 417ac5a52ed870b6059d998f8c96717194b5352b

Authored by yibo
1 parent c3242821

feat: display Agent API Endpoint in frontend and update templates

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 &quot;&quot;, 纯文本编排)
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(&#39;/skills/:name/updates&#39;, h((req, res) =&gt; {
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
... ... @@ -22,6 +22,7 @@
22 22 "maxDiskMB": 1024,
23 23 "maxProcesses": 5
24 24 },
  25 + "companyId": "test",
25 26 "heartbeat": {
26 27 "intervalMinutes": 15,
27 28 "enabled": false
... ...
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 () =&gt; {
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
... ...