#!/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);