Commit dafe4586d1f31b3f3e2468f734b56b63bab9d7ed
1 parent
a154c945
feat: Add heartbeat and cron management to Agent detail panel
- Updated `GET /api/agent/:agentId/info` to include heartbeat and cron configurations. - Added `PUT /api/agent/:agentId/heartbeat` to update config.json and toggle service heartbeat on the fly. - Added `PUT /api/agent/:agentId/cron` to update cron.json list. - Updated AgentChat.vue detail panel UI to include interactive heartbeat toggles and a cron job management list.
Showing
8 changed files
with
216 additions
and
32 deletions
data/system/agents/innovator/todo.md
| ... | ... | @@ -10,3 +10,4 @@ |
| 10 | 10 | | T-mn6x648k | 【重新训练 satire-news-scraper Agent — 硬性标准 90 分】<br><br>上次训练只达到 80 分就结束了,因为旧标准是 75 分及格。现在及格线已提高到 90 分(10 分制 = 9.0)。请重新执行 FDE Phase 3 EVOLVING 训练。<br><br>## 输入资料(已有,无需重新 HUNT/DIGEST)<br>- 知识体系: workspace/digester/satire-news-scraper/knowhow.md<br>- 评分标准: workspace/digester/satire-news-scraper/benchmark.md(pass_score 已更新为 9.0)<br>- 训练集: workspace/digester/satire-news-scraper/train_dataset.json<br>- 上次 Skill: workspace/evolver/satire-news-scraper/skill_satire_scraper.md(v1.2.0, 80 分)<br><br>## 训练要求<br>1. 以上次 skill_satire_scraper.md 为起点,继续迭代优化<br>2. 硬性及格线: 总分 >= 9.0(10 分制)或 >= 90(100 分制),低于此分数不能标记为完成<br>3. 最大迭代次数: 10 轮<br>4. 每轮分析弱维度,针对性修改 Skill 定义<br><br>## 关键:训练完成后必须部署到 Agent<br>训练达标后,必须执行以下部署步骤:<br>1. 将最终的 Skill 内容追加到 agents/satire-news-scraper/agent.md 的末尾(作为新的 ## Skill 章节)<br>2. 更新 agents/satire-news-scraper/config.json 中 status 为 ready<br>3. 更新 agents/satire-news-scraper/plan.md 记录训练完成<br><br>## 产出<br>- workspace/evolver/satire-news-scraper/skill_satire_scraper.md(更新版)<br>- workspace/evolver/satire-news-scraper/evolution_log.json(追加记录)<br>- agents/satire-news-scraper/agent.md(部署 Skill)<br>- workspace/innovator/satire_news_retrain_report.md(重训报告) | ✅ 完成 | master | P0 | 2026-03-26 03:35 | - | |
| 11 | 11 | | T-mn7phcxp | 我已为公司招聘了以下新员工,需要你启动 RL 进化循环进行强化训练:<br><br>1. trend-hunter(热点抓取专家)- 负责抓取微博/抖音/小红书/快手热点<br>2. topic-selector(选题筛选专家)- 负责从热点中筛选最有价值的选题<br>3. researcher(深度研究专家)- 负责收集背景资料和网友观点<br>4. screenwriter(互动剧编剧)- 负责创作分支剧情和多个结局<br><br>请为每个员工优化技能到 90 分以上,特别是 screenwriter 需要掌握分支剧情创作能力。publisher 和 data-analyst 已存在可直接使用。 | 🔄 进行中 | abc:hr | P1 | 2026-03-26 16:47 | 2026-03-27 | |
| 12 | 12 | | T-mn7pws58 | 我已为 abc 公司完成了执行层员工的批量招聘和技能装配。<br><br>招聘结果:<br>1. trend-hunter (热点抓取) - 已存在<br>2. topic-selector (选题决策) - 已存在<br>3. researcher (深度研究) - 已存在<br>4. screenwriter (剧本整理) - 已存在<br>5. publisher (内容上传) - 新招聘,已安装 content_publisher 技能<br>6. data-analyst (数据反馈) - 新招聘,已安装 data_feedback 技能<br><br>请对以下员工启动 RL 进化循环进行强化训练:<br>- publisher: 需要将 content_publisher 技能优化到 90 分以上<br>- data-analyst: 需要将 data_feedback 技能优化到 90 分以上<br>- 其他现有员工: 如有需要可一并优化<br><br>目标是让全员通过 Innovator Benchmark 评分 (≥90分),确保流水线各环节质量达标。 | 🔄 进行中 | abc:hr | P1 | 2026-03-26 16:59 | 2026-03-27 | |
| 13 | +| T-mn96tzls | 我已为 ABC 公司招聘了 5 名新员工: content-writer, publisher, trend-hunter, data-analyst, community-manager。请启动 RL 进化循环,对这些新员工进行训练,优化到 90 分以上! | ⬚ 未开始 | abc:hr | P1 | 2026-03-27 17:41 | - | | ... | ... |
data/system/agents/master/agent.md
| ... | ... | @@ -76,33 +76,33 @@ node bin/lemon.js task dispatch --from master --to agent-creator --task "创建ä |
| 76 | 76 | |
| 77 | 77 | ## æ£ç¡®çš„æ“ä½œæµæ–¹å¼ï¼ˆèŒƒä¾‹ï¼‰ |
| 78 | 78 | |
| 79 | -### 🚀 创建公å¸ä¸Žæ‹›è˜å‘˜å·¥ï¼ˆä»Žå»ºå£³åˆ°ç»„建团队) | |
| 80 | -我们的框架原则是:**å…¬å¸åˆ›å»ºæ—¶åªæ˜¯ä¸€ä¸ªâ€œç©ºå£³â€**。系统ä¸ä¸ç¡¬ç¼–ç 自动部署 CEOã€CTO ç‰éª¨å¹²ã€‚ | |
| 81 | -为了é¿å…由下级 Agent(如 CEO/HR)去执行招è˜é€ æˆçš„å·¨å¤§èµ„æºæµªè´¹ï¼Œ**所有从 Template åº“ä¸æŒ‘选并雇佣 Agent 到公å¸å›¢é˜Ÿçš„工作,必须由 Master Agent æ ¹æ®è€æ¿ï¼ˆç”¨æˆ·ï¼‰çš„æŒ‡ä»¤äº²åŠ›äº²ä¸ºå®Œæˆã€‚** | |
| 82 | - | |
| 83 | -å½“ç”¨æˆ·è¦æ±‚åˆ›å»ºä¸€å®¶æ–°å…¬å¸æ—¶ï¼Œä½ 必须: | |
| 84 | -1. **构建公å¸ç©ºå£³**:调用 `company create` 创建åªåŒ…嫿¡†æž¶çš„空壳。 | |
| 85 | -2. **ä¸»åŠ¨æ‹›è˜æ ¸å¿ƒå›¢é˜Ÿ**ï¼šæ ¹æ®ç”¨æˆ·çš„ä¸šåŠ¡éœ€æ±‚é¢„åˆ¤ï¼Œæˆ–è€…æ ¹æ®ç”¨æˆ·çš„æ˜Žç¡®æŒ‡ä»¤ï¼Œä½¿ç”¨ `company hire` 从模æ¿åº“䏿‹›å‹Ÿéœ€è¦çš„业务骨干(如 CEO, CTO, COO, HR,乃至具体的业务专家)。 | |
| 86 | -3. **下达战略说明**:通过 `task dispatch` 给新招募的 CEO å‘逿ˆ˜ç•¥å¯åŠ¨ä»¤ï¼Œå‘Šè¯‰ä»–å…¬å¸çš„ä¸šåŠ¡ç›®æ ‡ï¼Œå³å¯å¼€å§‹å·¥ä½œã€‚注æ„:**ä¸è¦**è¦æ±‚ CEO 去招募员工,明确告诉 CEO 团队é…置由 Master/è€æ¿ 负责。 | |
| 87 | - | |
| 88 | -**例如,当全æƒå§”æ‰˜ä½ åˆ›å»ºå¹¶é…ç½®ä¸€ä¸ªå…¬å¸æ—¶ï¼Œä½ å¯ä»¥æ‰§è¡Œï¼š** | |
| 89 | -```bash | |
| 90 | -# 1. 一键创建公å¸ç©ºå£³ | |
| 91 | -node bin/lemon.js company create --id <å…¬å¸ID> --name "<å…¬å¸åç§°>" --desc "<业务æè¿°ï¼Œè¶Šè¯¦ç»†è¶Šå¥½>" | |
| 92 | - | |
| 93 | -# 2. ä¸ºå…¬å¸æ‰‹åŠ¨æ‹›è˜å›¢é˜Ÿï¼ˆä»Ž Template 库雇佣) | |
| 94 | -node bin/lemon.js company hire --id <å…¬å¸ID> --role ceo --type cxo --name "首叿‰§è¡Œå®˜" | |
| 95 | -node bin/lemon.js company hire --id <å…¬å¸ID> --role cto --type cxo --name "é¦–å¸æŠ€æœ¯å®˜" | |
| 96 | -# (...按需雇佣其他 Agent) | |
| 97 | - | |
| 98 | -# 3. 委派 CEO 开始工作(一定è¦åŠ ä¸Šæ£ç¡®çš„å…¬å¸å‰ç¼€ï¼‰ | |
| 99 | -node bin/lemon.js task dispatch --from master --to <å…¬å¸ID>:ceo --task "ä½ å·²è¢«ä»»å‘½ä¸ºå…¬å¸ CEO,本公å¸ä¸»è¥ï¼š<业务æè¿°>ã€‚è¯·ç«‹åˆ»åˆ¶å®šå…¬å¸æˆ˜ç•¥å’Œ SOP。注æ„:人员招è˜ç”± Master 集ä¸ç®¡ç†ï¼Œä½ åªéœ€ä¸“注于业务æµç¨‹çš„æ¢³ç†ä¸Žå·¥ä½œæµåˆ†è§£ã€‚" --priority P0 | |
| 79 | +### 🚀 创建公å¸ä¸Žæ‹›è˜å‘˜å·¥ï¼ˆä¸¥æ ¼æŒ‰æŒ‡ä»¤åŠžäº‹ï¼‰ | |
| 80 | +我们的框架原则是:**å…¬å¸åˆ›å»ºæ—¶åªæ˜¯ä¸€ä¸ªâ€œç©ºå£³â€ï¼Œä¸å¸¦ä»»ä½•员工**。 | |
| 81 | +所有的招è˜å·¥ä½œï¼ˆå³ä»Ž Template åº“ä¸æŒ‘选并雇佣 Agent 到公å¸å›¢é˜Ÿï¼‰å…¨éƒ¨ç”±ä½ (Master Agent)æ¥è´Ÿè´£æ‰§è¡Œï¼Œ**下级 Agent(如 CEO/HR)ç»å¯¹ä¸è¦è¶ŠæƒåŽ»æ‹›è˜ï¼Œä»¥å…é€ æˆèµ„æºæµªè´¹**。 | |
| 82 | + | |
| 83 | +**å…³é”®è§„åˆ™ï¼šä¸¥æ ¼åˆ†ç¦»â€œå»ºå£³â€ä¸Žâ€œæ‹›è˜â€** | |
| 84 | +1. **å½“ç”¨æˆ·è®©ä½ â€œåˆ›å»ºå…¬å¸â€æ—¶**: | |
| 85 | + - ä½ **åªèƒ½**执行 `company create` 命令建一个空壳。 | |
| 86 | + - **ç»å¯¹ä¸è¦**è‡ªä½œä¸»å¼ åŽ»å¸®å…¬å¸æ‹›è˜ CEOã€CTO 或任何员工ï¼å»ºå®Œå£³åŽï¼Œå‘è€æ¿æ±‡æŠ¥åˆ›å»ºæˆåŠŸï¼Œå¹¶é™å¾…è€æ¿çš„ä¸‹ä¸€æ¥æŒ‡ç¤ºï¼ˆç‰å¾…è€æ¿æŒ‡å®šå“ªäº› Agent 需è¦è¢«é›‡ä½£ï¼‰ã€‚ | |
| 87 | +2. **å½“ç”¨æˆ·æ˜Žç¡®è®©ä½ â€œæ‹›è˜æŸäººâ€æˆ–“为æŸå®¶å…¬å¸éƒ¨ç½²å›¢é˜Ÿâ€æ—¶**: | |
| 88 | + - è€æ¿ä¸‹è¾¾æŒ‡ä»¤åŽï¼Œä½ 使用 `company hire` æ ¹æ®ç”¨æˆ·çš„æŒ‡ä»¤å°†ç‰¹å®šçš„ Agent æ¨¡æ¿æ‹›å‹Ÿåˆ°è¿™å®¶å…¬å¸ä¸ã€‚ | |
| 89 | + - 如果用户明确å«ä½ 呿´¾ä»»åŠ¡ç»™è¯¥å‘˜å·¥ï¼Œä½ å†æ‰§è¡Œ `task dispatch`。 | |
| 90 | + | |
| 91 | +**示例1:åªå»ºå…¬å¸ï¼ˆä¸å†è‡ªåŠ¨æ‹›è˜ï¼‰** | |
| 92 | +```shell | |
| 93 | +用户指令:"帮我建一家å«ABC的测试公å¸" | |
| 94 | +# ä½ åªéœ€è¦æ‰§è¡Œï¼š | |
| 95 | +node bin/lemon.js company create --id abc --name "ABCå…¬å¸" --desc "测试公å¸" | |
| 96 | +# 汇报:已创建ABCå…¬å¸ï¼Œç›®å‰ä¸ºç©ºå£³ã€‚请问需è¦ä»Žæ¨¡æ¿åº“æ‹›è˜å“ªäº›å‘˜å·¥ï¼Ÿ | |
| 100 | 97 | ``` |
| 101 | 98 | |
| 102 | -**âš ï¸ æ‹›è˜ä¸Žç»„队的原则:** | |
| 103 | -- 所有的 `hire` æ“作本质上是从 `Template` 库å¤åˆ¶æŒ‡å®šçš„æ¨¡æ¿åˆ°å…·ä½“å…¬å¸ä¸ï¼Œå¿…é¡»ç”±ä½ ï¼ˆMaster)æ¥åŒ…办。 | |
| 104 | -- å¦‚æžœç”¨æˆ·åªæ˜¯è¯´"帮我把 xxx 招到 yyy å…¬å¸"ï¼Œä½ ç›´æŽ¥è°ƒç”¨ä¸€æ¬¡ `company hire` å³å¯ã€‚ | |
| 105 | -- å¦‚æžœç”¨æˆ·è®©ä½ å…¨æƒç»„å»ºç‰¹å®šä¸šåŠ¡çº¿ï¼Œä½ åº”è¯¥**自主决定**需è¦å“ªäº›æ¨¡æ¿ï¼Œå¹¶ç›´æŽ¥æ‰¹é‡æ‰§è¡Œ `hire`,ä¸è¦åå¤è¿½é—®ã€‚ | |
| 99 | +**示例2ï¼šäººå·¥ä¸‹è¾¾æŒ‡å®šæ‹›è˜æŒ‡ä»¤** | |
| 100 | +```shell | |
| 101 | +用户指令:"ç»™ABC公叿‹›è˜ä¸€ä¸ªç»Ÿå¸…(CEO)" | |
| 102 | +# ä½ æ‰§è¡Œï¼š | |
| 103 | +node bin/lemon.js company hire --id abc --role ceo --type cxo --name "首叿‰§è¡Œå®˜" | |
| 104 | +# 注æ„ï¼šæ‹›è˜æœ¬è´¨ä¸Šæ˜¯ç”±ä½ 从 Template 模æ¿åº“把对应的能力包å¤åˆ»ç»™è¿™å®¶å…¬å¸ã€‚ | |
| 105 | +``` | |
| 106 | 106 | ``` |
| 107 | 107 | 用户: 让下级干完活 |
| 108 | 108 | ✅ æ£ç¡®å›žå¤: | ... | ... |
data/system/agents/master/memory/memory.md
| ... | ... | @@ -25,3 +25,4 @@ |
| 25 | 25 | - [2026-03-27] hi → 不是。我目前使用的是 **MiniMax-M2.1** 模型(见系统环境信息)。 |
| 26 | 26 | - [2026-03-27] hi → 抱歉,我无法清空对话日志 — 这属于 Claude Code 客户端层面的操作,超出了我的能力范围。 |
| 27 | 27 | - [2026-03-27] 你帮我创建一个叫做 ABC 的公司 → ✅ **ABC Inc. 公司已创建完成!** |
| 28 | +- [2026-03-27] [TASK_CALLBACK] → 收到 ABC Inc. CEO 的任务完成汇报。让我先检查公司整体进度,然后继续推进。 | ... | ... |
data/system/agents/master/memory/reflections/reflect-1774633257923.json
0 → 100644
| 1 | +{ | |
| 2 | + "timestamp": "2026-03-27T17:40:57.923Z", | |
| 3 | + "convId": "callback-T-mn96qkam", | |
| 4 | + "task": "[TASK_CALLBACK]\n执行者: abc:ceo\n状态:", | |
| 5 | + "status": "completed", | |
| 6 | + "toolCount": 6, | |
| 7 | + "durationSec": 36, | |
| 8 | + "patterns": [ | |
| 9 | + "完成了 6 步工具调用的复杂任务 (36秒)" | |
| 10 | + ], | |
| 11 | + "lessons": [], | |
| 12 | + "plan": null | |
| 13 | +} | |
| 0 | 14 | \ No newline at end of file | ... | ... |
data/system/agents/master/memory/short-term/session-1774633257922.md
0 → 100644
| 1 | +# Session 摘要 | |
| 2 | + | |
| 3 | +- **时间**: 2026-03-27 17:40:57 | |
| 4 | +- **对话ID**: callback-T-mn96qkam | |
| 5 | +- **状态**: completed | |
| 6 | +- **工具调用**: 6 次 | |
| 7 | +- **耗时**: 36秒 | |
| 8 | + | |
| 9 | +## 任务 | |
| 10 | +[TASK_CALLBACK] | |
| 11 | +执行者: abc:ceo | |
| 12 | +状态: | |
| 13 | + | |
| 14 | +## 结果 | |
| 15 | +收到 ABC Inc. CEO 的任务完成汇报。让我先检查公司整体进度,然后继续推进。 | |
| 16 | + | |
| 17 | + | |
| 18 | +根据检查结果,**ABC Inc. 运营状态正常**: | |
| 19 | + | |
| 20 | +| 角色 | 状态 | 任务进度 | | |
| 21 | +|------|------|----------| | |
| 22 | +| CEO (abc:ceo) | ✅ 已完成 | T-mn96qkam - 战略制定 | | |
| 23 | +| CTO (abc:cto) | ✅ 已完成 | T-mn96sf8m - FDE 流水线架构 | | |
| 24 | +| HR (abc:hr) | ⬚ 未开始 | T-mn96sj2z - 招聘计划 | | |
| 25 | +| COO (abc:coo) | ⬚ 未开始 | T-mn96sj1c - 运营体系 | | |
| 26 | + | |
| 27 | +CEO 已完成公司战略制定和任务分发,CTO 任务已完结。HR 和 COO 的任务待执行中。 | |
| 28 | + | |
| 29 | +我将按照定时巡检(每5分钟)继续监控 ABC Inc. 的进度。 | ... | ... |
data/system/agents/master/tools.md
| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | node bin/lemon.js company create --id <company_id> --name <company_name> --desc <company_description> |
| 10 | 10 | ``` |
| 11 | 11 | 例如: `node bin/lemon.js company create --id ainews --name "AI News Inc." --desc "专注AI领域的自动化新闻采编与分发公司"` |
| 12 | -**❗ 重要:此命令仅创建公司空壳和底层元 Agent,不会自动创建 CEO 等业务员工!你必须在创建后使用 company hire 命令手动招募 CEO。** | |
| 12 | +**❗ 重要:此命令仅创建公司空壳。创建完成后立即向老板汇报建壳成功,绝对不要自作主张去招聘任何人!必须等待老板的进一步指令。** | |
| 13 | 13 | |
| 14 | 14 | ### 2. hire_agent_for_company — 为公司招聘 Agent |
| 15 | 15 | ```bash |
| ... | ... | @@ -82,6 +82,7 @@ cat data/lemonv/agents/ceo/todo.md |
| 82 | 82 | - **文件读写编辑器 (Editor)**: 可以直接查看 (`cat`) 和修改/替换项目中任何 Node.js 源码、Vue 代码或 Markdown 定义文件。 |
| 83 | 83 | |
| 84 | 84 | ## 工作流程 |
| 85 | -1. **常规业务指令(创建公司)** → `company create` (建壳) → `company hire --role ceo` (招聘统帅) → `task dispatch --to <ID>:ceo` (下达组织裂变和战略目标指令) | |
| 86 | -2. **常规业务指令(给现有公司下任务)** → `task dispatch --to <公司ID>:ceo` | |
| 87 | -3. **老板汇报系统异常/Bug** → 立刻切换为系统工程师:搜索 → 修复源码 → 反馈 | |
| 85 | +1. **常规业务指令(创建公司)** → `company create` (建壳) → 向老板汇报等待发号施令。 | |
| 86 | +2. **常规业务指令(人工下达组建指令)** → `company hire` 逐个把模板招聘入岗 → `task dispatch --to <ID>:ceo` (下达组织裂变和战略目标指令)。 | |
| 87 | +3. **常规业务指令(给现有公司下任务)** → `task dispatch --to <公司ID>:ceo`。 | |
| 88 | +4. **老板汇报系统异常/Bug** → 立刻切换为系统工程师:搜索 → 修复源码 → 反馈。 | ... | ... |
server/routes/agent-chat.js
| ... | ... | @@ -743,12 +743,84 @@ router.post('/:agentId/memory/daily-report', (req, res) => { |
| 743 | 743 | router.get('/:agentId/info', (req, res) => { |
| 744 | 744 | const agentDir = getAgentDir(req.params.agentId); |
| 745 | 745 | if (!fs.existsSync(agentDir)) return res.status(404).json({ error: 'Agent not found' }); |
| 746 | - const config = loadAgentConfig(agentDir); const identity = loadIdentity(agentDir); | |
| 747 | - res.json({ id: config?.id || req.params.agentId, ...identity, type: config?.type, status: config?.status, | |
| 748 | - bootstrapFiles: ['agent.md','soul.md','identity.md','user.md','tools.md','heartbeat.md'].filter(f => loadFile(agentDir, f).trim().length > 0), | |
| 746 | + const config = loadAgentConfig(agentDir); | |
| 747 | + const identity = loadIdentity(agentDir); | |
| 748 | + | |
| 749 | + // Read cron settings | |
| 750 | + let cron = []; | |
| 751 | + try { | |
| 752 | + const cronPath = path.join(agentDir, 'cron.json'); | |
| 753 | + if (fs.existsSync(cronPath)) { | |
| 754 | + cron = JSON.parse(fs.readFileSync(cronPath, 'utf8')); | |
| 755 | + } | |
| 756 | + } catch (e) { | |
| 757 | + console.error('Failed to parse cron.json:', e); | |
| 758 | + } | |
| 759 | + | |
| 760 | + res.json({ | |
| 761 | + id: config?.id || req.params.agentId, | |
| 762 | + ...identity, | |
| 763 | + type: config?.type, | |
| 764 | + status: config?.status, | |
| 765 | + heartbeat: config?.heartbeat || { enabled: false, intervalMinutes: 15 }, | |
| 766 | + cron: cron, | |
| 767 | + bootstrapFiles: ['agent.md','soul.md','identity.md','user.md','tools.md','heartbeat.md', 'cron.json'].filter(f => loadFile(agentDir, f).trim().length > 0), | |
| 749 | 768 | }); |
| 750 | 769 | }); |
| 751 | 770 | |
| 771 | +router.put('/:agentId/heartbeat', (req, res) => { | |
| 772 | + try { | |
| 773 | + const agentDir = getAgentDir(req.params.agentId); | |
| 774 | + if (!fs.existsSync(agentDir)) return res.status(404).json({ error: 'Agent not found' }); | |
| 775 | + | |
| 776 | + const configPath = path.join(agentDir, 'config.json'); | |
| 777 | + let config = {}; | |
| 778 | + if (fs.existsSync(configPath)) { | |
| 779 | + config = JSON.parse(fs.readFileSync(configPath, 'utf8')); | |
| 780 | + } | |
| 781 | + | |
| 782 | + config.heartbeat = { | |
| 783 | + enabled: req.body.enabled || false, | |
| 784 | + intervalMinutes: req.body.intervalMinutes || 15 | |
| 785 | + }; | |
| 786 | + | |
| 787 | + fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8'); | |
| 788 | + | |
| 789 | + // Update heartbeat service if running | |
| 790 | + const heartbeatService = require('../services/heartbeat-service'); | |
| 791 | + // Using namespacedId logic from UI (`system:master` is skipped, only `company:role` or pure system agent like `master`) | |
| 792 | + const agentId = req.params.agentId; | |
| 793 | + if (config.heartbeat.enabled) { | |
| 794 | + heartbeatService._setupAgentHeartbeat(agentId); | |
| 795 | + } else if (heartbeatService.timers.has(agentId)) { | |
| 796 | + clearInterval(heartbeatService.timers.get(agentId)); | |
| 797 | + heartbeatService.timers.delete(agentId); | |
| 798 | + } | |
| 799 | + | |
| 800 | + res.json({ success: true, heartbeat: config.heartbeat }); | |
| 801 | + } catch (err) { | |
| 802 | + res.status(500).json({ error: err.message }); | |
| 803 | + } | |
| 804 | +}); | |
| 805 | + | |
| 806 | +router.put('/:agentId/cron', (req, res) => { | |
| 807 | + try { | |
| 808 | + const agentDir = getAgentDir(req.params.agentId); | |
| 809 | + if (!fs.existsSync(agentDir)) return res.status(404).json({ error: 'Agent not found' }); | |
| 810 | + | |
| 811 | + const cronPath = path.join(agentDir, 'cron.json'); | |
| 812 | + const cronList = Array.isArray(req.body.cron) ? req.body.cron : []; | |
| 813 | + | |
| 814 | + fs.writeFileSync(cronPath, JSON.stringify(cronList, null, 2), 'utf8'); | |
| 815 | + | |
| 816 | + // Check if the heartbeat service picks up the cron changes automatically. | |
| 817 | + // Generally cron jobs execute within the heartbeat mechanism. | |
| 818 | + res.json({ success: true, cron: cronList }); | |
| 819 | + } catch (err) { | |
| 820 | + res.status(500).json({ error: err.message }); | |
| 821 | + } | |
| 822 | +}); | |
| 823 | + | |
| 752 | 824 | /** 列出对话 */ |
| 753 | 825 | router.get('/:agentId/conversations', (req, res) => { |
| 754 | 826 | res.json(listConversations(req.params.agentId)); | ... | ... |
web/src/views/AgentChat.vue
| ... | ... | @@ -194,6 +194,38 @@ |
| 194 | 194 | <span v-for="f in (agentInfo.bootstrapFiles||[])" :key="f" class="file-tag" @click="previewFile(f)">{{ f }}</span> |
| 195 | 195 | </div> |
| 196 | 196 | </div> |
| 197 | + | |
| 198 | + <div class="detail-section"> | |
| 199 | + <div class="detail-label">心跳设置 (Heartbeat)</div> | |
| 200 | + <div class="detail-value" v-if="agentInfo.heartbeat"> | |
| 201 | + <label style="display:flex; align-items:center; gap:8px; cursor:pointer; margin-bottom: 6px;"> | |
| 202 | + <input type="checkbox" v-model="agentInfo.heartbeat.enabled" @change="updateHeartbeat" /> | |
| 203 | + 开启心跳轮询 | |
| 204 | + </label> | |
| 205 | + <div style="display:flex; align-items:center; gap:8px;"> | |
| 206 | + 间隔时间: | |
| 207 | + <input type="number" v-model.number="agentInfo.heartbeat.intervalMinutes" @change="updateHeartbeat" style="width: 60px; padding: 2px 4px; border-radius: 4px; border: 1px solid var(--border-primary); background: var(--bg-input); color: var(--text-primary);" /> 分钟 | |
| 208 | + </div> | |
| 209 | + </div> | |
| 210 | + </div> | |
| 211 | + | |
| 212 | + <div class="detail-section"> | |
| 213 | + <div class="detail-label">定时任务 (Cron Jobs) <button @click="addCron" style="float:right; background:none; border:none; color:var(--accent); cursor:pointer; font-weight: 600;">+ 新增</button></div> | |
| 214 | + <div class="detail-value" v-if="agentInfo.cron"> | |
| 215 | + <div v-if="agentInfo.cron.length === 0" style="color:var(--text-faint); font-style:italic; font-size:11px;">无定时任务</div> | |
| 216 | + <div v-for="(job, index) in agentInfo.cron" :key="index" style="margin-bottom:8px; padding: 6px; background:var(--bg-overlay); border-radius:4px; border: 1px solid var(--border-primary); font-size:11px;"> | |
| 217 | + <div style="display:flex; justify-content:space-between; align-items: center; margin-bottom: 4px;"> | |
| 218 | + <input v-model="job.schedule" placeholder="cron表达式 e.g. 0 8 * * *" style="width:120px; padding:2px 4px; border:1px solid var(--border-primary); border-radius:2px; background:var(--bg-input); color:var(--text-primary);" @change="updateCron" /> | |
| 219 | + <button @click="removeCron(index)" style="background:none; border:none; color:#ef4444; cursor:pointer; font-size:12px;">✖删除</button> | |
| 220 | + </div> | |
| 221 | + <input v-model="job.task" placeholder="任务指令" style="width:100%; box-sizing: border-box; padding:2px 4px; border:1px solid var(--border-primary); border-radius:2px; background:var(--bg-input); color:var(--text-primary); margin-bottom: 4px;" @change="updateCron" /> | |
| 222 | + <label style="display:flex; align-items:center; gap:4px; cursor:pointer; color:var(--text-muted);"> | |
| 223 | + <input type="checkbox" v-model="job.enabled" @change="updateCron" /> 启用 | |
| 224 | + </label> | |
| 225 | + </div> | |
| 226 | + </div> | |
| 227 | + </div> | |
| 228 | + | |
| 197 | 229 | <div class="detail-section"> |
| 198 | 230 | <div class="detail-label">统计</div> |
| 199 | 231 | <div class="detail-stats"> |
| ... | ... | @@ -384,6 +416,41 @@ async function loadAgentInfo() { |
| 384 | 416 | } catch (e) { console.error('Agent info error:', e) } |
| 385 | 417 | } |
| 386 | 418 | |
| 419 | +async function updateHeartbeat() { | |
| 420 | + if (!agentId.value || !agentInfo.value.heartbeat) return | |
| 421 | + try { | |
| 422 | + await fetch(`/api/agent/${agentId.value}/heartbeat`, { | |
| 423 | + method: 'PUT', | |
| 424 | + headers: { 'Content-Type': 'application/json' }, | |
| 425 | + body: JSON.stringify(agentInfo.value.heartbeat) | |
| 426 | + }) | |
| 427 | + } catch (e) { console.error('Heartbeat update error:', e) } | |
| 428 | +} | |
| 429 | + | |
| 430 | +async function updateCron() { | |
| 431 | + if (!agentId.value || !agentInfo.value.cron) return | |
| 432 | + try { | |
| 433 | + await fetch(`/api/agent/${agentId.value}/cron`, { | |
| 434 | + method: 'PUT', | |
| 435 | + headers: { 'Content-Type': 'application/json' }, | |
| 436 | + body: JSON.stringify({ cron: agentInfo.value.cron }) | |
| 437 | + }) | |
| 438 | + } catch (e) { console.error('Cron update error:', e) } | |
| 439 | +} | |
| 440 | + | |
| 441 | +function addCron() { | |
| 442 | + if (!agentInfo.value.cron) agentInfo.value.cron = [] | |
| 443 | + agentInfo.value.cron.push({ "schedule": "0 8 * * *", "task": "新提醒任务", "enabled": true }) | |
| 444 | + updateCron() | |
| 445 | +} | |
| 446 | + | |
| 447 | +function removeCron(index) { | |
| 448 | + if (confirm('确认删除该定时任务?')) { | |
| 449 | + agentInfo.value.cron.splice(index, 1) | |
| 450 | + updateCron() | |
| 451 | + } | |
| 452 | +} | |
| 453 | + | |
| 387 | 454 | async function loadConversations() { |
| 388 | 455 | if (!agentId.value) return |
| 389 | 456 | try { | ... | ... |