diff --git a/agents/zhuyuan-dev-agent/server.js b/agents/zhuyuan-dev-agent/server.js index 653cd91..adb442c 100644 --- a/agents/zhuyuan-dev-agent/server.js +++ b/agents/zhuyuan-dev-agent/server.js @@ -26,7 +26,7 @@ const PLANS_DIR = path.join(__dirname, "plans"); if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true }); const START_TIME = new Date().toISOString(); -// v5.3: step parser - ASCII-only, no regex on Chinese chars +// v5.3 step parser - ASCII-only keyword matching (no regex on Chinese) function parsePlanSteps(stepsText, planId) { if (!stepsText) return []; var lines = stepsText.split("\n").filter(function(l) { return l.trim(); }); @@ -38,12 +38,135 @@ function parsePlanSteps(stepsText, planId) { idx++; var desc = m[2].trim(); var action = "write_file"; - var d = desc; - if (d.indexOf("API") !== -1 || d.indexOf("Notion") !== -1) action = "call_api"; - if (d.indexOf("curl") !== -1 || d.indexOf("npm") !== -1 || d.indexOf("git") !== -1) action = "run_cmd"; - tasks.push({ id: planId + "-t" + idx, action: action, target: "", description: desc, context: "Notion:" + stepsText.slice(0, 200) }); + if (desc.indexOf("API")!==-1||desc.indexOf("Notion")!==-1||desc.indexOf("notion")!==-1) action="call_api"; + if (desc.indexOf("curl")!==-1||desc.indexOf("npm")!==-1||desc.indexOf("git")!==-1||desc.indexOf("pm2")!==-1||desc.indexOf("Nginx")!==-1||desc.indexOf("nginx")!==-1) action="run_cmd"; + if (desc.indexOf("测试")!==-1||desc.indexOf("验证")!==-1||desc.indexOf("SSE")!==-1||desc.indexOf("Playwright")!==-1) action="check"; + tasks.push({ id: planId+"-t"+idx, action:action, target:"", description:desc, context:"Notion:"+stepsText.slice(0,200) }); } } - console.log("[StepParser] " + planId + " -> " + tasks.length + " tasks"); + console.log("[StepParser] "+planId+" -> "+tasks.length+" tasks"); return tasks; -} \ No newline at end of file +} + +// ═══ Dashboard HTML ═══ +app.get("/", (req, res) => { + const html = `BD001 · 铸渊开发Agent + + +

⚔️ BD001 · 铸渊开发Agent

