diff --git a/server-tools/bingshuo-tcs-controller/controller-engine.mjs b/server-tools/bingshuo-tcs-controller/controller-engine.mjs index c14c7c3..8f61709 100644 --- a/server-tools/bingshuo-tcs-controller/controller-engine.mjs +++ b/server-tools/bingshuo-tcs-controller/controller-engine.mjs @@ -28,6 +28,9 @@ function digestFile(file) { function sourceFingerprint() { const sources = [ runtimePath, + path.join(moduleDir, "controller-engine.mjs"), + path.join(moduleDir, "model-client.mjs"), + path.join(moduleDir, "server.mjs"), path.join( repositoryRoot, "tcs-core/zhuyuan-brain/runtime/brain-runtime-contract.json", @@ -111,7 +114,14 @@ export class ControllerEngine { initialize() { 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)) { runRuntime("enter", [ "--state-dir", diff --git a/server-tools/bingshuo-tcs-controller/controller-engine.test.mjs b/server-tools/bingshuo-tcs-controller/controller-engine.test.mjs index 00cf0e5..ce9722f 100644 --- a/server-tools/bingshuo-tcs-controller/controller-engine.test.mjs +++ b/server-tools/bingshuo-tcs-controller/controller-engine.test.mjs @@ -3,7 +3,11 @@ 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"; +import { + ControllerEngine, + bootEvent, + runRuntime, +} from "./controller-engine.mjs"; class FixtureModelClient { 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.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); +}); diff --git a/server-tools/bingshuo-tcs-controller/server.mjs b/server-tools/bingshuo-tcs-controller/server.mjs index 4358ac8..a99f03c 100644 --- a/server-tools/bingshuo-tcs-controller/server.mjs +++ b/server-tools/bingshuo-tcs-controller/server.mjs @@ -131,8 +131,11 @@ const server = http.createServer(async (request, response) => { }); try { - engine.initialize(); + const initialState = engine.initialize(); runtime.source_loaded = true; + if (initialState.status === "AWAITING_HUMAN_BINGSHUO") { + runtime.phase = "PAUSED_FOR_HUMAN"; + } } catch (error) { process.stderr.write( `${JSON.stringify({ @@ -151,6 +154,7 @@ server.listen(PORT, HOST, () => { port: PORT, })}\n`, ); + if (runtime.phase === "PAUSED_FOR_HUMAN") return; eventQueue = handleEvent(bootEvent()).catch((error) => { runtime.phase = "FAILED_CLOSED"; runtime.last_error = String(error.message || error).slice(0, 240);