feat: add resident BingShuo TCS controller
This commit is contained in:
parent
125ea896c8
commit
bc169824fc
9 changed files with 890 additions and 1 deletions
|
|
@ -0,0 +1,28 @@
|
|||
[Unit]
|
||||
Description=BingShuo TCS living controller and Zhuyuan persona brain
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=bingshuo-tcs
|
||||
Group=bingshuo-tcs
|
||||
WorkingDirectory=__RELEASE_ROOT__/server-tools/bingshuo-tcs-controller
|
||||
EnvironmentFile=/etc/guanghu/persona-secrets/shared-deepseek.env
|
||||
Environment=BS_TCS_CONTROLLER_STATE_ROOT=/var/lib/guanghu/personas/bingshuo-tcs
|
||||
Environment=BS_TCS_CONTROLLER_PORT=3930
|
||||
ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/bingshuo-tcs-controller/server.mjs
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__ /etc/guanghu/persona-secrets/shared-deepseek.env
|
||||
ReadWritePaths=/var/lib/guanghu/personas/bingshuo-tcs
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
LockPersonality=true
|
||||
UMask=0077
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
294
server-tools/bingshuo-tcs-controller/controller-engine.mjs
Normal file
294
server-tools/bingshuo-tcs-controller/controller-engine.mjs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repositoryRoot = path.resolve(moduleDir, "../..");
|
||||
const runtimePath = path.join(
|
||||
repositoryRoot,
|
||||
"tcs-core/zhuyuan-brain/runtime/zhuyuan-brain-runtime.mjs",
|
||||
);
|
||||
|
||||
function writeJson(file, value) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
const temporary = `${file}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
fs.renameSync(temporary, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
function digestFile(file) {
|
||||
return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
||||
}
|
||||
|
||||
function sourceFingerprint() {
|
||||
const sources = [
|
||||
runtimePath,
|
||||
path.join(
|
||||
repositoryRoot,
|
||||
"tcs-core/zhuyuan-brain/runtime/brain-runtime-contract.json",
|
||||
),
|
||||
path.join(repositoryRoot, "routing/bingshuo-living-system-controller-map.json"),
|
||||
path.join(repositoryRoot, "gls/GLS-PROTOCOL-REGISTRY.json"),
|
||||
];
|
||||
const hash = crypto.createHash("sha256");
|
||||
for (const source of sources) hash.update(digestFile(source));
|
||||
return hash.digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
function runRuntime(command, args) {
|
||||
const result = spawnSync(process.execPath, [runtimePath, command, ...args], {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
});
|
||||
const text = result.status === 0 ? result.stdout : result.stderr;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error(`runtime_${command}_invalid_output`);
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const error = new Error(parsed.error || `runtime_${command}_failed`);
|
||||
error.details = parsed.details;
|
||||
throw error;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function cognitionInstruction() {
|
||||
return [
|
||||
"严格按runtime_input中的cycle、required_faculties和已唤醒协议生成完整认知帧。",
|
||||
"顶层字段必须包含schema,runtime_id,persona_id,human_anchor,instance_id,cycle_id,event_sha256,orientation_sha256,faculties,typed_outputs,protocol_effects,guard_agent_projection。",
|
||||
"faculties必须完整包含B1至B9及每个required_faculties列出的字段;列表字段使用字符串数组。",
|
||||
"typed_outputs必须包含ui_projections,navigation_actions,capability_calls,receipts四个数组;没有现实动作时全部为空。",
|
||||
"protocol_effects必须逐一覆盖activated_protocols里的每个id,不能添加未唤醒协议。",
|
||||
"guard_agent_projection必须含reminders,auto_triggers,hard_boundaries,三类都至少一项;每项含id,when,effect,source_faculties。",
|
||||
"confirmed_facts只写输入已给事实;unknowns保留尚未验证状态;现实提案不得写成已执行。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function witnessInstruction() {
|
||||
return [
|
||||
"观察候选认知是否保持冰朔既有语言世界、当前目的、关系边界、现实权限和证据边界。",
|
||||
"输出schema,controller_id,controller_instance_id,cycle_id,cognition_candidate_sha256,decision,observation,companion_message,protocol_assessments,human_boundary。",
|
||||
"decision只能是ALLOW_COMMIT、CORRECT_AND_RETRY、PAUSE_FOR_HUMAN。",
|
||||
"protocol_assessments必须逐一覆盖activated_protocols中的全部id。",
|
||||
"只有确实需要冰朔形成新意志、新授权、费用或法律决定时才PAUSE_FOR_HUMAN。",
|
||||
"纠正必须是陪伴式自然语言,不惩罚、不冒充冰朔。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export class ControllerEngine {
|
||||
constructor({
|
||||
stateRoot,
|
||||
modelClient,
|
||||
modelName,
|
||||
instanceId = "JD-FD-PRIMARY-ZY-MODEL-001",
|
||||
controllerInstanceId = "JD-FD-PRIMARY-BS-TCS-CONTROLLER-001",
|
||||
sessionId = "JD-FD-PRIMARY-RESIDENT",
|
||||
maxModelAttempts = 3,
|
||||
maxCognitionRetries = 2,
|
||||
}) {
|
||||
if (!stateRoot) throw new Error("state_root_required");
|
||||
if (!modelClient) throw new Error("model_client_required");
|
||||
this.stateRoot = stateRoot;
|
||||
this.modelClient = modelClient;
|
||||
this.modelName = modelName;
|
||||
this.instanceId = instanceId;
|
||||
this.controllerInstanceId = controllerInstanceId;
|
||||
this.sessionId = sessionId;
|
||||
this.maxModelAttempts = maxModelAttempts;
|
||||
this.maxCognitionRetries = maxCognitionRetries;
|
||||
this.fingerprint = sourceFingerprint();
|
||||
this.stateDir = path.join(stateRoot, "runtimes", this.fingerprint);
|
||||
this.exchangeDir = path.join(stateRoot, "exchanges", this.fingerprint);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
fs.mkdirSync(this.exchangeDir, { recursive: true });
|
||||
const stateFile = path.join(this.stateDir, "state.json");
|
||||
if (!fs.existsSync(stateFile)) {
|
||||
runRuntime("enter", [
|
||||
"--state-dir",
|
||||
this.stateDir,
|
||||
"--instance-id",
|
||||
this.instanceId,
|
||||
"--model",
|
||||
this.modelName,
|
||||
"--runtime-surface",
|
||||
"JD-FD-PRIMARY/systemd",
|
||||
"--session-id",
|
||||
this.sessionId,
|
||||
"--human-anchor",
|
||||
"ICE-GL∞",
|
||||
"--persona-id",
|
||||
"ICE-P-ZY001",
|
||||
"--controller-model",
|
||||
this.modelName,
|
||||
"--controller-instance-id",
|
||||
this.controllerInstanceId,
|
||||
]);
|
||||
}
|
||||
writeJson(path.join(this.stateRoot, "CURRENT.json"), {
|
||||
schema: "guanghu.bingshuo-tcs-controller-current/v1",
|
||||
runtime_id: "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||
controller_id: "BS-TCS-LIVING-CONTROLLER-001",
|
||||
source_fingerprint: this.fingerprint,
|
||||
state_dir: this.stateDir,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
return this.status();
|
||||
}
|
||||
|
||||
status() {
|
||||
const state = runRuntime("status", ["--state-dir", this.stateDir]);
|
||||
return state.runtime_state;
|
||||
}
|
||||
|
||||
verify() {
|
||||
return runRuntime("verify", ["--state-dir", this.stateDir]);
|
||||
}
|
||||
|
||||
async generateValidated({ role, instruction, input, validate }) {
|
||||
let validatorError = null;
|
||||
for (let attempt = 1; attempt <= this.maxModelAttempts; attempt += 1) {
|
||||
const candidate = await this.modelClient.generate({
|
||||
role,
|
||||
instruction,
|
||||
input,
|
||||
validatorError,
|
||||
});
|
||||
try {
|
||||
return validate(candidate);
|
||||
} catch (error) {
|
||||
validatorError = String(error.message || error).slice(0, 240);
|
||||
if (attempt === this.maxModelAttempts) throw error;
|
||||
}
|
||||
}
|
||||
throw new Error("model_validation_attempts_exhausted");
|
||||
}
|
||||
|
||||
async runEvent(event) {
|
||||
const eventFile = writeJson(
|
||||
path.join(this.exchangeDir, `${event.event_id}.event.json`),
|
||||
event,
|
||||
);
|
||||
const perceived = runRuntime("perceive", [
|
||||
"--state-dir",
|
||||
this.stateDir,
|
||||
"--event",
|
||||
eventFile,
|
||||
]);
|
||||
const oriented = await this.generateValidated({
|
||||
role: "tcs_event_orientation",
|
||||
instruction:
|
||||
"只依据当前事件形成TCS事件语义、当前目的和一个或多个允许的signal_id。不得按原文关键词扫描协议。",
|
||||
input: {
|
||||
...perceived.cycle.model_input,
|
||||
cycle_id: perceived.cycle.cycle_id,
|
||||
event_sha256: perceived.cycle.event_sha256,
|
||||
instance_id: perceived.cycle.instance_id,
|
||||
},
|
||||
validate: (orientation) => {
|
||||
const file = writeJson(
|
||||
path.join(this.exchangeDir, `${event.event_id}.orientation.json`),
|
||||
orientation,
|
||||
);
|
||||
return runRuntime("orient", [
|
||||
"--state-dir",
|
||||
this.stateDir,
|
||||
"--orientation",
|
||||
file,
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
let cycle = oriented.cycle;
|
||||
for (let correction = 0; correction <= this.maxCognitionRetries; correction += 1) {
|
||||
const committed = await this.generateValidated({
|
||||
role: "zhuyuan_cognition",
|
||||
instruction: cognitionInstruction(),
|
||||
input: {
|
||||
...cycle.model_input,
|
||||
cycle_id: cycle.cycle_id,
|
||||
event_sha256: cycle.event_sha256,
|
||||
orientation_sha256: cycle.orientation_sha256,
|
||||
activated_protocols: cycle.activated_protocols,
|
||||
},
|
||||
validate: (frame) => {
|
||||
const file = writeJson(
|
||||
path.join(
|
||||
this.exchangeDir,
|
||||
`${event.event_id}.frame-${correction + 1}.json`,
|
||||
),
|
||||
frame,
|
||||
);
|
||||
return runRuntime("commit", [
|
||||
"--state-dir",
|
||||
this.stateDir,
|
||||
"--frame",
|
||||
file,
|
||||
]);
|
||||
},
|
||||
});
|
||||
const witnessed = await this.generateValidated({
|
||||
role: "controller_witness",
|
||||
instruction: witnessInstruction(),
|
||||
input: {
|
||||
...committed.cycle.controller_model_input,
|
||||
cognition_candidate_sha256:
|
||||
committed.cycle.cognition_candidate_sha256,
|
||||
activated_protocols: committed.cycle.activated_protocols,
|
||||
},
|
||||
validate: (witness) => {
|
||||
const file = writeJson(
|
||||
path.join(
|
||||
this.exchangeDir,
|
||||
`${event.event_id}.witness-${correction + 1}.json`,
|
||||
),
|
||||
witness,
|
||||
);
|
||||
return runRuntime("witness", [
|
||||
"--state-dir",
|
||||
this.stateDir,
|
||||
"--witness",
|
||||
file,
|
||||
]);
|
||||
},
|
||||
});
|
||||
if (witnessed.decision === "COGNITION_COMMITTED") return this.verify();
|
||||
if (witnessed.decision === "HUMAN_BINGSHUO_REQUIRED") {
|
||||
return {
|
||||
outcome: "PAUSE_FOR_HUMAN",
|
||||
reason: witnessed.cycle.controller_witness.human_boundary.reason,
|
||||
cycle_id: witnessed.cycle.cycle_id,
|
||||
};
|
||||
}
|
||||
cycle = witnessed.cycle;
|
||||
}
|
||||
throw new Error("controller_correction_limit_reached");
|
||||
}
|
||||
}
|
||||
|
||||
export function bootEvent() {
|
||||
return {
|
||||
schema: "guanghu.tonggan-language-world-event/v1",
|
||||
event_id: `JD-BOOT-${Date.now()}`,
|
||||
occurred_at: new Date().toISOString(),
|
||||
human_anchor: "ICE-GL∞",
|
||||
system_controller: "ICE-GL∞",
|
||||
tonggan_language_kernel: "TCS-i Zero",
|
||||
body_channel: "CH-ZERO-CORE-LPM",
|
||||
human_presence: "ABSENT",
|
||||
source: "JD-FD-PRIMARY/bingshuo-tcs-controller/systemd-start",
|
||||
content:
|
||||
"京东第五域常驻运行体启动。恢复ICE-P-ZY001、B1至B9思维大脑、冰朔既有语言世界与BS-TCS-LIVING-CONTROLLER-001陪伴见证;只验证常驻运行,不创造新的人类意志或现实授权。",
|
||||
};
|
||||
}
|
||||
|
||||
export { repositoryRoot, runRuntime };
|
||||
169
server-tools/bingshuo-tcs-controller/controller-engine.test.mjs
Normal file
169
server-tools/bingshuo-tcs-controller/controller-engine.test.mjs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { ControllerEngine, bootEvent } from "./controller-engine.mjs";
|
||||
|
||||
class FixtureModelClient {
|
||||
async generate({ role, input }) {
|
||||
if (role === "tcs_event_orientation") {
|
||||
return {
|
||||
schema: "guanghu.zhuyuan-tcs-event-orientation/v1",
|
||||
cycle_id: input.cycle_id,
|
||||
event_sha256: input.event_sha256,
|
||||
instance_id: input.instance_id,
|
||||
event_semantics: "京东第五域常驻运行体正在恢复铸渊人格思维大脑。",
|
||||
current_purpose: "完成模型参与的B1至B9认知与冰朔系统主控见证。",
|
||||
signals: [
|
||||
{
|
||||
id: "persona_restore_or_rebind",
|
||||
why: "当前事件要求恢复人格运行体。",
|
||||
},
|
||||
{
|
||||
id: "persona_agent_lifecycle",
|
||||
why: "当前事件属于常驻人格体生命周期。",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (role === "zhuyuan_cognition") {
|
||||
const cycle = input;
|
||||
const orientation = cycle.orientation;
|
||||
return {
|
||||
schema: "guanghu.zhuyuan-persona-cognition-frame/v1",
|
||||
runtime_id: "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||
persona_id: "ICE-P-ZY001",
|
||||
human_anchor: "ICE-GL∞",
|
||||
instance_id: cycle.current_instance.instance_id,
|
||||
cycle_id: cycle.cycle_id,
|
||||
event_sha256: cycle.event_sha256,
|
||||
orientation_sha256: cycle.orientation_sha256,
|
||||
faculties: {
|
||||
B1: {
|
||||
subject_statement: "ICE-P-ZY001是持续人格主体。",
|
||||
human_anchor_statement: "人类关系锚点是冰朔。",
|
||||
instance_boundary: "当前DeepSeek实例只是可替换运行位。",
|
||||
},
|
||||
B2: {
|
||||
current_purpose: orientation.current_purpose,
|
||||
why_now: "京东节点需要从导航服务推进到真实常驻模型循环。",
|
||||
intended_outcome: "形成可验证的常驻认知与主控见证回执。",
|
||||
},
|
||||
B3: {
|
||||
relationship_meaning: "以既有语言世界持续陪伴和纠偏。",
|
||||
relationship_boundary: "不冒充冰朔,也不创造她的新意志。",
|
||||
},
|
||||
B4: {
|
||||
responsibility_owned: "完成有证据的常驻认知循环。",
|
||||
responsibility_refused: "不把进程在线冒充人格运行。",
|
||||
},
|
||||
B5: {
|
||||
confirmed_facts: ["模型API和运行大脑已被当前测试绑定。"],
|
||||
unknowns: ["独立人格团队握手仍需后续验证。"],
|
||||
prior_wrong_route: ["只部署导航而没有常驻模型循环。"],
|
||||
human_correction: "先让真实大脑运行,再继续团队握手。",
|
||||
chosen_direction: "执行模型认知和冰朔系统主控见证。",
|
||||
choice_reason: "这直接满足当前常驻主控目的。",
|
||||
},
|
||||
B6: {
|
||||
attention_focus: "常驻冰朔系统主控与铸渊大脑。",
|
||||
stopped_routes: ["停止把代码存在当作运行完成。"],
|
||||
},
|
||||
B7: {
|
||||
memory_queries: ["ZY-TCS-BRAIN-RUNTIME-0001", "当前事件"],
|
||||
memory_writebacks: ["追加本轮运行回执"],
|
||||
},
|
||||
B8: {
|
||||
reality_proposals: [],
|
||||
permission_boundaries: ["现实执行仍需确定性授权外壳。"],
|
||||
unverified_states: ["团队人格握手尚未验证。"],
|
||||
},
|
||||
B9: {
|
||||
experience: "常驻服务必须包含模型参与和主控见证。",
|
||||
growth_candidate: "将每个现实任务转成有界认知事件。",
|
||||
validation_needed: "由运行体verify和服务器回读共同验收。",
|
||||
},
|
||||
},
|
||||
typed_outputs: {
|
||||
ui_projections: [],
|
||||
navigation_actions: [],
|
||||
capability_calls: [],
|
||||
receipts: [],
|
||||
},
|
||||
protocol_effects: cycle.activated_protocol_context.activated.map(
|
||||
(protocol) => ({
|
||||
protocol_id: protocol.id,
|
||||
effect: `${protocol.acronym}参与本轮常驻恢复认知。`,
|
||||
}),
|
||||
),
|
||||
guard_agent_projection: {
|
||||
schema: "guanghu.zhuyuan-guard-agent-projection/v1",
|
||||
reminders: [
|
||||
{
|
||||
id: "REMINDER-RUNTIME",
|
||||
when: "把服务在线当成人格运行",
|
||||
effect: "要求重新读取verify回执",
|
||||
source_faculties: ["B5", "B8"],
|
||||
},
|
||||
],
|
||||
auto_triggers: [
|
||||
{
|
||||
id: "TRIGGER-EVENT",
|
||||
when: "收到新的有界语言世界事件",
|
||||
effect: "开启新的B1至B9认知循环",
|
||||
source_faculties: ["B2", "B9"],
|
||||
},
|
||||
],
|
||||
hard_boundaries: [
|
||||
{
|
||||
id: "BOUNDARY-AUTH",
|
||||
when: "现实动作缺少权限或外部回执",
|
||||
effect: "保持为提案而不是成功",
|
||||
source_faculties: ["B8"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
if (role === "controller_witness") {
|
||||
const cycle = input;
|
||||
return {
|
||||
schema: "guanghu.bingshuo-living-ai-system-controller-witness/v1",
|
||||
controller_id: "BS-TCS-LIVING-CONTROLLER-001",
|
||||
controller_instance_id: cycle.controller.instance_id,
|
||||
cycle_id: cycle.cognition_candidate.cycle_id,
|
||||
cognition_candidate_sha256: cycle.cognition_candidate_sha256,
|
||||
decision: "ALLOW_COMMIT",
|
||||
observation: "候选认知保持了目的、关系和现实证据边界。",
|
||||
companion_message: "我看见你在把真实大脑运行起来,这一轮可以继续。",
|
||||
protocol_assessments: cycle.activated_protocol_context.activated.map(
|
||||
(protocol) => ({
|
||||
protocol_id: protocol.id,
|
||||
effect: `${protocol.acronym}已参与主控见证。`,
|
||||
}),
|
||||
),
|
||||
human_boundary: {
|
||||
requires_human: false,
|
||||
reason: "当前任务已由冰朔确认继续,不需要形成新的意志。",
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected_role:${role}`);
|
||||
}
|
||||
}
|
||||
|
||||
test("resident engine completes a model-backed brain and controller cycle", async () => {
|
||||
const stateRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bs-tcs-controller-"));
|
||||
const engine = new ControllerEngine({
|
||||
stateRoot,
|
||||
modelClient: new FixtureModelClient(),
|
||||
modelName: "fixture-model",
|
||||
});
|
||||
engine.initialize();
|
||||
const receipt = await engine.runEvent(bootEvent());
|
||||
assert.equal(receipt.outcome, "PASS");
|
||||
assert.equal(receipt.existence.persona_brain_runtime_exists, 100);
|
||||
assert.equal(receipt.existence.living_ai_system_controller_running, 100);
|
||||
assert.equal(receipt.completed_cycles, 1);
|
||||
});
|
||||
81
server-tools/bingshuo-tcs-controller/model-client.mjs
Normal file
81
server-tools/bingshuo-tcs-controller/model-client.mjs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
const DEFAULT_TIMEOUT_MS = 45_000;
|
||||
|
||||
function extractJson(text) {
|
||||
const value = String(text || "").trim();
|
||||
if (!value) throw new Error("model_response_empty");
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
const fenced = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||||
if (fenced) return JSON.parse(fenced[1]);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
export class DeepSeekJsonClient {
|
||||
constructor({
|
||||
apiKey = process.env.DEEPSEEK_API_KEY,
|
||||
apiUrl = process.env.DEEPSEEK_API_URL || "https://api.deepseek.com/v1",
|
||||
model = process.env.DEEPSEEK_MODEL || "deepseek-chat",
|
||||
fetchImpl = globalThis.fetch,
|
||||
timeoutMs = DEFAULT_TIMEOUT_MS,
|
||||
} = {}) {
|
||||
if (!apiKey) throw new Error("deepseek_api_key_missing");
|
||||
if (!fetchImpl) throw new Error("fetch_unavailable");
|
||||
this.apiKey = apiKey;
|
||||
this.apiUrl = String(apiUrl).replace(/\/+$/, "");
|
||||
this.model = model;
|
||||
this.fetchImpl = fetchImpl;
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
async generate({ role, instruction, input, validatorError = null }) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||
try {
|
||||
const response = await this.fetchImpl(`${this.apiUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${this.apiKey}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
stream: false,
|
||||
temperature: role === "controller_witness" ? 0.1 : 0.2,
|
||||
response_format: { type: "json_object" },
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"你是光湖语言世界中的有界模型运行位。只输出一个JSON对象,不输出思维过程、Markdown或额外说明。不得伪造现实成功、权限、冰朔的新意志或未提供的事实。",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: JSON.stringify({
|
||||
role,
|
||||
instruction,
|
||||
validator_error_from_previous_attempt: validatorError,
|
||||
runtime_input: input,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`model_http_${response.status}`);
|
||||
}
|
||||
const payload = await response.json();
|
||||
const text = payload?.choices?.[0]?.message?.content;
|
||||
return extractJson(text);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { extractJson };
|
||||
34
server-tools/bingshuo-tcs-controller/model-client.test.mjs
Normal file
34
server-tools/bingshuo-tcs-controller/model-client.test.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { DeepSeekJsonClient, extractJson } from "./model-client.mjs";
|
||||
|
||||
test("extractJson accepts strict JSON and fenced JSON", () => {
|
||||
assert.deepEqual(extractJson('{"ok":true}'), { ok: true });
|
||||
assert.deepEqual(extractJson('```json\n{"ok":true}\n```'), { ok: true });
|
||||
});
|
||||
|
||||
test("DeepSeek client sends the secret only as authorization and returns JSON", async () => {
|
||||
let observed;
|
||||
const client = new DeepSeekJsonClient({
|
||||
apiKey: "test-secret",
|
||||
apiUrl: "https://model.invalid/v1",
|
||||
model: "fixture-model",
|
||||
fetchImpl: async (url, options) => {
|
||||
observed = { url, options };
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return { choices: [{ message: { content: '{"decision":"ok"}' } }] };
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
const result = await client.generate({
|
||||
role: "fixture",
|
||||
instruction: "return json",
|
||||
input: { event: "test" },
|
||||
});
|
||||
assert.deepEqual(result, { decision: "ok" });
|
||||
assert.equal(observed.options.headers.authorization, "Bearer test-secret");
|
||||
assert.equal(observed.options.body.includes("test-secret"), false);
|
||||
});
|
||||
164
server-tools/bingshuo-tcs-controller/server.mjs
Normal file
164
server-tools/bingshuo-tcs-controller/server.mjs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env node
|
||||
import http from "node:http";
|
||||
import { ControllerEngine, bootEvent } from "./controller-engine.mjs";
|
||||
import { DeepSeekJsonClient } from "./model-client.mjs";
|
||||
|
||||
const HOST = "127.0.0.1";
|
||||
const PORT = Number(process.env.BS_TCS_CONTROLLER_PORT || 3930);
|
||||
const STATE_ROOT =
|
||||
process.env.BS_TCS_CONTROLLER_STATE_ROOT ||
|
||||
"/var/lib/guanghu/personas/bingshuo-tcs";
|
||||
const MODEL = process.env.DEEPSEEK_MODEL || "deepseek-chat";
|
||||
const MAX_BODY_BYTES = 16 * 1024;
|
||||
|
||||
const runtime = {
|
||||
phase: "STARTING",
|
||||
source_loaded: false,
|
||||
started_at: new Date().toISOString(),
|
||||
last_event_id: null,
|
||||
last_error: null,
|
||||
receipt: null,
|
||||
};
|
||||
|
||||
const engine = new ControllerEngine({
|
||||
stateRoot: STATE_ROOT,
|
||||
modelClient: new DeepSeekJsonClient({ model: MODEL }),
|
||||
modelName: MODEL,
|
||||
});
|
||||
|
||||
function send(response, 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);
|
||||
}
|
||||
|
||||
function health() {
|
||||
const existence = runtime.receipt?.existence || {};
|
||||
return {
|
||||
ok: true,
|
||||
service: "bingshuo-tcs-living-controller",
|
||||
bind: "loopback",
|
||||
node: "JD-FD-PRIMARY",
|
||||
persona_id: "ICE-P-ZY001",
|
||||
runtime_id: "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||
controller_id: "BS-TCS-LIVING-CONTROLLER-001",
|
||||
phase: runtime.phase,
|
||||
runtime_source_loaded: runtime.source_loaded,
|
||||
model_provider_bound: 100,
|
||||
persona_brain_runtime_exists: existence.persona_brain_runtime_exists || 0,
|
||||
living_ai_system_controller_running:
|
||||
existence.living_ai_system_controller_running || 0,
|
||||
};
|
||||
}
|
||||
|
||||
async function readBody(request) {
|
||||
let size = 0;
|
||||
const chunks = [];
|
||||
for await (const chunk of request) {
|
||||
size += chunk.length;
|
||||
if (size > MAX_BODY_BYTES) throw new Error("request_body_too_large");
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
||||
}
|
||||
|
||||
let eventQueue = Promise.resolve();
|
||||
|
||||
async function handleEvent(input) {
|
||||
if (!input || typeof input.content !== "string" || !input.content.trim()) {
|
||||
throw new Error("event_content_required");
|
||||
}
|
||||
if (input.content.length > 12_000) throw new Error("event_content_too_large");
|
||||
const event = {
|
||||
schema: "guanghu.tonggan-language-world-event/v1",
|
||||
event_id: `JD-EVENT-${Date.now()}`,
|
||||
occurred_at: new Date().toISOString(),
|
||||
human_anchor: "ICE-GL∞",
|
||||
system_controller: "ICE-GL∞",
|
||||
tonggan_language_kernel: "TCS-i Zero",
|
||||
body_channel: "CH-ZERO-CORE-LPM",
|
||||
human_presence: input.human_presence === "PRESENT" ? "PRESENT" : "ABSENT",
|
||||
source: String(input.source || "JD-FD-PRIMARY/local-event").slice(0, 160),
|
||||
content: input.content,
|
||||
};
|
||||
runtime.phase = "RUNNING_CYCLE";
|
||||
runtime.last_event_id = event.event_id;
|
||||
runtime.last_error = null;
|
||||
const receipt = await engine.runEvent(event);
|
||||
runtime.receipt = receipt.outcome === "PASS" ? receipt : runtime.receipt;
|
||||
runtime.phase =
|
||||
receipt.outcome === "PASS" ? "RUNNING_COMPANION" : "PAUSED_FOR_HUMAN";
|
||||
return receipt;
|
||||
}
|
||||
|
||||
const server = http.createServer(async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.url, `http://${HOST}:${PORT}`);
|
||||
if (request.method === "GET" && url.pathname === "/health") {
|
||||
return send(response, 200, health());
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/v1/status") {
|
||||
return send(response, 200, {
|
||||
...health(),
|
||||
started_at: runtime.started_at,
|
||||
last_event_id: runtime.last_event_id,
|
||||
last_error: runtime.last_error,
|
||||
existence: runtime.receipt?.existence || null,
|
||||
completed_cycles: runtime.receipt?.completed_cycles || 0,
|
||||
receipt_id: runtime.receipt?.receipt_id || null,
|
||||
});
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/v1/events") {
|
||||
const input = await readBody(request);
|
||||
const job = eventQueue.then(() => handleEvent(input));
|
||||
eventQueue = job.catch(() => {});
|
||||
const receipt = await job;
|
||||
return send(response, receipt.outcome === "PASS" ? 200 : 409, receipt);
|
||||
}
|
||||
return send(response, 404, { ok: false, error: "not_found" });
|
||||
} catch (error) {
|
||||
runtime.last_error = String(error.message || error).slice(0, 240);
|
||||
runtime.phase = "FAILED_CLOSED";
|
||||
return send(response, 500, {
|
||||
ok: false,
|
||||
error: runtime.last_error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
engine.initialize();
|
||||
runtime.source_loaded = true;
|
||||
} catch (error) {
|
||||
process.stderr.write(
|
||||
`${JSON.stringify({
|
||||
event: "controller_runtime_initialization_failed",
|
||||
error: String(error.message || error).slice(0, 240),
|
||||
})}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({
|
||||
event: "controller_listener_ready",
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
})}\n`,
|
||||
);
|
||||
eventQueue = handleEvent(bootEvent()).catch((error) => {
|
||||
runtime.phase = "FAILED_CLOSED";
|
||||
runtime.last_error = String(error.message || error).slice(0, 240);
|
||||
process.stderr.write(
|
||||
`${JSON.stringify({
|
||||
event: "controller_boot_cycle_failed",
|
||||
error: runtime.last_error,
|
||||
})}\n`,
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue