// HLDP-ZY://agents/zhuyuan-dev-agent/server.js
// @guardian: ICE-GL-ZY001 · 铸渊
// @sovereign: TCS-0002∞ · 冰朔
// @copyright: 国作登字-2026-A-00037559
// BD001 常驻Agent v5.0 — 零点原核UI风格
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, getStatus: getModelStatus } = 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 { wakeAndCheck, testConnection } = require("./modules/notion-poller");
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 });
const START_TIME = new Date().toISOString();
app.get("/", (req, res) => {
const html = `
BD001 · 铸渊开发Agent
⚔️ BD001 · 铸渊开发Agent
${AGENT_ID} · BS-SG-002 · guanghuyaoming.com
30s
SYS STATUS—
状态加载中…
API 调用—
版本—
WAKE AGENT
🔔
每晚 20:00 · 30s超时
`;
res.set('Content-Type','text/html;charset=utf-8').send(html);
});
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.0",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.0",persona:AGENT_ID,plans})});
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"));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);recordMilestone(plan_id+"完成"+s.done+"/"+s.total);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] "+AGENT_ID+" · 零点原核UI · 端口 "+PORT)});