fix: recover interrupted controller cycles
This commit is contained in:
parent
380fca2b9f
commit
05aeab4817
3 changed files with 49 additions and 3 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue