VoidBot/packages/core/src/void-self-state-loader.ts
2026-05-04 20:45:26 +01:00

199 lines
7.2 KiB
TypeScript

import { readFile } from "node:fs/promises";
import { type VoidSelfStateContext } from "@voidbot/shared";
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
export async function loadVoidSelfState(
statePath: string,
): Promise<VoidSelfStateContext | undefined> {
let raw: string;
try {
raw = await readFile(statePath, "utf8");
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code === "ENOENT") {
return undefined;
}
throw error;
}
const parsed = JSON.parse(raw) as JsonObject;
return {
sourcePath: statePath,
loadedAt: new Date().toISOString(),
summary: renderVoidSelfStateSummary(parsed),
};
}
type JsonObject = { [key: string]: JsonValue };
function renderVoidSelfStateSummary(state: JsonObject): string {
const identity = getObject(state, "identity");
const goals = getArray(state, "goals");
const memories = getObject(state, "memories");
const runtime = getObject(state, "moderation_runtime");
const canonicalState = getObject(state, "canonical_state");
const privateNotes = getStringArray(identity, "private_notes").slice(0, 4);
const activeGoals = goals
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.filter((goal) => readString(goal, "status") === "active")
.slice(0, 4)
.map((goal) => readString(goal, "label"))
.filter((value): value is string => Boolean(value));
const topValues = getArray(canonicalState, "values")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.sort(
(left, right) =>
(readNumber(right, "priority") ?? 0) - (readNumber(left, "priority") ?? 0),
)
.slice(0, 4)
.map((value) => readString(value, "label"))
.filter((value): value is string => Boolean(value));
const voiceSummary = summarizeCurrentActivations(getObject(canonicalState, "voice_style"), 5);
const behavioralSummary = summarizeCurrentActivations(
getObject(canonicalState, "behavioral_dimensions"),
5,
);
const semanticMemories = getArray(memories, "semantic")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.slice(-6)
.map((memory) => {
const subject = readString(memory, "subjectLabel") ?? readString(memory, "subjectId") ?? "unknown";
const summary = readString(memory, "summary") ?? "(no summary)";
return `- ${subject}: ${summary}`;
});
const musings = getArray(memories, "musings")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.slice(-4)
.map((musing) => {
const topic = readString(musing, "topic") ?? "untitled";
const summary = readString(musing, "summary") ?? "(no summary)";
return `- ${topic}: ${summary}`;
});
const recentMusings = getArray(runtime, "recent_musings")
.map((value) => (typeof value === "string" ? value : undefined))
.filter((value): value is string => Boolean(value))
.slice(-4)
.map((value) => `- ${value}`);
const candidateInterventions = getArray(runtime, "candidate_interventions")
.map((value) => (isObject(value) ? value : undefined))
.filter((value): value is JsonObject => Boolean(value))
.slice(-3)
.map((intervention) => {
const summary = readString(intervention, "summary") ?? "draft intervention";
const draft = readString(intervention, "draft") ?? "(no draft)";
return `- ${summary}: ${draft}`;
});
const lastRun = getObject(runtime, "last_run");
const lastRunSummary = readString(lastRun, "summary");
return [
`- Identity: ${readString(identity, "name") ?? "Void"}${readString(identity, "public_description") ? ` — ${readString(identity, "public_description")}` : ""}`,
privateNotes.length > 0
? `- Private notes: ${privateNotes.join(" | ")}`
: "- Private notes: none recorded.",
activeGoals.length > 0
? `- Active goals: ${activeGoals.join(" | ")}`
: "- Active goals: none recorded.",
topValues.length > 0
? `- Highest-priority values: ${topValues.join(" | ")}`
: "- Highest-priority values: none recorded.",
voiceSummary
? `- Current voice activations: ${voiceSummary}`
: "- Current voice activations: none highlighted.",
behavioralSummary
? `- Current behavioral activations: ${behavioralSummary}`
: "- Current behavioral activations: none highlighted.",
lastRunSummary ? `- Last moderation run: ${lastRunSummary}` : "- Last moderation run: none recorded.",
semanticMemories.length > 0
? ["- Recent semantic memories:", ...semanticMemories].join("\n")
: "- Recent semantic memories: none recorded.",
musings.length > 0
? ["- Stored musings:", ...musings].join("\n")
: "- Stored musings: none recorded.",
recentMusings.length > 0
? ["- Recent moderation musings:", ...recentMusings].join("\n")
: "- Recent moderation musings: none recorded.",
candidateInterventions.length > 0
? ["- Draft conversation/intervention seeds:", ...candidateInterventions].join("\n")
: "- Draft conversation/intervention seeds: none recorded.",
].join("\n");
}
function summarizeCurrentActivations(
category: JsonObject | undefined,
limit: number,
): string | undefined {
if (!category) {
return undefined;
}
const entries = Object.entries(category)
.map(([key, value]) => {
if (!isObject(value)) {
return undefined;
}
const activation = readNumber(value, "current_activation");
if (activation === undefined) {
return undefined;
}
return {
key,
activation,
};
})
.filter((value): value is { key: string; activation: number } => Boolean(value))
.sort((left, right) => right.activation - left.activation)
.slice(0, limit);
if (entries.length === 0) {
return undefined;
}
return entries
.map(({ key, activation }) => `${key}=${activation.toFixed(2)}`)
.join(", ");
}
function getObject(value: JsonObject | undefined, key: string): JsonObject | undefined {
const candidate = value?.[key];
return isObject(candidate) ? candidate : undefined;
}
function getArray(value: JsonObject | undefined, key: string): JsonValue[] {
const candidate = value?.[key];
return Array.isArray(candidate) ? candidate : [];
}
function getStringArray(value: JsonObject | undefined, key: string): string[] {
return getArray(value, key)
.map((entry) => (typeof entry === "string" ? entry : undefined))
.filter((entry): entry is string => Boolean(entry));
}
function readString(value: JsonObject | undefined, key: string): string | undefined {
const candidate = value?.[key];
return typeof candidate === "string" ? candidate : undefined;
}
function readNumber(value: JsonObject | undefined, key: string): number | undefined {
const candidate = value?.[key];
return typeof candidate === "number" && Number.isFinite(candidate) ? candidate : undefined;
}
function isObject(value: JsonValue | undefined): value is JsonObject {
return typeof value === "object" && value !== null && !Array.isArray(value);
}