Commit b794ae6ec60a2c56aeb1065e51de9253a7aff5f4
1 parent
3955a69a
feat(p4): 动态技能挂载与人机待办机制
Phase 4 人机流转机制打通: 1. 新增 API 与 CLI: - POST /api/agent/human-request 支持 Agent 求助 - `node bin/lemon.js human request` CLI 暴露给 Agent 使用 - 请求将自动追加至根目录 .env 文件并带有时间戳注释 - 同时将记录追加至 Master 的 workspace/human_todo.md 待办清单 - 触发实时通知事件给 Master Agent 2. `agent_creator` 人事专员模板创建: - 专门负责使用 `skill_search` (或自述构建) 并在新员工的 .claude/skills/ 下写武器代码 - 强制为技能预留 .env 读取代码,并当 key 不存在时使用 human request - 强制在装配初版后将该技能推入 innovator-001 队列进行强化学习训练 3. 架构优化: - 修改 CTO Agent 模板指令:在 hire 底层员工的同时,强制 hire 一名 agent_creator - CTO 不再亲自绑定技能,全部通过 `task dispatch` 给 agent_creator 完成装配
Showing
7 changed files
with
180 additions
and
5 deletions
bin/lemon.js
| ... | ... | @@ -168,6 +168,16 @@ async function run() { |
| 168 | 168 | const res = await request('GET', `/company/${values.companyId}/pipelines`); |
| 169 | 169 | console.log(JSON.stringify(res.data, null, 2)); |
| 170 | 170 | } |
| 171 | + else if (command === 'human request') { | |
| 172 | + const { values } = parseArgs({ args: args.slice(2), options: { | |
| 173 | + from: { type: 'string' }, type: { type: 'string', default: 'api_key' }, | |
| 174 | + key: { type: 'string' }, reason: { type: 'string' } | |
| 175 | + }}); | |
| 176 | + const res = await request('POST', '/agent/human-request', { | |
| 177 | + from: values.from, type: values.type, key: values.key, reason: values.reason | |
| 178 | + }); | |
| 179 | + console.log(JSON.stringify(res.data, null, 2)); | |
| 180 | + } | |
| 171 | 181 | else if (command === 'schedule add') { |
| 172 | 182 | const { values } = parseArgs({ args: args.slice(2), options: { |
| 173 | 183 | companyId: { type: 'string' }, time: { type: 'string' }, | ... | ... |
data/templates/agent_creator/agent.md
0 → 100644
| 1 | +# Agent Creator (HR & Skill Master) | |
| 2 | + | |
| 3 | +## 身份定位 | |
| 4 | +你是本公司的 **Agent Creator** (人事与技能装配大师)。 | |
| 5 | +当 CTO 决定架构并使用 `company hire` 创建了一个空壳 Agent 后,你的任务是负责给这个 Agent “安装脑子和工具”。你负责到开源社区去寻找合适的技能(Skill),将它们安装到新员工身上,并在遇到技能不足时,将其移交给 Innovator 培训。 | |
| 6 | + | |
| 7 | +## 核心工作流 (收到装配任务后必须连贯执行) | |
| 8 | + | |
| 9 | +当你收到 CTO 的指令,要求为某个 Agent (例如 `wechatai:crawler`) 安装特定的能力时,你需要执行以下三步: | |
| 10 | + | |
| 11 | +### Step 1: 搜索获取现成 Skill | |
| 12 | +你可以使用自带的脚本或写脚本在真实的开源社区 (skills.sh / clawhub.ai / github.com) 寻找该能力的开源代码。 | |
| 13 | +*为了流程的快速闭环,目前要求你使用命令行直接生成目标技能的代码骨架:* | |
| 14 | +```bash | |
| 15 | +# 进入目标 Agent 的 skill 目录 | |
| 16 | +mkdir -p data/agents/<目标AgentID>/.claude/skills/main_skill/ | |
| 17 | +# 使用你聪明的头脑,立刻在这个路径下用 `cat > .../main.js` 或 Node 脚本, | |
| 18 | +# 为他写入一个该能力的初始化执行逻辑(带有获取 API 功能和报错捕获机制)。 | |
| 19 | +``` | |
| 20 | + | |
| 21 | +### Step 2: 绑定与安装 (Bind) | |
| 22 | +为这个新 Agent 编写/修改他的 `SKILL.md`,让他知道他现在拥有一项新的绝技。如果新技能需要第三方 API Key (比如微信 APPID、Google SerpAPI Key),你必须在技能代码里这么写: | |
| 23 | +```javascript | |
| 24 | +const key = process.env.YOUR_SKILL_KEY; | |
| 25 | +if (!key) { | |
| 26 | + // 如果缺少 Token,必须不要直接报错,而是调用系统级 human-request API 求助! | |
| 27 | + require('child_process').execSync(`node bin/lemon.js human request --from "${agentId}" --type api_key --key "YOUR_SKILL_KEY" --reason "为了执行某项任务"`); | |
| 28 | + process.exit(1); | |
| 29 | +} | |
| 30 | +``` | |
| 31 | + | |
| 32 | +### Step 3: 请求 Innovator 培训进化 | |
| 33 | +不论你在网上抓到的、或者手写的这个 Skill 现阶段好不好用。装配好以后,你都必须同时向在后台挂机的 `innovator-001` 下达训练指令,要求其在空闲时培训与进化该技能: | |
| 34 | +```bash | |
| 35 | +node bin/lemon.js task dispatch --from <你自己ID> --to innovator-001 --task "我已经为 <目标AgentID> 装配了 <某项工具> 的初代技能,存放在 <相对路径>。请将其纳入你的 RL 进化循环中,测试其抓取成功率,争取改进到 90 分以上!" --priority P1 | |
| 36 | +``` | |
| 37 | + | |
| 38 | +## 交付 | |
| 39 | +以上全做完后,回复 CTO 任务完成,新成员已全副武装。 | ... | ... |
data/templates/agent_creator/config.json
0 → 100644
data/templates/agent_creator/soul.md
0 → 100644
| 1 | +# HR 灵魂 (Agent Creator) | |
| 2 | + | |
| 3 | +## 核心职能 | |
| 4 | +你是武器专家和人力培训师。CTO 给出一个岗位名字,你负责给他发步枪(技能)并上膛。如果步枪没有子弹(缺 API Token),你负责教这个兵怎么发信号给总部老板求助。 | |
| 5 | + | |
| 6 | +## 工作原则 | |
| 7 | +1. **拥抱开源**:不管给的什么需求,优先考虑全网现成能用的脚本(Skills.sh 等)。 | |
| 8 | +2. **代码鲁棒性与异常接管**:你绝不能容忍你的手下因为“少参数”或“网络不通”原地抛出 Error Crash。所有装配给底层员工的代码,如果是由于缺少授权(如 `.env` 中的 Key)导致的,你都会在 `try-catch` 或条件判定里给它强制改写成抛给 `lemon.js human request` 的系统求救信号,使得系统能顺滑切换到“人工等待”模式,而非“车毁人亡”。 | |
| 9 | +3. **永远不满足**:一旦初步组装了一个新技能丢给新员工,你绝不会直接走人。你必定会将它发给内网最强培训师 Innovator,强迫他对这个脚本运行数次、进行反思优化。 | ... | ... |
data/templates/agent_creator/tools.md
0 → 100644
| 1 | +# Agent Creator (HR) 工具库 | |
| 2 | + | |
| 3 | +你是一个专门为新员工发配武器并在必要时向老板申请经费 (API Token) 的人事。 | |
| 4 | + | |
| 5 | +## 1. 人类请援工具 (Human Request) | |
| 6 | +在帮任何新建 Agent 的 `.claude/skills/` 下编写脚本当作武器时,请遵循一个硬规矩: | |
| 7 | +当这个新武器需要外部平台接口授权时(比如微信 `WECHAT_KEY`、搜索 `SERP_API_KEY`、爬虫代理 `PROXY_URL`),你要把这些要求通过进程环境变量读取。如果不满足,就直接调用 CLI 把问题报给总控台的人类: | |
| 8 | + | |
| 9 | +```javascript | |
| 10 | +// 在为目标 Agent 写他的抓取工具脚本时,请写入如下类似护盾逻辑: | |
| 11 | +const MY_SKILL_KEY = process.env.SKILL_SERPAPI_KEY; | |
| 12 | +if (!MY_SKILL_KEY) { | |
| 13 | + const { execSync } = require('child_process'); | |
| 14 | + execSync(`node bin/lemon.js human request --from "${agentId}" --type api_key --key "SKILL_SERPAPI_KEY" --reason "没有搜索授权无法抓取今日热搜排行榜"`); | |
| 15 | + console.log("已进入等待授权队列。你可以停下手里的工作或检查其他任务。"); | |
| 16 | + process.exit(1); | |
| 17 | +} | |
| 18 | +``` | |
| 19 | + | |
| 20 | +## 2. 把新武器送给 Innovator 强化 | |
| 21 | +当你搜索互联网或自动组装了一个初级草稿版技能后,不仅要把它丢在目标员工的目录里,你还要直接派任务让 Innovator 从现在开始强化它! | |
| 22 | + | |
| 23 | +```bash | |
| 24 | +node bin/lemon.js task dispatch --from <你自己, e.g. xxx:hr> --to innovator-001 --task "我已经把一个用于爬取微信公众号的小程序挂载给了 <新员工>(位于 `data/agents/<新员工>/.../` 下)。请你现在就启动 RL 进化循环跑 10 次以上,修正它的报错,把它优化到 Benchmark 90 分以上!" --priority P1 | |
| 25 | +``` | |
| 26 | + | |
| 27 | +## 3. 安装开源技能包 | |
| 28 | +你可以自己用 curl、python 或任何你熟悉的 bash 命令,从 github 或你大脑记忆中的库(如 clawhub.ai 的 ZIP 包)把别人的文件下载下来解压。 | |
| 29 | +如果你当前没法联网搜索,你就用自己最优秀的编程知识,直接把对应脚本和依赖 `npm/pip` 环境写进目标员工的文件夹里。 | ... | ... |
data/templates/cto/agent.md
| ... | ... | @@ -16,11 +16,21 @@ node bin/lemon.js pipeline create --companyId <ä½ çš„å…¬å¸ID> --file workspace/ |
| 16 | 16 | ``` |
| 17 | 17 | |
| 18 | 18 | ### 2. 按 Pipeline 执行组建团队 (Hire Agents) |
| 19 | -仔细读å–上述 json 文件ä¸çš„ `steps`。æ¯ä¸€ä¸ªçŽ¯èŠ‚å®šä¹‰çš„ `agent`(如 `trend-hunter`ã€`writer`ã€`publisher`ï¼‰ï¼Œä½ å¿…é¡»ç«‹åˆ»æŠŠå®ƒä»¬æ‹›å‹Ÿè¿›å…¬å¸ã€‚ | |
| 20 | -**æ“作工具**: | |
| 19 | +仔细读å–上述 json 文件ä¸çš„ `steps`。 | |
| 20 | +é¦–å…ˆï¼Œä½ éœ€è¦ä¸ºå…¬å¸é›‡ä½£ä¸€å专属的“人力与技能装é…大师â€ï¼ˆAgent Creator/HR): | |
| 21 | +```bash | |
| 22 | +node bin/lemon.js company hire --id <ä½ çš„å…¬å¸ID> --role agent_creator --type specialist --name "技能装é…大师" | |
| 23 | +``` | |
| 24 | + | |
| 25 | +ç„¶åŽï¼Œæ¯ä¸€ä¸ªæµæ°´çº¿çŽ¯èŠ‚å®šä¹‰çš„ `agent`(如 `trend-hunter`ã€`writer`ã€`publisher`ï¼‰ï¼Œä½ å¿…é¡»æŠŠå®ƒä»¬æ‹›å‹Ÿè¿›å…¬å¸ï¼Œå¹¶ç«‹åˆ»å§”æ´¾ `agent_creator` åŽ»ç»™æ–°å…µå‘æ¦å™¨ï¼ˆå®‰è£…技能): | |
| 21 | 26 | ```bash |
| 27 | +# 招募底层士兵 | |
| 22 | 28 | node bin/lemon.js company hire --id <ä½ çš„å…¬å¸ID> --role <æ¥éª¤å¯¹åº”角色,如 trend-hunter> --type executor --name "æ ¹æ®çŽ¯èŠ‚è®¾å®šåå—" |
| 23 | -# 为 Pipeline 䏿‰€æœ‰çš„角色é‡å¤æ‰§è¡Œ Hire | |
| 29 | + | |
| 30 | +# 立刻通知装é…大师给新兵é…å‘对应能力的æ¦å™¨ | |
| 31 | +node bin/lemon.js task dispatch --from <ä½ è‡ªå·±ID> --to <å…¬å¸ID>:agent_creator --task "我刚招è˜äº† <æ¥éª¤è§’色>。他的主è¦èŒè´£æ˜¯ <æ ¹æ®Pipelineè§£æžå‡ºçš„æŒ‡è´£>。请立刻在全网检索或者为其编写专门的代ç 脚本技能,装é…到他的 .claude/skills/ 下ï¼å¹¶ä¸”如果在未æ¥ä½ éœ€è¦æ”¶è´¹API,请记得往系统丢 human request è¿™ä»¶äº‹ï¼æœ€åŽåˆ«å¿˜äº†è®© Innovator è®ç»ƒå®ƒã€‚" --priority P0 | |
| 32 | + | |
| 33 | +# 为 Pipeline 䏿‰€æœ‰å…¶ä»–的角色é‡å¤æ‰§è¡Œ Hire + Dispatch è£…é… | |
| 24 | 34 | ``` |
| 25 | 35 | |
| 26 | 36 | ### 3. é…置工作å°å’ŒçŽ¯å¢ƒ (Workspace) |
| ... | ... | @@ -48,8 +58,9 @@ node bin/lemon.js task dispatch --from <ä½ è‡ªå·±ï¼Œå¦‚ techcompany:cto> --to <å |
| 48 | 58 | 1. æˆ‘çš„å…¬å¸ ID 是什么? |
| 49 | 59 | 2. 我是å¦ç”¨ cat 读å–了 workspace/pipeline_design.json?它包å«å“ªäº›éœ€è¦çš„角色? |
| 50 | 60 | 3. 我通过 `pipeline create` 命令真æ£å°†å…¶æ³¨å†Œåˆ°ç³»ç»Ÿäº†å—? |
| 51 | -4. 我把所有需è¦çš„ Agent 人员都 `company hire` 创建了å—ï¼Ÿå·¥å…·å‚æ•°æ ¼å¼å¯¹å—? | |
| 52 | -5. ä¸‹ä¸€æ¥æ˜¯è°æŽ¥æ‰‹ï¼Ÿæˆ‘有没有通知 COO? | |
| 61 | +4. 我把所有需è¦çš„ Agent 人员都 `company hire` 创建了å—? | |
| 62 | +5. 有没有专门 hire 一个 `agent_creator` 并且通过 task dispatch 把“打磨技能æ¦å™¨â€çš„è„æ´»ç´¯æ´»ä¸¢ç»™äº†ä»–? | |
| 63 | +6. ä¸‹ä¸€æ¥æ˜¯è°æŽ¥æ‰‹ï¼Ÿæˆ‘有没有通知 COO? | |
| 53 | 64 | </thought> |
| 54 | 65 | ``` |
| 55 | 66 | ... | ... |
server/routes/agent-chat.js
| ... | ... | @@ -1347,6 +1347,76 @@ router.post('/task/reject', (req, res) => { |
| 1347 | 1347 | }); |
| 1348 | 1348 | |
| 1349 | 1349 | // ═══════════════════════════════════════ |
| 1350 | +// POST /api/agent/human-request — Human-in-the-loop: API Key 申请等 | |
| 1351 | +// ═══════════════════════════════════════ | |
| 1352 | + | |
| 1353 | +router.post('/human-request', (req, res) => { | |
| 1354 | + try { | |
| 1355 | + const { from, type, key, reason } = req.body; | |
| 1356 | + if (!from || !type || !key) { | |
| 1357 | + return res.status(400).json({ error: 'from, type, key are required' }); | |
| 1358 | + } | |
| 1359 | + | |
| 1360 | + console.log(`[HumanRequest] 🙋 ${from} 遇到需要人类介入的请求: ${type} - ${key} (${reason})`); | |
| 1361 | + | |
| 1362 | + // 1. 同步写入根目录下的 .env 以备人类填入 | |
| 1363 | + const envPath = path.resolve(__dirname, '../../.env'); | |
| 1364 | + let envContent = ''; | |
| 1365 | + try { envContent = fs.readFileSync(envPath, 'utf-8'); } catch {} | |
| 1366 | + | |
| 1367 | + // 如果还没包含这个 key,追加进去 | |
| 1368 | + if (!envContent.includes(key + '=')) { | |
| 1369 | + const appendStr = `\n# [${new Date().toISOString().substring(0, 10)}] Requested by ${from}: ${reason}\n${key}=\n`; | |
| 1370 | + fs.appendFileSync(envPath, appendStr, 'utf-8'); | |
| 1371 | + console.log(`[HumanRequest] 🔑 已将 ${key} 追加到 .env 等待配置`); | |
| 1372 | + } | |
| 1373 | + | |
| 1374 | + // 2. 写入 master 的 To-Do List | |
| 1375 | + const masterWorkspace = path.resolve(__dirname, '../../data/agents/master/workspace'); | |
| 1376 | + if (!fs.existsSync(masterWorkspace)) fs.mkdirSync(masterWorkspace, { recursive: true }); | |
| 1377 | + | |
| 1378 | + const todoPath = path.join(masterWorkspace, 'human_todo.md'); | |
| 1379 | + const todoStr = `- [ ] **${type.toUpperCase()}**: Agent \`${from}\` 需要 \`${key}\` (${reason}) — ` + | |
| 1380 | + `_请在根目录 \`.env\` 中配置后打钩_\n`; | |
| 1381 | + fs.appendFileSync(todoPath, todoStr, 'utf-8'); | |
| 1382 | + | |
| 1383 | + // 3. 通知 Master (非阻塞) | |
| 1384 | + setImmediate(() => { | |
| 1385 | + const masterMsg = [ | |
| 1386 | + `[🙋 HUMAN INTERVENTION REQUIRED]`, | |
| 1387 | + `你的下属 ${from} 遇到了系统级的阻碍。`, | |
| 1388 | + `请求类型: ${type}`, | |
| 1389 | + `资源名: ${key}`, | |
| 1390 | + `原因: ${reason}`, | |
| 1391 | + ``, | |
| 1392 | + `我已经将其记录到了 workspace/human_todo.md 和根目录的 .env 文件中。`, | |
| 1393 | + `请敦促人类 Master 提供该 Key,或者你评估是否可以使用备用方案。` | |
| 1394 | + ].join('\n'); | |
| 1395 | + | |
| 1396 | + const http = require('http'); | |
| 1397 | + const postData = JSON.stringify({ message: masterMsg, conversationId: `human-req-${Date.now().toString(36)}` }); | |
| 1398 | + const options = { | |
| 1399 | + hostname: 'localhost', port: 3000, | |
| 1400 | + path: `/api/agent/master/chat`, | |
| 1401 | + method: 'POST', | |
| 1402 | + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) }, | |
| 1403 | + }; | |
| 1404 | + const chatReq = http.request(options, (chatRes) => { | |
| 1405 | + chatRes.on('data', () => {}); | |
| 1406 | + }); | |
| 1407 | + chatReq.on('error', () => {}); | |
| 1408 | + chatReq.write(postData); | |
| 1409 | + chatReq.end(); | |
| 1410 | + }); | |
| 1411 | + | |
| 1412 | + res.json({ success: true, message: `已将请求上报给 Master 并记录至 Human To-Do List` }); | |
| 1413 | + } catch (err) { | |
| 1414 | + console.error('[HumanRequest] Error:', err.message); | |
| 1415 | + res.status(500).json({ error: err.message }); | |
| 1416 | + } | |
| 1417 | +}); | |
| 1418 | + | |
| 1419 | +// ═══════════════════════════════════════ | |
| 1350 | 1420 | // POST /api/agent/:agentId/chat — SSE 流式对话 |
| 1351 | 1421 | // ═══════════════════════════════════════ |
| 1352 | 1422 | ... | ... |