70 lines
2.9 KiB
JavaScript
70 lines
2.9 KiB
JavaScript
|
|
import { readFileSync } from "node:fs";
|
||
|
|
import { dirname, resolve } from "node:path";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
|
||
|
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
||
|
|
const repoRoot = resolve(moduleDir, "../..");
|
||
|
|
const registryPath = resolve(moduleDir, "kernel-registry.json");
|
||
|
|
|
||
|
|
function readJson(path) {
|
||
|
|
return JSON.parse(readFileSync(path, "utf8"));
|
||
|
|
}
|
||
|
|
|
||
|
|
function resolveRepoPath(relativePath) {
|
||
|
|
const absolute = resolve(repoRoot, relativePath);
|
||
|
|
if (!absolute.startsWith(`${repoRoot}/`)) throw new Error("kernel path escapes repository root");
|
||
|
|
return absolute;
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadBounded(relativePath, maxBytes) {
|
||
|
|
const absolute = resolveRepoPath(relativePath);
|
||
|
|
const body = readFileSync(absolute);
|
||
|
|
if (body.byteLength > maxBytes) throw new Error(`kernel exceeds byte budget: ${relativePath}`);
|
||
|
|
return { relative_path: relativePath, bytes: body.byteLength, value: JSON.parse(body.toString("utf8")) };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function loadKernelRegistry() {
|
||
|
|
return readJson(registryPath);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function canonicalPersonaId(personaId, registry = loadKernelRegistry()) {
|
||
|
|
if (registry.personas[personaId]) return personaId;
|
||
|
|
for (const [canonical, entry] of Object.entries(registry.personas)) {
|
||
|
|
if (entry.aliases?.includes(personaId)) return canonical;
|
||
|
|
}
|
||
|
|
throw new Error(`unregistered persona: ${personaId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resolvePersonaChannelRuntime({ personaId, channelId, environmentId } = {}) {
|
||
|
|
if (typeof personaId !== "string" || personaId.length === 0) throw new TypeError("personaId is required");
|
||
|
|
const registry = loadKernelRegistry();
|
||
|
|
const canonical = canonicalPersonaId(personaId, registry);
|
||
|
|
const persona = registry.personas[canonical];
|
||
|
|
const maxBytes = registry.rules.resident_kernel_max_bytes;
|
||
|
|
const systemCore = loadBounded(registry.system_core.path, maxBytes);
|
||
|
|
const selfKernel = loadBounded(persona.self_kernel, maxBytes);
|
||
|
|
const selectedEnvironment = environmentId ?? persona.default_environment ?? null;
|
||
|
|
const environment = selectedEnvironment
|
||
|
|
? loadBounded(registry.environments[selectedEnvironment].path, maxBytes)
|
||
|
|
: null;
|
||
|
|
const memoryRoot = channelId
|
||
|
|
? persona.channel_memory_roots?.[channelId]
|
||
|
|
: persona.default_memory_root;
|
||
|
|
if (!memoryRoot) {
|
||
|
|
throw new Error(`no memory root registered for persona ${canonical} in channel ${channelId ?? "<default>"}`);
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
schema: "guanghu.persona-channel-runtime-projection/v1",
|
||
|
|
persona_id: canonical,
|
||
|
|
channel_id: channelId ?? null,
|
||
|
|
load_order: [
|
||
|
|
{ kind: "RULE_ORDER_SYSTEM_CORE", ...systemCore },
|
||
|
|
...(environment ? [{ kind: "CHANNEL_ENVIRONMENT", ...environment }] : []),
|
||
|
|
{ kind: "PERSONA_SELF_KERNEL", ...selfKernel },
|
||
|
|
{ kind: "HLDP_MEMORY_ROOT", ref: memoryRoot, retrieval_max_paths: registry.rules.memory_retrieval_max_paths },
|
||
|
|
],
|
||
|
|
self_kernel_path: selfKernel.relative_path,
|
||
|
|
memory_root: memoryRoot,
|
||
|
|
environment_path: environment?.relative_path ?? null,
|
||
|
|
};
|
||
|
|
}
|