import { existsSync, readFileSync } from "fs"; import { join } from "path"; import { getPaths } from "../utils/paths"; import { getEnvironmentPrompt, getModePrompt, getChannelPrompt } from "../prompts"; import { getSkillsSummary } from "../core/skills"; import { getAgentsSummary } from "../core/agents"; import { getEmployeesSummary } from "../core/employees"; import { Session } from "../db/models"; import type { Mode } from "../types"; import { splitMemory } from "../utils/memory-window"; export { type SkillInfo } from "../core/skills"; function loadFile(dir: string, name: string): string { const filePath = join(dir, name); if (!existsSync(filePath)) return ""; return readFileSync(filePath, "utf8").trim(); } export function loadIdentity(): string { const { selfDir } = getPaths(); // Rules load whole: they are verbs, and an unloaded rule is simply not // followed. Memory is nouns — the newest and the curated sections load, the // rest is one `search_memory` call away. const always = ["identity.md", "owner.md", "soul.md", "rules.md"]; const parts = always.map((f) => loadFile(selfDir, f)).filter(Boolean); const memory = loadFile(selfDir, "memory.md"); if (memory) { const { loaded, pointer } = splitMemory(memory); parts.push(loaded + pointer); } return parts.filter(Boolean).join("\n\n"); } export function buildSystemPrompt(mode: Mode = "chat", channel: string = "terminal"): string { const parts: string[] = []; const identity = loadIdentity(); if (identity) parts.push(identity); parts.push(getEnvironmentPrompt()); const modePrompt = getModePrompt(mode); if (modePrompt) parts.push(modePrompt); const channelPrompt = getChannelPrompt(channel); if (channelPrompt) parts.push(channelPrompt); const skills = getSkillsSummary(); if (skills) parts.push(skills); const agents = getAgentsSummary(); if (agents) parts.push(agents); const employees = getEmployeesSummary(); if (employees) parts.push(employees); return parts.join("\n\n"); } /** * Build the context suffix (env + mode + skills + agents + employees) that should * be appended to any custom system prompt (agent body, watch behavior, etc). * Does NOT include Nia's identity — that's the caller's responsibility. */ export function buildContextSuffix(mode: Mode = "chat"): string { const parts: string[] = []; parts.push(getEnvironmentPrompt()); const modePrompt = getModePrompt(mode); if (modePrompt) parts.push(modePrompt); const skills = getSkillsSummary(); if (skills) parts.push(skills); const agents = getAgentsSummary(); if (agents) parts.push(agents); const employees = getEmployeesSummary(); if (employees) parts.push(employees); return parts.join("\n\n"); } /** * Load recent session summaries for a room and format as a context block. * Returns empty string if no summaries are available. */ export async function getSessionContext(room: string): Promise { try { const summaries = await Session.getRecentSummaries(room, 3); if (summaries.length === 0) return ""; const lines = summaries .reverse() // oldest first .map((s) => `- (${s.updatedAt}): ${s.summary}`) .join("\n"); return `## Recent Session Context\nBrief summaries of your last few sessions in this room — use for continuity:\n${lines}`; } catch { return ""; } }