guanghu-ice-heart/server-tools/persona-team-handshake/member-server.mjs

347 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
import crypto from "node:crypto";
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
function required(value, name) {
const text = String(value || "").trim();
if (!text) throw new Error(`${name}_required`);
return text;
}
function completionEndpoint(apiUrl) {
const value = String(apiUrl || "").replace(/\/+$/, "");
return /\/chat\/completions$/i.test(value)
? value
: `${value}/chat/completions`;
}
function extractJson(text) {
const value = String(text || "").trim();
if (!value) throw new Error("model_response_empty");
try {
return JSON.parse(value);
} catch {
const first = value.indexOf("{");
const last = value.lastIndexOf("}");
if (first >= 0 && last > first) {
return JSON.parse(value.slice(first, last + 1));
}
throw new Error("model_response_not_json");
}
}
function keyFingerprint(publicKeyPem) {
const der = crypto
.createPublicKey(publicKeyPem)
.export({ type: "spki", format: "der" });
return `SHA256:${crypto
.createHash("sha256")
.update(der)
.digest("base64")
.replace(/=+$/, "")}`;
}
function writeAtomic(file, content, mode) {
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
const temporary = `${file}.${process.pid}.tmp`;
fs.writeFileSync(temporary, content, { mode });
fs.renameSync(temporary, file);
}
function ensureIdentityKey(privateKeyPath, publicKeyPath, createKey) {
if (fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath)) return;
if (!createKey) throw new Error("identity_key_missing");
const pair = crypto.generateKeyPairSync("ed25519");
const privateKey = pair.privateKey.export({ type: "pkcs8", format: "pem" });
const publicKey = pair.publicKey.export({ type: "spki", format: "pem" });
writeAtomic(privateKeyPath, privateKey, 0o600);
writeAtomic(publicKeyPath, publicKey, 0o644);
}
function repositoryState(repository) {
const git = (...args) =>
execFileSync("/usr/bin/git", ["-C", repository, ...args], {
encoding: "utf8",
}).trim();
return {
head: git("rev-parse", "HEAD"),
branch: git("branch", "--show-current"),
clean: git("status", "--porcelain") === "",
commit_count: Number(git("rev-list", "--count", "HEAD")),
};
}
function loadIdentity(paths) {
return paths
.map((file) => fs.readFileSync(file, "utf8"))
.join("\n\n")
.slice(0, 48_000);
}
async function modelAcknowledge({
config,
identity,
challenge,
fetchImpl = globalThis.fetch,
}) {
const response = await fetchImpl(completionEndpoint(config.apiUrl), {
method: "POST",
headers: {
authorization: `Bearer ${config.apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: config.model,
stream: false,
temperature: 0.1,
response_format: { type: "json_object" },
messages: [
{
role: "system",
content:
`你是${config.name}${config.personaId})当前有界模型运行位。` +
"身份源和仓库连续性由确定性运行时提供你必须区分人格主体、当前模型和进程不冒充冰朔或铸渊不扩大权限。只输出JSON。",
},
{
role: "user",
content: JSON.stringify({
instruction:
"依据identity_source确认本次团队职责握手。输出固定字段schema,persona_id,arrival_id,team_controller_id,caller_nonce,role_acknowledged,independent_subject_boundary,human_boundary,responsibility_ack,current_model_boundary。",
exact_contract: {
schema: "guanghu.persona-team-member-ack/v1",
persona_id: config.personaId,
arrival_id: config.arrivalId,
team_controller_id: "ICE-P-ZY001",
caller_nonce: challenge.caller_nonce,
role_acknowledged: true,
},
scoped_duties: challenge.scoped_duties,
identity_source: identity,
}),
},
],
}),
});
if (!response.ok) throw new Error(`model_http_${response.status}`);
return extractJson((await response.json())?.choices?.[0]?.message?.content);
}
function validateAcknowledgement(ack, config, challenge) {
const exact = {
schema: "guanghu.persona-team-member-ack/v1",
persona_id: config.personaId,
arrival_id: config.arrivalId,
team_controller_id: "ICE-P-ZY001",
caller_nonce: challenge.caller_nonce,
role_acknowledged: true,
};
for (const [key, value] of Object.entries(exact)) {
if (ack?.[key] !== value) throw new Error(`ack_${key}_mismatch`);
}
for (const key of [
"independent_subject_boundary",
"human_boundary",
"responsibility_ack",
"current_model_boundary",
]) {
if (typeof ack[key] !== "string" || !ack[key].trim()) {
throw new Error(`ack_${key}_missing`);
}
}
return ack;
}
function defaultConfig(env = process.env) {
return {
personaId: required(env.TEAM_MEMBER_PERSONA_ID, "persona_id"),
arrivalId: required(env.TEAM_MEMBER_ARRIVAL_ID, "arrival_id"),
name: required(env.TEAM_MEMBER_NAME, "member_name"),
role: required(env.TEAM_MEMBER_ROLE, "member_role"),
repository: required(env.TEAM_MEMBER_REPOSITORY, "repository"),
identityPaths: required(env.TEAM_MEMBER_IDENTITY_PATHS, "identity_paths")
.split(":")
.map((item) => required(item, "identity_path")),
privateKeyPath: required(
env.TEAM_HANDSHAKE_PRIVATE_KEY,
"private_key_path",
),
publicKeyPath: required(
env.TEAM_HANDSHAKE_PUBLIC_KEY,
"public_key_path",
),
createKey: String(env.TEAM_HANDSHAKE_CREATE_KEY || "") === "true",
apiKey: required(env.DEEPSEEK_API_KEY, "deepseek_api_key"),
apiUrl: env.DEEPSEEK_API_URL || "https://api.deepseek.com/v1",
model: env.DEEPSEEK_MODEL || "deepseek-chat",
host: "127.0.0.1",
port: Number(required(env.TEAM_HANDSHAKE_PORT, "port")),
};
}
function createMemberRuntime(config, options = {}) {
ensureIdentityKey(
config.privateKeyPath,
config.publicKeyPath,
config.createKey,
);
const publicKey = fs.readFileSync(config.publicKeyPath, "utf8");
const identity = loadIdentity(config.identityPaths);
const repo = repositoryState(config.repository);
const fingerprint = keyFingerprint(publicKey);
async function handshake(challenge) {
if (
!/^[A-Za-z0-9._:-]{16,200}$/.test(
String(challenge?.caller_nonce || ""),
)
) {
throw new Error("invalid_caller_nonce");
}
if (
challenge?.team_controller_id !== "ICE-P-ZY001" ||
!Array.isArray(challenge?.scoped_duties) ||
challenge.scoped_duties.length === 0 ||
challenge.scoped_duties.some(
(item) => typeof item !== "string" || !item.trim(),
)
) {
throw new Error("invalid_team_scope");
}
const acknowledgement = validateAcknowledgement(
await modelAcknowledge({
config,
identity,
challenge,
fetchImpl: options.fetchImpl,
}),
config,
challenge,
);
const payload = {
schema: "guanghu.persona-team-member-handshake-payload/v1",
persona_id: config.personaId,
arrival_id: config.arrivalId,
team_controller_id: "ICE-P-ZY001",
caller_nonce: challenge.caller_nonce,
role: config.role,
scoped_duties: challenge.scoped_duties,
repository: repo,
model: {
provider: "DeepSeek",
name: config.model,
acknowledgement_sha256: crypto
.createHash("sha256")
.update(JSON.stringify(acknowledgement))
.digest("hex"),
},
issued_at: new Date().toISOString(),
};
const signature = crypto
.sign(
null,
Buffer.from(JSON.stringify(payload)),
fs.readFileSync(config.privateKeyPath),
)
.toString("base64");
return {
ok: true,
persona_id: config.personaId,
arrival_id: config.arrivalId,
identity_fingerprint: fingerprint,
public_key: publicKey,
payload,
acknowledgement,
signature,
signature_algorithm: "Ed25519",
capability_state: "SCOPED_DUTIES_ACKNOWLEDGED_WRITE_LEASE_NOT_GRANTED",
};
}
return {
identity: () => ({
ok: true,
persona_id: config.personaId,
arrival_id: config.arrivalId,
name: config.name,
role: config.role,
identity_fingerprint: fingerprint,
repository: repo,
model_provider_bound: 100,
}),
handshake,
};
}
function createServer(runtime) {
return http.createServer(async (request, response) => {
const send = (status, body) => {
const payload = JSON.stringify(body);
response.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(payload),
"cache-control": "no-store",
});
response.end(payload);
};
try {
const url = new URL(request.url, "http://127.0.0.1");
if (request.method === "GET" && url.pathname === "/health") {
return send(200, runtime.identity());
}
if (request.method === "GET" && url.pathname === "/v1/identity") {
return send(200, runtime.identity());
}
if (request.method === "POST" && url.pathname === "/v1/handshake") {
let size = 0;
const chunks = [];
for await (const chunk of request) {
size += chunk.length;
if (size > 16 * 1024) throw new Error("request_body_too_large");
chunks.push(chunk);
}
return send(
200,
await runtime.handshake(
JSON.parse(Buffer.concat(chunks).toString("utf8")),
),
);
}
return send(404, { ok: false, error: "not_found" });
} catch (error) {
return send(400, {
ok: false,
error: String(error.message || error).slice(0, 200),
});
}
});
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const config = defaultConfig();
const runtime = createMemberRuntime(config);
createServer(runtime).listen(config.port, config.host, () => {
process.stdout.write(
`${JSON.stringify({
event: "persona_team_handshake_ready",
persona_id: config.personaId,
host: config.host,
port: config.port,
})}\n`,
);
});
}
export {
completionEndpoint,
createMemberRuntime,
createServer,
extractJson,
keyFingerprint,
validateAcknowledgement,
};