learning_ai_common_plat/packages/palace/src/wakeup.ts
saravanakumardb1 d1c6cf47c8 feat(palace): add @bytelyst/palace shared package — MemPalace primitives (91 tests)
New shared package: packages/palace/ (@bytelyst/palace)

Modules:
- types.ts — BasePalaceWingDoc, RoomDoc, MemoryDoc, TunnelDoc, KGTripleDoc, DiaryDoc
- halls.ts — HallType union, HALL_PRESETS (notelett/mindlyst/coding), hallFromLabel()
- cosine.ts — cosineSimilarity(), topKByCosine(), normalizeVector()
- dedup.ts — isContentDuplicate(), isExactDuplicate(), findClosestMatch()
- decay.ts — computeDecayedRelevance(), boostRelevance()
- extraction.ts — buildExtractionPrompt(), parseExtractionResponse(), regexFallbackExtraction()
- kg.ts — findContradictions(), mergeTriples(), isTripleCurrent()
- wakeup.ts — buildWakeUpLayers(), truncateToTokenBudget(), WAKEUP_PRESETS
- config.ts — palaceConfigSchema (Zod)

7 test files, 91 tests passing.
Consumed by NoteLett, MindLyst, and future palace-enabled products.
2026-04-10 00:57:00 -07:00

127 lines
3.7 KiB
TypeScript

/**
* Wake-up context builder for palace-augmented sessions.
*
* Builds a layered context string (L0/L1/L2) within a token budget.
* Products provide the raw data; this module assembles and truncates.
*/
export interface WakeUpLayer {
label: string;
content: string;
priority: number;
}
export interface WakeUpConfig {
totalBudget: number;
l0Budget: number;
l1Budget: number;
l2Budget: number;
}
export interface WakeUpContext {
text: string;
layers: { label: string; charCount: number }[];
totalChars: number;
truncated: boolean;
}
/**
* Approximate token count for a string.
* Uses the rough heuristic of ~4 characters per token.
*/
export function estimateTokens(text: string): number {
return Math.ceil(text.length / 4);
}
/**
* Truncate text to fit within a token budget.
*
* @param text - Input text
* @param maxTokens - Maximum tokens allowed
* @returns Truncated text (with "..." appended if truncated)
*/
export function truncateToTokenBudget(text: string, maxTokens: number): string {
if (!text) return '';
const maxChars = maxTokens * 4;
if (text.length <= maxChars) return text;
// Truncate at word boundary
const truncated = text.slice(0, maxChars);
const lastSpace = truncated.lastIndexOf(' ');
const cutPoint = lastSpace > maxChars * 0.8 ? lastSpace : maxChars;
return truncated.slice(0, cutPoint) + '...';
}
/**
* Build a wake-up context from L0/L1/L2 layers within a total token budget.
*
* Layer priority:
* - L0 (identity/project context) — always included, smallest budget
* - L1 (critical facts from recent memories) — high priority
* - L2 (semantically relevant memories) — fills remaining budget
*
* @param l0 - Identity/project context string
* @param l1 - Critical facts string
* @param l2 - Semantically relevant memories string
* @param config - Token budget configuration
* @returns Assembled wake-up context with metadata
*/
export function buildWakeUpLayers(
l0: string,
l1: string,
l2: string,
config: WakeUpConfig
): WakeUpContext {
const layers: { label: string; charCount: number }[] = [];
const parts: string[] = [];
let truncated = false;
// L0: identity (always included)
const l0Truncated = truncateToTokenBudget(l0, config.l0Budget);
if (l0Truncated) {
parts.push(`[Identity]\n${l0Truncated}`);
layers.push({ label: 'L0:identity', charCount: l0Truncated.length });
if (l0Truncated.endsWith('...')) truncated = true;
}
// L1: critical facts
const l1Truncated = truncateToTokenBudget(l1, config.l1Budget);
if (l1Truncated) {
parts.push(`[Critical Facts]\n${l1Truncated}`);
layers.push({ label: 'L1:facts', charCount: l1Truncated.length });
if (l1Truncated.endsWith('...')) truncated = true;
}
// L2: semantic context (gets remaining budget)
const usedTokens = estimateTokens(parts.join('\n\n'));
const remainingBudget = Math.max(0, config.totalBudget - usedTokens);
const l2Budget = Math.min(config.l2Budget, remainingBudget);
const l2Truncated = truncateToTokenBudget(l2, l2Budget);
if (l2Truncated) {
parts.push(`[Relevant Memories]\n${l2Truncated}`);
layers.push({ label: 'L2:semantic', charCount: l2Truncated.length });
if (l2Truncated.endsWith('...')) truncated = true;
}
const text = parts.join('\n\n');
return {
text,
layers,
totalChars: text.length,
truncated,
};
}
/**
* Default wake-up configs for each product.
*/
export const WAKEUP_PRESETS: Record<string, WakeUpConfig> = {
notelett: { totalBudget: 600, l0Budget: 50, l1Budget: 150, l2Budget: 400 },
mindlyst: { totalBudget: 800, l0Budget: 80, l1Budget: 200, l2Budget: 500 },
coding: { totalBudget: 800, l0Budget: 80, l1Budget: 200, l2Budget: 500 },
};