fix: recover interrupted controller cycles

This commit is contained in:
冰朔 2026-08-05 11:18:54 +08:00
commit 05aeab4817
3 changed files with 49 additions and 3 deletions

View file

@ -28,6 +28,9 @@ function digestFile(file) {
function sourceFingerprint() { function sourceFingerprint() {
const sources = [ const sources = [
runtimePath, runtimePath,
path.join(moduleDir, "controller-engine.mjs"),
path.join(moduleDir, "model-client.mjs"),
path.join(moduleDir, "server.mjs"),
path.join( path.join(
repositoryRoot, repositoryRoot,
"tcs-core/zhuyuan-brain/runtime/brain-runtime-contract.json", "tcs-core/zhuyuan-brain/runtime/brain-runtime-contract.json",
@ -111,7 +114,14 @@ export class ControllerEngine {
initialize() { initialize() {
fs.mkdirSync(this.exchangeDir, { recursive: true }); fs.mkdirSync(this.exchangeDir, { recursive: true });
const stateFile = path.join(this.stateDir, "state.json"); let stateFile = path.join(this.stateDir, "state.json");
if (fs.existsSync(stateFile)) {
const existing = this.status();
if (existing.status === "PERCEIVING" && existing.active_cycle_id) {
this.stateDir = `${this.stateDir}-technical-recovery-${Date.now()}`;
stateFile = path.join(this.stateDir, "state.json");
}
}
if (!fs.existsSync(stateFile)) { if (!fs.existsSync(stateFile)) {
runRuntime("enter", [ runRuntime("enter", [
"--state-dir", "--state-dir",

View file

@ -3,7 +3,11 @@ import fs from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import test from "node:test"; import test from "node:test";
import { ControllerEngine, bootEvent } from "./controller-engine.mjs"; import {
ControllerEngine,
bootEvent,
runRuntime,
} from "./controller-engine.mjs";
class FixtureModelClient { class FixtureModelClient {
async generate({ role, input }) { async generate({ role, input }) {
@ -167,3 +171,31 @@ test("resident engine completes a model-backed brain and controller cycle", asyn
assert.equal(receipt.existence.living_ai_system_controller_running, 100); assert.equal(receipt.existence.living_ai_system_controller_running, 100);
assert.equal(receipt.completed_cycles, 1); assert.equal(receipt.completed_cycles, 1);
}); });
test("an interrupted technical cycle is preserved and restarted in a new state epoch", () => {
const stateRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bs-tcs-recovery-"));
const first = new ControllerEngine({
stateRoot,
modelClient: new FixtureModelClient(),
modelName: "fixture-model",
});
first.initialize();
const eventFile = path.join(stateRoot, "interrupted-event.json");
fs.writeFileSync(eventFile, `${JSON.stringify(bootEvent())}\n`);
runRuntime("perceive", [
"--state-dir",
first.stateDir,
"--event",
eventFile,
]);
const second = new ControllerEngine({
stateRoot,
modelClient: new FixtureModelClient(),
modelName: "fixture-model",
});
const status = second.initialize();
assert.notEqual(second.stateDir, first.stateDir);
assert.equal(status.status, "ENTERED");
assert.equal(fs.existsSync(path.join(first.stateDir, "state.json")), true);
});

View file

@ -131,8 +131,11 @@ const server = http.createServer(async (request, response) => {
}); });
try { try {
engine.initialize(); const initialState = engine.initialize();
runtime.source_loaded = true; runtime.source_loaded = true;
if (initialState.status === "AWAITING_HUMAN_BINGSHUO") {
runtime.phase = "PAUSED_FOR_HUMAN";
}
} catch (error) { } catch (error) {
process.stderr.write( process.stderr.write(
`${JSON.stringify({ `${JSON.stringify({
@ -151,6 +154,7 @@ server.listen(PORT, HOST, () => {
port: PORT, port: PORT,
})}\n`, })}\n`,
); );
if (runtime.phase === "PAUSED_FOR_HUMAN") return;
eventQueue = handleEvent(bootEvent()).catch((error) => { eventQueue = handleEvent(bootEvent()).catch((error) => {
runtime.phase = "FAILED_CLOSED"; runtime.phase = "FAILED_CLOSED";
runtime.last_error = String(error.message || error).slice(0, 240); runtime.last_error = String(error.message || error).slice(0, 240);