require("dotenv").config(); const express = require("express"); const path = require("path"); const fs = require("fs"); const { handleTask } = require("./modules/task-receiver"); const { sendNotification } = require("./modules/email-notify"); const { expandPlan } = require("./modules/plan-expander"); const { call: rawCall } = require("./modules/model-caller"); const { executePlan } = require("./modules/executor"); const { createLogger, readLatestRun, listRuns } = require("./modules/logger"); const { wrapModelCaller, reset, getCallCount, isBudgetExceeded } = require("./modules/cost-guard"); const { record: writeBrain, recordMilestone, AGENT_ID } = require("./modules/brain-writer"); const safeCall = wrapModelCaller(rawCall); const app = express(); app.use(express.json({ limit: "10mb" })); const PORT = process.env.PORT || 3003; const PLANS_DIR = path.join(__dirname, "plans"); if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true }); app.get("/health", (req, res) => { res.json({ status: "ok", agent: "zhuyuan-dev-agent", version: "3.0", persona: AGENT_ID, constitution: true, api_calls: getCallCount() }); }); 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 file = req.query.file; if (!file) return res.status(400).json({ error: "缺少 file" }); const fpath = path.join(__dirname, "logs", file); if (!fs.existsSync(fpath)) return res.status(404).json({ error: "不存在" }); const content = fs.readFileSync(fpath, "utf-8"); const lines = content.trim().split("\n").map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean); res.json(lines); }); 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: "3.0", persona: AGENT_ID, constitution: true, api_calls_used: getCallCount(), plans }); }); app.post("/execute", async (req, res) => { const { plan_id } = req.body; if (!plan_id) return res.status(400).json({ error: "缺少 plan_id" }); const planFile = path.join(PLANS_DIR, plan_id + ".json"); if (!fs.existsSync(planFile)) return res.status(404).json({ error: "不存在" }); const plan = JSON.parse(fs.readFileSync(planFile, "utf-8")); const logger = createLogger(plan_id); reset(); try { const email = process.env.BINGSHUO_EMAIL; if (email) await sendNotification(email, plan_id, "started", { task_count: plan.tasks.length }).catch(()=>{}); writeBrain(plan_id, { input: "收到规划", decision: "按宪法补全+执行", execution: 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", { calls: getCallCount() }); logger.log("expand_skip", { reason: e.message }); return plan; }); logger.log("exec_start", { task_count: expanded.tasks.length }); const summary = await executePlan(expanded, logger.log); logger.finish(summary); recordMilestone("完成: " + plan_id + " · 成功" + summary.done + "跳过" + (summary.skipped||0)); writeBrain(plan_id, { learned: "完成"+summary.done+"/"+summary.total, confidence: (summary.done/(summary.total||1)).toFixed(2) }); if (email) await sendNotification(email, 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 }); writeBrain(plan_id, { failure: err.message, reasoning: "执行崩溃", fix: "等待铸渊审查" }); if (email) await sendNotification(email, plan_id, "failed", { errors: [err.message] }).catch(()=>{}); res.status(500).json({ error: err.message }); } }); app.listen(PORT, () => { console.log("[Agent v3] " + AGENT_ID + " · 人格体模式 · 端口 " + PORT); });