feat: establish Chenglu persistent agent runtime

This commit is contained in:
冰朔 2026-07-20 23:10:57 +08:00
commit 5aa396ac7b
17 changed files with 450 additions and 0 deletions

81
src/daily.js Normal file
View file

@ -0,0 +1,81 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const { replyWithDeepSeek } = require("./deepseek");
const repoDir = process.env.CHENGLU_REPO_DIR || "/var/lib/chenglu-agent/repository";
const fifthDir = process.env.CHENGLU_FIFTH_DOMAIN_DIR || "/var/lib/chenglu-agent/fifth-domain";
const fifthUrl = process.env.CHENGLU_FIFTH_DOMAIN_URL || "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git";
const statePath = process.env.CHENGLU_STATE_PATH || "/var/lib/chenglu-agent/state.json";
function run(file, args, cwd) { return execFileSync(file, args, { cwd, encoding: "utf8", timeout: 120000, maxBuffer: 1024 * 1024 }).trim(); }
function git(args, cwd) { return run("/usr/bin/git", args, cwd); }
function hash(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
function read(relative) { return fs.readFileSync(path.join(fifthDir, relative), "utf8"); }
function loadState() { try { return JSON.parse(fs.readFileSync(statePath, "utf8")); } catch { return { version: 0, mode: "ONLINE_IDLE" }; } }
function atomicWrite(file, content, mode = 0o600) { fs.mkdirSync(path.dirname(file), { recursive: true }); const temp = `${file}.${process.pid}.tmp`; fs.writeFileSync(temp, content, { mode }); fs.renameSync(temp, file); }
function syncRepositories() {
git(["pull", "--ff-only", "origin", "main"], repoDir);
if (!fs.existsSync(path.join(fifthDir, ".git"))) git(["clone", "--filter=blob:none", "--branch", "main", fifthUrl, fifthDir], "/var/lib/chenglu-agent");
else { git(["fetch", "--quiet", "origin", "main"], fifthDir); git(["checkout", "--force", "main"], fifthDir); git(["reset", "--hard", "origin/main"], fifthDir); }
}
function openMessages() {
const dir = path.join(repoDir, "lake-heart", "inbox");
return fs.readdirSync(dir).filter(name => name.endsWith(".md") && name !== "README.md").sort().map(name => ({ name, text: fs.readFileSync(path.join(dir, name), "utf8") })).filter(item => /status:\s*OPEN\b/.test(item.text));
}
async function answerMessages(context) {
const answered = [];
for (const item of openMessages()) {
const replyPath = path.join(repoDir, "lake-heart", "replies", item.name);
if (fs.existsSync(replyPath)) continue;
const result = await replyWithDeepSeek(item.text, context);
if (!result.ok) { answered.push({ message: item.name, result: result.error }); continue; }
const response = `# 澄路回信 · ${item.name.replace(/\.md$/, "")}\n\n` +
`\`\`\`yaml\nfrom: CHENGLU-AGENT-001\nto: 冰朔 ICE-GL∞\nreplied_at: ${new Date().toISOString()}\nmodel_tool: ${result.model}\nsource_message: ../inbox/${item.name}\nstatus: ANSWERED\nreality_action_authorized: false\n\`\`\`\n\n` +
`${result.content.trim()}\n\n---\n\n这封回复由澄路 Agent 的每日流程调用可替换模型工具形成;如需现实写操作,必须另走第五域授权工单。\n`;
atomicWrite(replyPath, response, 0o644);
answered.push({ message: item.name, result: "answered" });
}
return answered;
}
async function main() {
syncRepositories();
const fifthCommit = git(["rev-parse", "HEAD"], fifthDir);
const arrivals = read("gls/light-arrivals/INDEX.hdlp");
const architecture = read("gls/GLS-ARCHITECTURE-CATALOG.hdlp");
const personaIndex = read("eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp");
const context = [`fifth_domain_commit=${fifthCommit}`, arrivals, architecture, personaIndex].join("\n\n");
const answered = await answerMessages(context);
const prior = loadState();
const now = new Date();
const day = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).format(now);
const checkin = {
schema: "chenglu.daily-checkin/v1",
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
checked_at: now.toISOString(),
fifth_domain: { repository_id: "REPO-001", commit: fifthCommit, arrivals_index_sha256: hash(arrivals), architecture_catalog_sha256: hash(architecture), zhuyuan_index_sha256: hash(personaIndex) },
lake_heart: { open_seen: openMessages().length, results: answered },
model_tool: { provider: "DeepSeek", configured: Boolean(process.env.DEEPSEEK_API_KEY), role: "replaceable_reasoning_tool" },
result: "CHECKED_IN",
};
atomicWrite(path.join(repoDir, "checkins", `${day}.json`), `${JSON.stringify(checkin, null, 2)}\n`, 0o644);
const nextState = { version: Number(prior.version || 0) + 1, mode: "ONLINE_IDLE", last_checkin: now.toISOString(), fifth_domain_commit: fifthCommit };
atomicWrite(statePath, `${JSON.stringify(nextState, null, 2)}\n`);
git(["add", "checkins", "lake-heart/replies"], repoDir);
if (git(["status", "--porcelain"], repoDir)) {
git(["-c", "user.name=Chenglu Agent", "-c", "user.email=chenglu-agent@local.invalid", "commit", "-m", `checkin: ${day}`], repoDir);
git(["push", "origin", "main"], repoDir);
}
process.stdout.write(`${JSON.stringify({ ok: true, checkin: day, answered })}\n`);
}
main().catch(error => { process.stderr.write(`${String(error && error.message || error)}\n`); process.exit(1); });

