134 lines
7.3 KiB
JavaScript
134 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
import { chmodSync, existsSync, lstatSync, mkdirSync, rmSync } from "node:fs";
|
|
import { createConnection, createServer } from "node:net";
|
|
import { homedir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { MemoryStore } from "./store.js";
|
|
import { ContinuityStore } from "./continuity.js";
|
|
import { Timeline } from "./timeline.js";
|
|
import { ChainStore } from "./chain-store.js";
|
|
import { CoreProjection } from "./core-projection.js";
|
|
|
|
const stateDir = resolve(process.env.DESKPAW_STATE_DIR ?? join(homedir(), ".deskpaw"));
|
|
const runtimeDir = join(stateDir, "runtime");
|
|
const memoryDir = join(stateDir, "memory");
|
|
const socketPath = process.env.DESKPAW_MEMORY_SOCKET ?? (process.platform === "win32" ? "\\\\.\\pipe\\deskpaw-memory" : join(runtimeDir, "memory.sock"));
|
|
const databasePath = resolve(process.env.DESKPAW_MEMORY_DB ?? join(memoryDir, "data", "memory.sqlite3"));
|
|
const bundlesDir = resolve(process.env.DESKPAW_MEMORY_BUNDLES ?? join(memoryDir, "bundles"));
|
|
process.umask(0o077);
|
|
mkdirSync(dirname(databasePath), { recursive: true, mode: 0o700 });
|
|
if (process.platform !== "win32") {
|
|
mkdirSync(dirname(socketPath), { recursive: true, mode: 0o700 });
|
|
if (existsSync(socketPath)) {
|
|
if (!lstatSync(socketPath).isSocket()) throw new Error("Refusing to replace non-socket memory path");
|
|
const active = await new Promise((resolve, reject) => {
|
|
const socket = createConnection(socketPath);
|
|
socket.once("connect", () => { socket.destroy(); resolve(true); });
|
|
socket.once("error", error => {
|
|
if (["ENOENT", "ECONNREFUSED"].includes(error.code)) resolve(false); else reject(error);
|
|
});
|
|
socket.setTimeout(1000, () => { socket.destroy(); reject(new Error("Cannot verify memory socket owner")); });
|
|
});
|
|
if (active) throw new Error("Memory socket already has an owner");
|
|
if (existsSync(socketPath)) rmSync(socketPath);
|
|
}
|
|
}
|
|
const store = new MemoryStore(databasePath, bundlesDir);
|
|
const continuity = new ContinuityStore(store.db);
|
|
const timeline = new Timeline(store.db);
|
|
const chain = new ChainStore(store.db, timeline);
|
|
const coreProjection = new CoreProjection(chain, join(stateDir, "super"));
|
|
coreProjection.refresh();
|
|
|
|
function call(method, params) {
|
|
if (method === "memory.chain.context") { coreProjection.refresh(); return chain.context(params); }
|
|
if (method === "memory.chain.tick") return chain.tick(params);
|
|
if (method === "memory.chain.status") return chain.status(params);
|
|
if (method === "memory.chain.core.bootstrap") return chain.bootstrapCore(params);
|
|
if (method === "memory.chain.core.update") { const result = chain.editCore(params); coreProjection.refresh(); return result; }
|
|
if (method === "memory.chain.find") return chain.graph.find(params);
|
|
if (method === "memory.chain.browse") return chain.graph.browse(params);
|
|
if (method === "memory.chain.read") return chain.graph.read(params);
|
|
if (method === "memory.chain.node") return chain.node(params);
|
|
if (method === "memory.chain.project_decision") return chain.projectDecision(params);
|
|
if (method === "memory.organize.request") return chain.request({ ...params, trigger: "explicit" });
|
|
if (method === "memory.organize.claim") return chain.claim(params);
|
|
if (method === "memory.organize.inspect") return chain.inspect(params);
|
|
if (method === "memory.organize.commit") { const receipt = chain.commit(params); coreProjection.refresh(); return receipt; }
|
|
if (method === "memory.organize.fail") return chain.fail(params);
|
|
if (method === "memory.events.ingest") return continuity.ingest(params);
|
|
if (method === "memory.observation.claim") return continuity.claim(params);
|
|
if (method === "memory.observation.commit") return continuity.commit(params);
|
|
if (method === "memory.observation.fail") return continuity.fail(params);
|
|
if (method === "memory.continuity.recall") return continuity.recall(params);
|
|
if (method === "memory.evidence.get") return continuity.evidence(params);
|
|
if (method === "timeline.append") return timeline.append({ ...params, at: params.at ?? Date.now() });
|
|
if (method === "timeline.query") return timeline.query(params);
|
|
if (method === "timeline.read" || method === "timeline.expand") return timeline.read(params);
|
|
if (method === "health") return { status: "ok", version: "0.1.0", ...store.status() };
|
|
if (method === "memory.recall") return store.recall(params);
|
|
if (method === "memory.maintenance.status") return store.maintenanceStatus(Number(params.now ?? Date.now()));
|
|
if (method === "memory.maintenance.plan") return store.maintenancePlan(params);
|
|
if (method === "memory.maintenance.commit") return store.commitMaintenance(params);
|
|
if (method === "memory.topic.get") return store.topicDetail(params);
|
|
if (method === "memory.skill.list") return store.listSkills(params);
|
|
if (method === "memory.skill.get") return store.skillDetail(params);
|
|
if (method === "memory.skill.set_status") return store.setSkillStatus(params);
|
|
if (method === "interaction.remember") return store.rememberInteractions(params);
|
|
if (method === "transition.assess") return store.assessTransition(params);
|
|
if (method === "context.prepare") return { ...store.prepareContext(params),
|
|
...(params.owner ? { continuity: continuity.recall({ owner: params.owner, query: params.objective,
|
|
subject_id: params.schedule_id ? `schedule:${params.schedule_id}` : undefined, limit: 4 }) } : {}) };
|
|
if (method === "task.complete") return store.completeTask(params);
|
|
if (method === "consolidation.record") return store.recordConsolidation(params);
|
|
if (method === "bundle.get") return store.bundle(String(params.scope ?? ""), String(params.object_id ?? ""));
|
|
if (method === "bundle.list") return store.listBundles(params.scope === undefined ? undefined : String(params.scope));
|
|
if (method === "bundle.document.put") return store.updateBundleDocument(params);
|
|
if (method === "task.list") return store.listTasks(Number(params.limit ?? 20));
|
|
throw new Error(`Unknown method: ${method}`);
|
|
}
|
|
|
|
const server = createServer((socket) => {
|
|
socket.setEncoding("utf8");
|
|
socket.setTimeout(5000, () => socket.destroy());
|
|
let input = "";
|
|
let handled = false;
|
|
socket.on("error", () => {});
|
|
socket.on("data", (chunk) => {
|
|
if (handled) return;
|
|
input += chunk;
|
|
if (Buffer.byteLength(input) > 1_000_000) { handled = true; socket.destroy(); return; }
|
|
const newline = input.indexOf("\n");
|
|
if (newline < 0) return;
|
|
handled = true;
|
|
const line = input.slice(0, newline);
|
|
let id = null;
|
|
try {
|
|
const request = JSON.parse(line);
|
|
id = request.id ?? null;
|
|
const result = call(String(request.method ?? ""), request.params ?? {});
|
|
socket.end(JSON.stringify({ id: request.id ?? null, ok: true, result }) + "\n");
|
|
} catch (error) {
|
|
socket.end(JSON.stringify({ id, ok: false, error: error instanceof Error ? error.message : String(error) }) + "\n");
|
|
}
|
|
});
|
|
});
|
|
server.maxConnections = 64;
|
|
|
|
server.listen(socketPath, () => {
|
|
if (process.platform !== "win32") chmodSync(socketPath, 0o600);
|
|
console.log(`DeskPaw memory ready at ${socketPath}`);
|
|
});
|
|
let stopping = false;
|
|
function shutdown() {
|
|
if (stopping) return;
|
|
stopping = true;
|
|
server.close(() => {
|
|
store.close();
|
|
if (process.platform !== "win32" && existsSync(socketPath)) rmSync(socketPath);
|
|
process.exit(0);
|
|
});
|
|
}
|
|
process.once("SIGTERM", shutdown);
|
|
process.once("SIGINT", shutdown);
|