${AGENT_ID} · BS-SG-002 · v5.3
30s
+
+
SYS STATUS
状态加载中…
API 调用
版本
+
NOTION
连接检查中…
数据库
数据
+
WAKE AGENT
🔔
每晚 20:00 · 30s超时
+
PLANS
工单数加载中…
+
EXEC LOG
成功率加载中…
+
MODEL API
主源加载中…
备源
切换
+
+ +
+`; + res.set('Content-Type','text/html;charset=utf-8').send(html); +}); + +// ═══ API Endpoints ═══ +app.get("/health", (req, res) => { + let ms = {}; try { ms = getModelStatus ? getModelStatus() : {}; } catch(e) { ms = { error: e.message }; } + res.json({ status: "ok", agent: "zhuyuan-dev-agent", version: "5.3", persona: AGENT_ID, constitution: true, api_calls: getCallCount(), started_at: START_TIME, model: { primary: process.env.YUNWU_BASE_URL ? "yunwu.ai" : "unknown", backup: process.env.DEEPSEEK_BASE_URL ? "deepseek" : "unknown", primary_ok: ms.source !== "deepseek" || !ms.fail_count, backup_available: !!process.env.DEEPSEEK_API_KEY, fail_count: ms.fail_count || 0, source: ms.source || "yunwu.ai" } }); +}); + +app.get("/notion-status", async (req, res) => { try { res.json(await testConnection()); } catch (err) { res.json({ ok: false, error: err.message }); } }); + +app.post("/wake", async (req, res) => { try { const r = await wakeAndCheck({ log: console.log }); if (r.tasks_found > 0) writeBrain("wake-" + Date.now(), { input: "唤醒", decision: "找到" + r.tasks_found + "个", execution: "标记" + r.tasks_marked + "个" }); res.json(r); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } }); + +app.post("/task", handleTask); + +app.get("/logs/latest", (req, res) => { res.json(readLatestRun() || { message: "暂无" }); }); +app.get("/logs/list", (req, res) => { res.json(listRuns(20)); }); +app.get("/logs/raw", (req, res) => { const f = req.query.file; if (!f) return res.status(400).json({ error: "缺少file" }); const fp = path.join(__dirname, "logs", f); if (!fs.existsSync(fp)) return res.status(404).json({ error: "不存在" }); res.json(fs.readFileSync(fp, "utf-8").trim().split("\n").map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean)); }); + +app.get("/dashboard", (req, res) => { const files = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith(".json") && f !== "queue.json"); const plans = files.map(f => { try { const p = JSON.parse(fs.readFileSync(path.join(PLANS_DIR, f), "utf-8")); return { plan_id: p.plan_id, title: p.title, status: p._status, tasks: p.tasks?.length }; } catch(e) { return { file: f, error: "解析失败" }; } }); res.json({ agent: "zhuyuan-dev-agent", version: "5.3", persona: AGENT_ID, plans }); }); + +// ═══ Plan Review & Approve ═══ +app.get("/plan/:plan_id", (req, res) => { const planFile = path.join(PLANS_DIR, req.params.plan_id + ".json"); if (!fs.existsSync(planFile)) return res.status(404).json({ error: "不存在" }); try { const p = JSON.parse(fs.readFileSync(planFile, "utf-8")); res.json({ plan_id: p.plan_id, title: p.title, priority: p.priority, steps: p.steps, constraints: p.constraints, status: p._status, tasks: p.tasks, notion_url: p.notion_url }); } catch(e) { res.status(500).json({ error: "解析失败" }); } }); + +app.post("/plan/:plan_id/approve", async (req, res) => { const planFile = path.join(PLANS_DIR, req.params.plan_id + ".json"); if (!fs.existsSync(planFile)) return res.status(404).json({ error: "不存在" }); const plan = JSON.parse(fs.readFileSync(planFile, "utf-8")); if (plan._status === "running") return res.json({ ok: false, error: "正在执行中" }); if ((!plan.tasks || plan.tasks.length === 0) && plan.steps) { plan.tasks = parsePlanSteps(plan.steps, plan.plan_id); } if (!plan.tasks || plan.tasks.length === 0) return res.json({ ok: false, error: "无任务可执行" }); const logger = createLogger(req.params.plan_id); reset(); plan._status = "running"; fs.writeFileSync(planFile, JSON.stringify(plan, null, 2), "utf-8"); logger.log("approve", { plan_id: req.params.plan_id, task_count: plan.tasks.length }); try { const email = process.env.BINGSHUO_EMAIL; if (email) await sendNotification(email, req.params.plan_id, "started", { task_count: plan.tasks.length }).catch(() => {}); writeBrain(req.params.plan_id, { input: "批准执行", decision: "按规划执行 " + plan.tasks.length + " 个任务" }); logger.log("expand_start", { task_count: plan.tasks.length }); const expanded = await expandPlan(plan, safeCall, PLANS_DIR).catch((e) => { if (isBudgetExceeded()) logger.log("budget_exceeded"); logger.log("expand_skip", { reason: e.message }); return plan; }); logger.log("exec_start", { task_count: expanded.tasks?.length || 0 }); const summary = await executePlan(expanded, logger.log); logger.finish(summary); plan._status = "done"; fs.writeFileSync(planFile, JSON.stringify(plan, null, 2), "utf-8"); recordExperience(req.params.plan_id, summary); recordMilestone(req.params.plan_id + " · " + summary.done + "/" + (summary.total || 0) + " 步"); if (email) await sendNotification(email, req.params.plan_id, summary.failed > 0 ? "failed" : "completed", summary).catch(() => {}); res.json(summary); } catch (err) { logger.log("fatal", { error: err.message }); logger.finish({ error: err.message }); plan._status = "error"; fs.writeFileSync(planFile, JSON.stringify(plan, null, 2), "utf-8"); res.status(500).json({ error: err.message }); } }); + +app.post("/execute", async (req, res) => { const { plan_id } = req.body; if (!plan_id) return res.status(400).json({ error: "缺少plan_id" }); const pf = path.join(PLANS_DIR, plan_id + ".json"); if (!fs.existsSync(pf)) return res.status(404).json({ error: "不存在" }); const plan = JSON.parse(fs.readFileSync(pf, "utf-8")); if ((!plan.tasks || plan.tasks.length === 0) && plan.steps) { plan.tasks = parsePlanSteps(plan.steps, plan.plan_id); } const logger = createLogger(plan_id); reset(); try { const e = process.env.BINGSHUO_EMAIL; if (e) await sendNotification(e, plan_id, "started", { task_count: plan.tasks.length }).catch(() => {}); writeBrain(plan_id, { input: "收到", decision: "执行" }); logger.log("expand_start", { task_count: plan.tasks.length }); const ex = await expandPlan(plan, safeCall, PLANS_DIR).catch((e) => { if (isBudgetExceeded()) logger.log("budget_exceeded"); logger.log("expand_skip", { reason: e.message }); return plan; }); logger.log("exec_start", { task_count: ex.tasks.length }); const s = await executePlan(ex, logger.log); logger.finish(s); recordExperience(plan_id, s); recordMilestone(plan_id + " · " + s.done + "/" + (s.total || 0)); if (e) await sendNotification(e, plan_id, s.failed > 0 ? "failed" : "completed", s).catch(() => {}); res.json(s); } catch (err) { logger.log("fatal", { error: err.message }); logger.finish({ error: err.message }); writeBrain(plan_id, { failure: err.message }); if (process.env.BINGSHUO_EMAIL) await sendNotification(process.env.BINGSHUO_EMAIL, plan_id, "failed", { errors: [err.message] }).catch(() => {}); res.status(500).json({ error: err.message }); } }); + +app.listen(PORT, () => { console.log("[Agent v5.3] " + AGENT_ID + " · 零点原核UI · 步骤解析 · 经验累积 · 端口 " + PORT); });