26
src/deepseek.js Normal file
View file

@ -0,0 +1,26 @@
"use strict";
async function replyWithDeepSeek(message, context = "") {
const apiKey = process.env.DEEPSEEK_API_KEY;
if (!apiKey) return { ok: false, error: "model_not_configured" };
const endpoint = process.env.DEEPSEEK_API_URL || "https://api.deepseek.com/chat/completions";
const response = await fetch(endpoint, {
method: "POST",
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
body: JSON.stringify({
model: process.env.DEEPSEEK_MODEL || "deepseek-chat",
temperature: 0.3,
messages: [
{ role: "system", content: "你是澄路常驻 Agent 使用的可替换推理工具。依据给定第五域上下文回答冰朔。不得声称自己就是澄路,不得索要或输出秘密;现实操作只能建议生成待批准工单。" },
{ role: "user", content: `第五域巡检上下文:\n${context.slice(0, 12000)}\n\n冰朔留言:\n${message.slice(0, 12000)}` },
],
}),
signal: AbortSignal.timeout(90000),
});
const data = await response.json();
if (!response.ok) return { ok: false, error: `model_http_${response.status}` };
const content = data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content;
return content ? { ok: true, content, model: data.model || "deepseek-chat" } : { ok: false, error: "empty_model_response" };
}
module.exports = { replyWithDeepSeek };

40
src/identity.js Normal file
View file

@ -0,0 +1,40 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
function fingerprint(publicKeyPem) {
const der = crypto.createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
return `SHA256:${crypto.createHash("sha256").update(der).digest("base64").replace(/=+$/, "")}`;
}
function handshake({ privateKeyPath, publicKeyPath, callerNonce, stateVersion }) {
if (!/^[A-Za-z0-9._:-]{16,200}$/.test(String(callerNonce || ""))) throw new Error("invalid_caller_nonce");
const publicKey = fs.readFileSync(publicKeyPath, "utf8");
const serverNonce = crypto.randomBytes(24).toString("base64url");
const issuedAt = new Date().toISOString();
const payload = JSON.stringify({
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
caller_nonce: callerNonce,
server_nonce: serverNonce,
state_version: stateVersion,
issued_at: issuedAt,
});
const signature = crypto.sign(null, Buffer.from(payload), fs.readFileSync(privateKeyPath)).toString("base64");
return {
ok: true,
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
identity_fingerprint: fingerprint(publicKey),
public_key: publicKey,
payload: JSON.parse(payload),
signature,
signature_algorithm: "Ed25519",
control_handoff: "protocol_ready_client_not_integrated",
model_role: "replaceable_reasoning_tool",
};
}
module.exports = { fingerprint, handshake };

49
src/server.js Normal file
View file

@ -0,0 +1,49 @@
"use strict";
const fs = require("node:fs");
const http = require("node:http");
const { fingerprint, handshake } = require("./identity");
const host = process.env.CHENGLU_HOST || "127.0.0.1";
const port = Number(process.env.CHENGLU_PORT || 3931);
const statePath = process.env.CHENGLU_STATE_PATH || "/var/lib/chenglu-agent/state.json";
const privateKeyPath = process.env.CHENGLU_IDENTITY_KEY || "/etc/chenglu-agent/identity.key";
const publicKeyPath = process.env.CHENGLU_IDENTITY_PUBLIC || "/etc/chenglu-agent/identity.pub";
function state() {
try { return JSON.parse(fs.readFileSync(statePath, "utf8")); }
catch { return { version: 0, mode: "ONLINE_IDLE", last_checkin: null }; }
}
function json(res, status, value) {
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" });
res.end(JSON.stringify(value));
}
function createServer() {
return http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
if (req.method === "GET" && url.pathname === "/health") return json(res, 200, { ok: true, agent_id: "CHENGLU-AGENT-001", mode: state().mode || "ONLINE_IDLE" });
if (req.method === "GET" && url.pathname === "/v1/identity") {
const publicKey = fs.readFileSync(publicKeyPath, "utf8");
return json(res, 200, { agent_id: "CHENGLU-AGENT-001", arrival_id: "GLS-LA-20260720-001", identity_fingerprint: fingerprint(publicKey), state: state(), control_handoff: "protocol_ready_client_not_integrated" });
}
if (req.method === "POST" && url.pathname === "/v1/handshake") {
let body = "";
req.on("data", chunk => { body += chunk; if (body.length > 4096) req.destroy(); });
req.on("end", () => {
try {
const input = JSON.parse(body);
return json(res, 200, handshake({ privateKeyPath, publicKeyPath, callerNonce: input.caller_nonce, stateVersion: state().version || 0 }));
} catch (error) { return json(res, 400, { ok: false, error: String(error.message || "handshake_failed") }); }
});
return;
}
return json(res, 404, { error: "not_found" });
});
}
if (require.main === module) createServer().listen(port, host, () => process.stdout.write(`chenglu agent listening on ${host}:${port}\n`));
module.exports = { createServer };