diff --git a/dist/index.js b/dist/index.js index 26756b7..7083a63 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,6 +1,6 @@ // src/index.ts import { Type } from "@sinclair/typebox"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { homedir } from "node:os"; import { readFileSync } from "node:fs"; @@ -55,6 +55,14 @@ var MemoryStore = class { this.db.exec(`ALTER TABLE semantic ADD COLUMN last_accessed TEXT`); } catch { } + try { + this.db.exec(`ALTER TABLE lessons ADD COLUMN project TEXT`); + } catch { + } + try { + this.db.exec(`ALTER TABLE semantic ADD COLUMN embedding BLOB`); + } catch { + } try { this.db.exec(` CREATE VIRTUAL TABLE IF NOT EXISTS semantic_fts USING fts5(key, value, content='semantic', content_rowid='rowid'); @@ -136,6 +144,23 @@ var MemoryStore = class { return result.changes > 0; }); } + /** + * Store a pre-computed embedding for a key. + * Converts Float32Array → Buffer for SQLite BLOB storage. + */ + setEmbedding(key, embedding) { + const normalized = key.toLowerCase(); + const blob = Buffer.from(new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength)); + this.db.prepare("UPDATE semantic SET embedding = ? WHERE key = ?").run(blob, normalized); + } + /** + * Return all semantic keys with their raw embedding BLOBs. + * Used for in-memory cosine similarity at query time. + * Entries without an embedding have embedding = null. + */ + getAllEmbeddings() { + return this.db.prepare("SELECT key, embedding FROM semantic ORDER BY updated_at DESC").all(); + } listSemantic(prefix, limit = 100) { if (prefix) { return this.db.prepare("SELECT * FROM semantic WHERE key LIKE ? ORDER BY updated_at DESC LIMIT ?").all(`${prefix}%`, limit); @@ -179,7 +204,7 @@ var MemoryStore = class { } } // ─── Lessons ───────────────────────────────────────────────────── - addLesson(rule, category = "general", source = "consolidation", negative = false) { + addLesson(rule, category = "general", source = "consolidation", negative = false, project) { const trimmed = rule.trim(); if (!trimmed) return { success: false, reason: "empty rule" }; const normalizedCategory = category.trim().toLowerCase() || "general"; @@ -196,8 +221,8 @@ var MemoryStore = class { } const id = crypto.randomUUID(); this.db.prepare( - "INSERT INTO lessons (id, rule, category, source, negative) VALUES (?, ?, ?, ?, ?)" - ).run(id, trimmed, normalizedCategory, source, negative ? 1 : 0); + "INSERT INTO lessons (id, rule, category, source, negative, project) VALUES (?, ?, ?, ?, ?, ?)" + ).run(id, trimmed, normalizedCategory, source, negative ? 1 : 0, project ?? null); this.logEvent("create", "lesson", id, trimmed.slice(0, 100)); return { success: true, id }; }); @@ -207,15 +232,32 @@ var MemoryStore = class { if (!row) return void 0; return { ...row, negative: !!row.negative }; } - listLessons(category, limit = 50) { + /** + * List lessons, optionally filtered by category and/or project. + * + * Project filtering: + * - If `project` is provided, returns lessons where `project = slug` OR `project IS NULL` + * (NULL = user-authored or pre-migration lessons, treated as global). + * - If `project` is not provided, returns all lessons (no project filter). + */ + listLessons(category, limit = 50, project) { let rows; - if (category) { + if (category && project) { + const normalizedCategory = category.trim().toLowerCase(); + rows = this.db.prepare( + "SELECT * FROM lessons WHERE category = ? AND (project = ? OR project IS NULL) AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?" + ).all(normalizedCategory, project, limit); + } else if (category) { const normalizedCategory = category.trim().toLowerCase(); rows = this.db.prepare("SELECT * FROM lessons WHERE category = ? AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?").all(normalizedCategory, limit); + } else if (project) { + rows = this.db.prepare( + "SELECT * FROM lessons WHERE (project = ? OR project IS NULL) AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?" + ).all(project, limit); } else { rows = this.db.prepare("SELECT * FROM lessons WHERE is_deleted = 0 ORDER BY created_at DESC LIMIT ?").all(limit); } - return rows.map((r) => ({ ...r, negative: !!r.negative })); + return rows.map((r) => ({ ...r, negative: !!r.negative, project: r.project ?? null })); } /** * Search lessons by relevance to a query. Uses FTS5 when available, @@ -228,14 +270,14 @@ var MemoryStore = class { const ftsQuery = terms.map((t) => `"${t.replace(/"/g, '""')}"`).join(" OR "); try { const rows = this.db.prepare(` - SELECT l.id, l.rule, l.category, l.source, l.negative, l.created_at + SELECT l.id, l.rule, l.category, l.source, l.negative, l.created_at, l.project FROM lessons l JOIN lessons_fts fts ON l.rowid = fts.rowid WHERE lessons_fts MATCH ? AND l.is_deleted = 0 ORDER BY bm25(lessons_fts) LIMIT ? `).all(ftsQuery, limit); - return rows.map((r) => ({ ...r, negative: !!r.negative })); + return rows.map((r) => ({ ...r, negative: !!r.negative, project: r.project ?? null })); } catch { return this._searchLessonsFallback(query, limit); } @@ -247,7 +289,7 @@ var MemoryStore = class { return all.map((entry) => { const text = `${entry.rule} ${entry.category}`.toLowerCase(); const matches = terms.filter((t) => text.includes(t)).length; - return { entry: { ...entry, negative: !!entry.negative }, score: matches / terms.length }; + return { entry: { ...entry, negative: !!entry.negative, project: entry.project ?? null }, score: matches / terms.length }; }).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map(({ entry }) => entry); } deleteLesson(id) { @@ -294,18 +336,85 @@ function jaccard(a, b) { return intersection.size / union.size; } +// src/embedder.ts +var MODEL = "Xenova/all-MiniLM-L6-v2"; +var LOAD_TIMEOUT_MS = 3e4; +var INFER_TIMEOUT_MS = 5e3; +var TEXT_CHAR_LIMIT = 512; +var _pipe = null; +var _failed = false; +async function getPipe() { + if (_failed) return null; + if (_pipe) return _pipe; + try { + const pkg = "@xenova/transformers"; + const mod = await import(pkg).catch(() => null); + if (!mod) { + console.error("pi-memory: @xenova/transformers not installed, semantic search disabled"); + _failed = true; + return null; + } + const { pipeline, env } = mod; + env.allowRemoteModels = true; + env.useBrowserCache = false; + _pipe = await withTimeout( + pipeline("feature-extraction", MODEL, { quantized: true }), + LOAD_TIMEOUT_MS, + "model load" + ); + return _pipe; + } catch (err) { + console.error(`pi-memory: embedder unavailable (${err?.message ?? err}), using FTS-only`); + _failed = true; + return null; + } +} +async function embed(text) { + const pipe = await getPipe(); + if (!pipe) return null; + try { + const out = await withTimeout( + pipe(text.slice(0, TEXT_CHAR_LIMIT), { pooling: "mean", normalize: true }), + INFER_TIMEOUT_MS, + "inference" + ); + return new Float32Array(out.data); + } catch { + return null; + } +} +function similarity(a, b) { + let dot = 0; + const len = Math.min(a.length, b.length); + for (let i = 0; i < len; i++) dot += a[i] * b[i]; + return dot; +} +function fromBlob(b) { + if (!b) return null; + const raw = Uint8Array.from(b); + return new Float32Array(raw.buffer); +} +function withTimeout(p, ms, label) { + return Promise.race([ + p, + new Promise( + (_, reject) => setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms) + ) + ]); +} + // src/injector.ts import os from "node:os"; var MAX_CONTEXT_CHARS = 8e3; var SEARCH_LIMIT = 15; var LESSON_SEARCH_LIMIT = 15; -function buildContextBlock(store, cwd, prompt, config) { +async function buildContextBlock(store, cwd, prompt, config) { if (prompt?.trim()) { return buildSelectiveBlock(store, prompt, cwd, config); } return buildFallbackBlock(store, cwd); } -function buildSelectiveBlock(store, prompt, cwd, config) { +async function buildSelectiveBlock(store, prompt, cwd, config) { const sections = []; let semanticCount = 0; let lessonCount = 0; @@ -314,20 +423,82 @@ function buildSelectiveBlock(store, prompt, cwd, config) { const slug = cwd ? projectSlug(cwd) : ""; if (slug) { const projectResults = store.searchSemantic(slug, 5); - const seen = new Set(results.map((r) => r.key)); + const seen2 = new Set(results.map((r) => r.key)); for (const r of projectResults) { - if (!seen.has(r.key)) { + if (!seen2.has(r.key)) { results.push(r); - seen.add(r.key); + seen2.add(r.key); } } } - if (results.length > 0) { - sections.push(formatSection("Relevant Memory", results.map(formatSemantic))); - semanticCount = results.length; - store.touchAccessed(results.map((r) => r.key)); + const filteredResults = slug ? results.filter((r) => { + if (!r.key.startsWith("project.")) return true; + const parts = r.key.split("."); + return parts.length >= 2 && parts[1] === slug; + }) : results; + const seen = new Set(filteredResults.map((r) => r.key)); + const SEMANTIC_THRESHOLD = 0.25; + const SEMANTIC_LIMIT = 8; + const allEmbs = store.getAllEmbeddings(); + const promptVec = await embed(prompt); + const semanticKeys = /* @__PURE__ */ new Set(); + if (promptVec) { + const semanticHits = allEmbs.flatMap(({ key, embedding }) => { + const vec = fromBlob(embedding); + if (!vec) return []; + const score = similarity(promptVec, vec); + return score >= SEMANTIC_THRESHOLD ? [{ key, score }] : []; + }).sort((a, b) => b.score - a.score).slice(0, SEMANTIC_LIMIT); + for (const { key } of semanticHits) { + semanticKeys.add(key); + if (!seen.has(key)) { + const entry = store.getSemantic(key); + if (entry) { + filteredResults.push(entry); + seen.add(key); + } + } + } + backfillEmbeddings(store, allEmbs.filter((r) => !r.embedding)).catch(() => { + }); } - const lessons = mode === "selective" ? getRelevantLessons(store, prompt, cwd) : store.listLessons(void 0, 50); + const expandedPrefixes = /* @__PURE__ */ new Set(); + for (const r of [...filteredResults]) { + const prefix = keyDomainPrefix(r.key); + if (!prefix || expandedPrefixes.has(prefix)) continue; + expandedPrefixes.add(prefix); + const limit = semanticKeys.has(r.key) ? 20 : 5; + for (const sibling of store.listSemantic(prefix, limit)) { + if (!seen.has(sibling.key)) { + filteredResults.push(sibling); + seen.add(sibling.key); + } + } + } + if (semanticKeys.size > 0) { + const semanticPrefixes = /* @__PURE__ */ new Set(); + for (const k of semanticKeys) { + const p = keyDomainPrefix(k); + if (p) semanticPrefixes.add(p); + } + const isSemanticRelated = (key) => { + if (semanticKeys.has(key)) return true; + const p = keyDomainPrefix(key); + return p ? semanticPrefixes.has(p) : false; + }; + const priority = filteredResults.filter((r) => isSemanticRelated(r.key)); + const rest = filteredResults.filter((r) => !isSemanticRelated(r.key)); + priority.sort((a, b) => a.key.localeCompare(b.key)); + rest.sort((a, b) => a.key.localeCompare(b.key)); + filteredResults.length = 0; + filteredResults.push(...priority, ...rest); + } + if (filteredResults.length > 0) { + sections.push(formatSection("Relevant Memory", filteredResults.map(formatSemantic))); + semanticCount = filteredResults.length; + store.touchAccessed(filteredResults.map((r) => r.key)); + } + const lessons = mode === "selective" ? getRelevantLessons(store, prompt, cwd) : store.listLessons(void 0, 50, slug || void 0); if (lessons.length > 0) { const corrections = lessons.filter((l) => l.negative); const positives = lessons.filter((l) => !l.negative); @@ -387,7 +558,11 @@ function buildFallbackBlock(store, cwd) { semanticCount += prefs.length; } const projects = store.listSemantic("project.", 50); - const relevant = cwd ? projects.filter((p) => p.key.includes(projectSlug(cwd)) || p.confidence >= 0.9) : projects; + const slug = cwd ? projectSlug(cwd) : ""; + const relevant = slug ? projects.filter((p) => { + const parts = p.key.split("."); + return parts.length >= 2 && parts[1] === slug; + }) : projects; if (relevant.length > 0) { sections.push(formatSection("Project Context", relevant.map(formatSemantic))); semanticCount += relevant.length; @@ -397,7 +572,7 @@ function buildFallbackBlock(store, cwd) { sections.push(formatSection("Tool Preferences", tools.map(formatSemantic))); semanticCount += tools.length; } - const lessons = store.listLessons(void 0, 50); + const lessons = store.listLessons(void 0, 50, slug || void 0); if (lessons.length > 0) { const corrections = lessons.filter((l) => l.negative); const positives = lessons.filter((l) => !l.negative); @@ -458,6 +633,20 @@ var MEMORY_DRIFT_CAVEAT = `## Before acting on memory - Memory records can become stale. If a memory names a file, function, or flag \u2014 verify it still exists before recommending it. "The memory says X exists" is not the same as "X exists now." - If a recalled memory conflicts with what you observe in the current code or project state, trust what you observe now. - Memories about project state (deadlines, decisions, architecture) decay fastest \u2014 check if still relevant.`; +function keyDomainPrefix(key) { + const parts = key.split("."); + return parts.length >= 3 ? parts.slice(0, 2).join(".") : null; +} +async function backfillEmbeddings(store, missing) { + if (missing.length === 0) return; + for (const { key } of missing.slice(0, 10)) { + const entry = store.getSemantic(key); + if (!entry) continue; + const displayKey = key.split(".").slice(1).join(" "); + const vec = await embed(`${displayKey} ${entry.value}`); + if (vec) store.setEmbedding(key, vec); + } +} function projectSlug(cwd) { const parts = cwd.split("/").filter(Boolean); const skip = /* @__PURE__ */ new Set(["workplace", "local", "home", "src", "scratch", os.userInfo().username]); @@ -594,7 +783,7 @@ function parseConsolidationResponse(text) { return { semantic: [], lessons: [] }; } } -function applyExtracted(store, extracted, source = "consolidation") { +function applyExtracted(store, extracted, source = "consolidation", project) { let semanticCount = 0; let lessonCount = 0; for (const s of extracted.semantic) { @@ -604,7 +793,8 @@ function applyExtracted(store, extracted, source = "consolidation") { } for (const l of extracted.lessons) { if (isDerivableLesson(l.rule)) continue; - const result = store.addLesson(l.rule, l.category, source, l.negative); + const lessonProject = source === "user" ? void 0 : project; + const result = store.addLesson(l.rule, l.category, source, l.negative, lessonProject); if (result.success) lessonCount++; } return { semantic: semanticCount, lessons: lessonCount }; @@ -680,12 +870,12 @@ function resolveDbPath(cwd) { const piMemory = settings?.["pi-memory"]; warnUnknownKeys(piMemory, "pi-memory", PI_MEMORY_KNOWN_KEYS); if (piMemory && typeof piMemory === "object" && typeof piMemory.localPath === "string" && piMemory.localPath) { - return join(piMemory.localPath, "memory.db"); + return resolve(cwd, piMemory.localPath, "memory.db"); } const piTotalRecall = settings?.["pi-total-recall"]; warnUnknownKeys(piTotalRecall, "pi-total-recall", PI_TOTAL_RECALL_KNOWN_KEYS); if (piTotalRecall && typeof piTotalRecall === "object" && typeof piTotalRecall.localPath === "string" && piTotalRecall.localPath) { - return join(piTotalRecall.localPath, "memory", "memory.db"); + return resolve(cwd, piTotalRecall.localPath, "memory", "memory.db"); } } catch { } @@ -767,13 +957,13 @@ function index_default(pi) { } }, 5e3); } - if (!injectorConfig.perTurnInjection) { + if (injectorConfig.perTurnInjection === false) { try { const alreadyInjected = ctx.sessionManager.getEntries().some( (e) => e.type === "custom_message" && e.customType === "pi-memory-context" ); if (!alreadyInjected) { - const { text, stats: injStats } = buildContextBlock( + const { text, stats: injStats } = await buildContextBlock( store, sessionCwd, void 0, @@ -798,8 +988,8 @@ function index_default(pi) { }); pi.on("before_agent_start", async (event, ctx) => { if (!store) return; - if (!injectorConfig.perTurnInjection) return; - const { text } = buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig); + if (injectorConfig.perTurnInjection === false) return; + const { text } = await buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig); if (!text) return; return { systemPrompt: `${event.systemPrompt} @@ -875,6 +1065,8 @@ ${text}` prompt, "--print", "--no-extensions", + "--no-tools", + "--no-session", "--model", injectorConfig.consolidationModel ?? DEFAULT_CONSOLIDATION_MODEL ], { @@ -892,7 +1084,8 @@ ${text}` ]); if (result.code === 0 && result.stdout) { const extracted = parseConsolidationResponse(result.stdout); - const applied = applyExtracted(store, extracted, `session:${sessionId ?? "unknown"}`); + const slug = sessionCwd ? projectSlug(sessionCwd) : void 0; + const applied = applyExtracted(store, extracted, `session:${sessionId ?? "unknown"}`, slug || void 0); if (applied.semantic + applied.lessons > 0) { console.error(`pi-memory: consolidated ${applied.semantic} facts, ${applied.lessons} lessons`); } @@ -952,6 +1145,12 @@ ${text}` return ok("Both key and value required for facts"); } store.setSemantic(params.key, params.value, 0.95, "user"); + const _key = params.key; + const _val = params.value; + embed(`${_key.split(".").slice(1).join(" ")} ${_val}`).then((vec) => { + if (vec) store.setEmbedding(_key, vec); + }).catch(() => { + }); return ok(`Remembered: ${params.key} = ${params.value}`); } if (params.type === "lesson") { @@ -1062,7 +1261,10 @@ function extractText(content) { } export { DEFAULT_CONSOLIDATION_MODEL, + MemoryStore, + buildContextBlock, index_default as default, + projectSlug, readSettingsConfig, resolveDbPath }; diff --git a/dist/index.js.map b/dist/index.js.map index 44ed84f..2b0fa84 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../src/index.ts", "../src/store.ts", "../src/injector.ts", "../src/consolidator.ts"], - "sourcesContent": ["/**\n * pi-memory \u2014 Persistent memory extension for pi.\n *\n * Learns corrections, preferences, and patterns from sessions.\n * Injects relevant memory into future conversations.\n *\n * Lifecycle:\n * - session_start: open store, inject memory as a one-shot custom message\n * - (memory context is no longer injected per-turn \u2014 see v1.2.0 changelog)\n * - agent_end: queue messages for consolidation\n * - session_shutdown: consolidate and close store\n *\n * Tools:\n * - memory_search: search semantic memory\n * - memory_remember: manually add a memory\n * - memory_forget: delete a memory\n * - memory_lessons: list learned corrections\n * - memory_stats: show memory statistics\n */\nimport type { ExtensionAPI, AgentToolResult, SessionEntry } from \"@earendil-works/pi-coding-agent\";\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { readFileSync } from \"node:fs\";\nimport { MemoryStore } from \"./store.js\";\nimport { buildContextBlock, type InjectorConfig } from \"./injector.js\";\n\ntype ToolResult = AgentToolResult;\nfunction ok(text: string): ToolResult { return { content: [{ type: \"text\", text }], details: {} }; }\n\n/**\n * Strip one layer of surrounding quotes from a string value.\n * Some local models (e.g. Qwen on certain runners) double-JSON-encode tool\n * arguments, emitting `\"\\\"fact\\\"\"` instead of `\"fact\"`. We defensively\n * unwrap so these calls don't fail schema validation / equality checks.\n */\nfunction stripQuotes(v: T): T {\n if (typeof v !== \"string\") return v;\n const s = v.trim();\n if (s.length >= 2) {\n const first = s[0];\n const last = s[s.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n try {\n // Prefer JSON.parse for double-quoted (handles escapes)\n if (first === '\"') return JSON.parse(s) as unknown as T;\n } catch { /* fall through */ }\n return s.slice(1, -1) as unknown as T;\n }\n }\n return v;\n}\nimport {\n buildConsolidationPrompt,\n parseConsolidationResponse,\n applyExtracted,\n type ConsolidationInput,\n} from \"./consolidator.js\";\n\nconst DEFAULT_MEMORY_DIR = join(homedir(), \".pi\", \"memory\");\nconst DEFAULT_DB_PATH = join(DEFAULT_MEMORY_DIR, \"memory.db\");\nconst GLOBAL_SETTINGS_PATH = join(homedir(), \".pi\", \"agent\", \"settings.json\");\n\n/**\n * Default model used for session-end consolidation when no user override is\n * present in settings.json. Preserves the historical behavior for existing\n * users \u2014 overridable via `memory.consolidationModel` (global or project).\n */\nexport const DEFAULT_CONSOLIDATION_MODEL = \"claude-sonnet-4-20250514\";\n\n/**\n * Resolve the memory DB path for a given working directory.\n *\n * Priority (highest first):\n * 1. \"pi-memory\".localPath from {cwd}/.pi/settings.json \u2192 {localPath}/memory.db\n * 2. \"pi-total-recall\".localPath cascade \u2192 {localPath}/memory/memory.db\n * 3. Global default: ~/.pi/memory/memory.db (preserves existing behavior)\n */\n/**\n * Emit a warning when a settings block contains keys outside a known\n * schema. Catches silent typos like `LocalPath` vs `localPath` \u2014 an unknown\n * key is usually a misspelled known key that got silently ignored, leaving\n * the user wondering why their config didn't take effect.\n *\n * Logs to stderr (console.error) since this runs inside module-level code\n * at session_start; ctx.ui isn't reliably available yet.\n */\nfunction warnUnknownKeys(block: unknown, blockName: string, knownKeys: readonly string[]): void {\n if (!block || typeof block !== \"object\") return;\n const unknown = Object.keys(block as Record).filter((k) => !knownKeys.includes(k));\n if (unknown.length === 0) return;\n console.error(\n `pi-memory: ignoring unknown key(s) in settings.json \"${blockName}\" block: ${unknown.join(\", \")} (expected: ${knownKeys.join(\", \")})`,\n );\n}\n\nconst PI_MEMORY_KNOWN_KEYS = [\"localPath\", \"lessonInjection\", \"consolidationModel\", \"perTurnInjection\"] as const;\nconst PI_TOTAL_RECALL_KNOWN_KEYS = [\"localPath\"] as const;\n\nexport function resolveDbPath(cwd: string): string {\n // Try reading the local project settings for an explicit localPath override\n try {\n const localSettingsPath = join(cwd, \".pi\", \"settings.json\");\n const raw = readFileSync(localSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw);\n\n // Package-specific override wins.\n const piMemory = settings?.[\"pi-memory\"];\n warnUnknownKeys(piMemory, \"pi-memory\", PI_MEMORY_KNOWN_KEYS);\n if (piMemory && typeof piMemory === \"object\" && typeof piMemory.localPath === \"string\" && piMemory.localPath) {\n return join(piMemory.localPath, \"memory.db\");\n }\n\n // pi-total-recall cascade.\n const piTotalRecall = settings?.[\"pi-total-recall\"];\n warnUnknownKeys(piTotalRecall, \"pi-total-recall\", PI_TOTAL_RECALL_KNOWN_KEYS);\n if (piTotalRecall && typeof piTotalRecall === \"object\" && typeof piTotalRecall.localPath === \"string\" && piTotalRecall.localPath) {\n return join(piTotalRecall.localPath, \"memory\", \"memory.db\");\n }\n } catch {\n // No local settings or parse error \u2014 use global default\n }\n // Default: global shared memory (preserves existing behavior)\n return DEFAULT_DB_PATH;\n}\n\n/**\n * Apply a single settings-block (the object under `memory` / `pi-memory`) to\n * `config`. Invalid fields are ignored so a malformed value for one key\n * cannot clobber a valid value already set by a higher-priority source.\n */\nfunction mergeMemorySettings(config: InjectorConfig, memorySettings: unknown): void {\n if (!memorySettings || typeof memorySettings !== \"object\") return;\n const m = memorySettings as Record;\n\n if (m.lessonInjection === \"all\" || m.lessonInjection === \"selective\") {\n config.lessonInjection = m.lessonInjection;\n }\n if (typeof m.perTurnInjection === \"boolean\") {\n config.perTurnInjection = m.perTurnInjection;\n }\n if (typeof m.consolidationModel === \"string\" && m.consolidationModel.trim()) {\n config.consolidationModel = m.consolidationModel.trim();\n }\n}\n\n/**\n * Read pi-memory config from settings.json.\n * Looks for a \"memory\" (or project-local \"pi-memory\") key with\n * extension-specific settings.\n *\n * Example settings.json:\n * {\n * \"memory\": {\n * \"perTurnInjection\": true,\n * \"lessonInjection\": \"selective\",\n * \"consolidationModel\": \"openai/gpt-4.1-mini\"\n * }\n * }\n *\n * Exported for tests.\n */\nexport function readSettingsConfig(cwd?: string): InjectorConfig {\n const config: InjectorConfig = {};\n\n // Read global settings\n try {\n const raw = readFileSync(GLOBAL_SETTINGS_PATH, \"utf-8\");\n const settings = JSON.parse(raw);\n mergeMemorySettings(config, settings?.memory);\n } catch {\n // no global settings\n }\n\n // Override with local project settings if available\n if (cwd) {\n try {\n const raw = readFileSync(join(cwd, \".pi\", \"settings.json\"), \"utf-8\");\n const settings = JSON.parse(raw);\n // Accept either `memory` (preferred) or `pi-memory` (package-scoped).\n mergeMemorySettings(config, settings?.memory ?? settings?.[\"pi-memory\"]);\n } catch {\n // no local settings\n }\n }\n\n return config;\n}\n\nexport default function (pi: ExtensionAPI) {\n let store: MemoryStore | null = null;\n let pendingUserMessages: string[] = [];\n let pendingAssistantMessages: string[] = [];\n let sessionCwd: string = \"\";\n let sessionId: string | undefined;\n let cachedCtx: any = null;\n let resolvedDbPath: string = DEFAULT_DB_PATH;\n let injectorConfig: InjectorConfig = readSettingsConfig();\n\n // \u2500\u2500\u2500 Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.on(\"session_start\", async (_event, ctx) => {\n try {\n sessionCwd = ctx.cwd;\n cachedCtx = ctx;\n sessionId = (ctx as any).sessionId ?? (ctx as any).session?.id;\n\n // Resolve per-agent DB path from local settings or cwd\n resolvedDbPath = resolveDbPath(sessionCwd);\n injectorConfig = readSettingsConfig(sessionCwd);\n\n store = new MemoryStore(resolvedDbPath);\n\n // Seed pending messages from existing session history so that\n // /memory-consolidate works even when resuming a session (the\n // historical messages never fire agent_end). See #5.\n pendingUserMessages = [];\n pendingAssistantMessages = [];\n try {\n const branch = ctx.sessionManager.getBranch();\n for (const entry of branch) {\n if (entry.type !== \"message\") continue;\n const msg = (entry as any).message;\n if (!msg) continue;\n if (msg.role === \"user\") {\n const text = extractText(msg.content);\n if (text) pendingUserMessages.push(text);\n } else if (msg.role === \"assistant\") {\n const text = extractText(msg.content);\n if (text) pendingAssistantMessages.push(text);\n }\n }\n } catch {\n // Session may not have entries yet (brand-new session)\n }\n\n const stats = store.stats();\n if (stats.semantic + stats.lessons > 0) {\n ctx.ui.setStatus(\"pi-memory\", `Memory: ${stats.semantic} facts, ${stats.lessons} lessons`);\n // The captured ctx may be stale by the time this fires (resume,\n // /new, /fork, /reload). Stale-ctx access throws synchronously;\n // swallow it \u2014 by then the new session has set its own status.\n setTimeout(() => {\n try { ctx.ui.setStatus(\"pi-memory\", \"\"); } catch { /* ctx stale: harmless */ }\n }, 5000);\n }\n\n // Inject stored memory as a one-shot custom message BEFORE any user\n // message arrives. Matches pi-knowledge-search's pattern.\n //\n // Skipped when `perTurnInjection: true` \u2014 in that mode the\n // before_agent_start handler below takes over with per-turn semantic\n // matching via systemPrompt mutation.\n //\n // Historical note: v1.0.x mutated event.systemPrompt in before_agent_start.\n // That broke provider prefix caches on every turn boundary (any drift in\n // the system block re-writes the conversation suffix at cacheWrite rates).\n //\n // v1.1.x returned { message } from before_agent_start. That was worse: the\n // custom message landed AFTER the user's question in history, so the model\n // responded to the memory block instead of the user.\n //\n // v1.2.0 injects once at session_start using fallback mode (all facts +\n // lessons, 8KB cap). Correct ordering, stable cache, simpler model.\n //\n // v1.3.x adds `perTurnInjection: true` as an opt-in to restore v1.0.x\n // per-turn selective behavior (mutates systemPrompt, breaks cache on\n // every turn boundary \u2014 users opt in knowing the tradeoff).\n if (!injectorConfig.perTurnInjection) {\n try {\n const alreadyInjected = ctx.sessionManager\n .getEntries()\n .some(\n (e: SessionEntry) =>\n e.type === \"custom_message\" && e.customType === \"pi-memory-context\",\n );\n if (!alreadyInjected) {\n const { text, stats: injStats } = buildContextBlock(\n store,\n sessionCwd,\n undefined, // no prompt \u2192 fallback: dump all relevant memory\n injectorConfig,\n );\n if (text) {\n pi.sendMessage({\n customType: \"pi-memory-context\",\n content: text,\n display: false,\n details: injStats,\n });\n }\n }\n } catch {\n // Injection is nice-to-have; never break startup over it.\n }\n }\n } catch (err: any) {\n ctx.ui.notify(`pi-memory: failed to open store: ${err.message}`, \"warning\");\n }\n });\n\n // ----------------------------------------------------------------\n // Opt-in per-turn selective injection (v1.3.0).\n //\n // When `perTurnInjection: true` is set, run a semantic search against the\n // current user prompt and append matching memory to event.systemPrompt.\n // MUST use systemPrompt (not { message }) \u2014 returning { message } puts the\n // content AFTER the user message and causes the model to respond to the\n // injected memory instead of the user. See v1.1.x postmortem.\n //\n // This breaks provider prefix caches on every turn boundary \u2014 an accepted\n // cost for users who want per-query relevance from large memory stores.\n // ----------------------------------------------------------------\n pi.on(\"before_agent_start\", async (event, ctx) => {\n if (!store) return;\n if (!injectorConfig.perTurnInjection) return;\n\n const { text } = buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig);\n if (!text) return;\n\n return {\n systemPrompt: `${event.systemPrompt}\\n\\n${text}`,\n };\n });\n\n\n pi.on(\"agent_end\", async (event, _ctx) => {\n // Collect messages for consolidation at shutdown\n for (const msg of event.messages) {\n if (msg.role === \"user\" && \"content\" in msg) {\n const text = extractText(msg.content);\n if (text) {\n pendingUserMessages.push(text);\n if (pendingUserMessages.length > 60) pendingUserMessages.shift();\n }\n } else if (msg.role === \"assistant\" && \"content\" in msg) {\n const text = extractText(msg.content);\n if (text) {\n pendingAssistantMessages.push(text);\n if (pendingAssistantMessages.length > 60) pendingAssistantMessages.shift();\n }\n }\n }\n });\n\n // Consolidate memory when switching sessions (/new, /resume)\n pi.on(\"session_before_switch\", async (_event, ctx) => {\n if (!store) return;\n\n if (pendingUserMessages.length >= 3) {\n ctx.ui.setStatus(\"pi-memory\", \"\uD83E\uDDE0 Consolidating memory...\");\n try {\n await consolidateSession();\n } catch {\n // Best-effort\n } finally {\n // Always clear \u2014 even if consolidateSession threw synchronously or\n // ctx went stale. Stuck indicator otherwise pins for the rest of\n // the session. See samfoy/pi-total-recall#5.\n try { ctx.ui.setStatus(\"pi-memory\", \"\"); } catch { /* ctx stale: harmless */ }\n }\n }\n\n // Reset for the next session\n pendingUserMessages = [];\n pendingAssistantMessages = [];\n });\n\n pi.on(\"session_shutdown\", async () => {\n if (!store) return;\n\n // Immediate visual feedback \u2014 user sees this as soon as C-c C-c fires\n if (cachedCtx) {\n cachedCtx.ui.setStatus(\"pi-memory\", \"\uD83E\uDDE0 Consolidating memory...\");\n }\n\n // Consolidate if we have enough conversation\n if (pendingUserMessages.length >= 3) {\n try {\n await consolidateSession();\n } catch {\n // Best-effort \u2014 don't crash on shutdown\n }\n }\n\n store.close();\n store = null;\n });\n\n // \u2500\u2500\u2500 Consolidation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async function consolidateSession(): Promise {\n if (!store) return;\n\n const input: ConsolidationInput = {\n userMessages: pendingUserMessages,\n assistantMessages: pendingAssistantMessages,\n cwd: sessionCwd,\n sessionId,\n };\n\n const currentFacts = store.listSemantic(undefined, 200).map(f => ({ key: f.key, value: f.value }));\n const currentLessons = store.listLessons(undefined, 100).map(l => ({ rule: l.rule, category: l.category }));\n const prompt = buildConsolidationPrompt(input, currentFacts, currentLessons);\n\n // Use pi's exec to call the LLM via a lightweight pi session.\n // Use a fast model to avoid blocking shutdown for too long.\n //\n // Defence in depth: pi.exec has a 45s timeout, but we also wrap the\n // whole call in a hard 60s backstop. If pi.exec's timeout ever fails\n // to kill the child (e.g. stuck in syscall), the Promise.race below\n // still rejects and lets the caller clear its status indicator.\n const EXEC_TIMEOUT_MS = 45_000;\n const HARD_TIMEOUT_MS = 60_000;\n let backstopHandle: ReturnType | undefined;\n try {\n const execPromise = pi.exec(\"pi\", [\n \"-p\", prompt,\n \"--print\",\n \"--no-extensions\",\n \"--model\", injectorConfig.consolidationModel ?? DEFAULT_CONSOLIDATION_MODEL,\n ], {\n timeout: EXEC_TIMEOUT_MS,\n cwd: sessionCwd,\n });\n\n const result = await Promise.race([\n execPromise,\n new Promise((_, reject) => {\n backstopHandle = setTimeout(\n () => reject(new Error(\"consolidation backstop timeout\")),\n HARD_TIMEOUT_MS,\n );\n }),\n ]);\n\n if (result.code === 0 && result.stdout) {\n const extracted = parseConsolidationResponse(result.stdout);\n const applied = applyExtracted(store!, extracted, `session:${sessionId ?? \"unknown\"}`);\n if (applied.semantic + applied.lessons > 0) {\n // Log but don't notify \u2014 we're shutting down\n console.error(`pi-memory: consolidated ${applied.semantic} facts, ${applied.lessons} lessons`);\n }\n }\n } catch {\n // Timeout or exec failure \u2014 skip consolidation this session\n } finally {\n if (backstopHandle) clearTimeout(backstopHandle);\n }\n }\n\n // \u2500\u2500\u2500 Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.registerTool({\n name: \"memory_search\",\n label: \"Memory Search\",\n description: \"Search persistent memory for facts, preferences, and project patterns the user has established across sessions.\",\n parameters: Type.Object({\n query: Type.String({ description: \"Search query\" }),\n limit: Type.Optional(Type.Number({ description: \"Max results (default 10)\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const results = store.searchSemantic(params.query, params.limit ?? 10);\n if (results.length === 0) {\n return ok(\"No matching memories found.\");\n }\n\n const text = results.map(r =>\n `${r.key}: ${r.value} (confidence: ${r.confidence}, source: ${r.source})`\n ).join(\"\\n\");\n\n return ok(text);\n },\n });\n\n pi.registerTool({\n name: \"memory_remember\",\n label: \"Memory Remember\",\n description: \"Store a fact, preference, or lesson in persistent memory. Use dotted keys like pref.editor, project.rosie.lang, tool.sed.usage. For corrections, use type='lesson'.\",\n parameters: Type.Object({\n type: Type.String({ description: \"'fact' for key-value, 'lesson' for a correction\" }),\n key: Type.Optional(Type.String({ description: \"Dotted key for facts (e.g. pref.commit_style)\" })),\n value: Type.Optional(Type.String({ description: \"Value for facts\" })),\n rule: Type.Optional(Type.String({ description: \"Rule text for lessons\" })),\n category: Type.Optional(Type.String({ description: \"Category for lessons (default: general)\" })),\n negative: Type.Optional(Type.Boolean({ description: \"True if this is something to AVOID\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n // Defensively unwrap double-quoted string args from misbehaving model runners.\n params = {\n ...params,\n type: stripQuotes(params.type),\n key: stripQuotes(params.key),\n value: stripQuotes(params.value),\n rule: stripQuotes(params.rule),\n category: stripQuotes(params.category),\n };\n\n if (params.type !== \"fact\" && params.type !== \"lesson\") {\n return ok(`Invalid type: ${params.type}. Must be 'fact' or 'lesson'.`);\n }\n\n if (params.type === \"fact\") {\n if (!params.key || !params.value) {\n return ok(\"Both key and value required for facts\");\n }\n store.setSemantic(params.key, params.value, 0.95, \"user\");\n return ok(`Remembered: ${params.key} = ${params.value}`);\n }\n\n if (params.type === \"lesson\") {\n if (!params.rule) {\n return ok(\"Rule text required for lessons\");\n }\n const result = store.addLesson(params.rule, params.category ?? \"general\", \"user\", params.negative ?? false);\n if (result.success) {\n return ok(`Lesson learned: ${params.rule}`);\n }\n return ok(`Already known (${result.reason}): ${params.rule}`);\n }\n\n return ok(\"Unknown type\");\n },\n });\n\n pi.registerTool({\n name: \"memory_forget\",\n label: \"Memory Forget\",\n description: \"Remove a fact or lesson from persistent memory.\",\n parameters: Type.Object({\n type: Type.String(),\n key: Type.Optional(Type.String({ description: \"Key for facts\" })),\n id: Type.Optional(Type.String({ description: \"ID for lessons\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n params = {\n ...params,\n type: stripQuotes(params.type),\n key: stripQuotes(params.key),\n id: stripQuotes(params.id),\n };\n\n if (params.type !== \"fact\" && params.type !== \"lesson\") {\n return ok(`Invalid type: ${params.type}. Must be 'fact' or 'lesson'.`);\n }\n\n if (params.type === \"fact\" && params.key) {\n const deleted = store.deleteSemantic(params.key);\n return ok(deleted ? `Forgot: ${params.key}` : `Not found: ${params.key}`);\n }\n\n if (params.type === \"lesson\" && params.id) {\n const deleted = store.deleteLesson(params.id);\n return ok(deleted ? `Forgot lesson ${params.id}` : `Not found: ${params.id}`);\n }\n\n return ok(\"Provide key (for facts) or id (for lessons)\");\n },\n });\n\n pi.registerTool({\n name: \"memory_lessons\",\n label: \"Memory Lessons\",\n description: \"List learned corrections and lessons from past sessions.\",\n parameters: Type.Object({\n category: Type.Optional(Type.String({ description: \"Filter by category\" })),\n limit: Type.Optional(Type.Number({ description: \"Max results (default 50)\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const lessons = store.listLessons(params.category, params.limit ?? 50);\n if (lessons.length === 0) {\n return ok(\"No lessons learned yet.\");\n }\n\n const text = lessons.map(l =>\n `${l.negative ? \"\u274C\" : \"\u2705\"} [${l.category}] ${l.rule} (id: ${l.id.slice(0, 8)})`\n ).join(\"\\n\");\n\n return ok(text);\n },\n });\n\n pi.registerTool({\n name: \"memory_stats\",\n label: \"Memory Stats\",\n description: \"Show memory statistics \u2014 how many facts, lessons, and events are stored.\",\n parameters: Type.Object({}) as any,\n async execute(_id, _params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const stats = store.stats();\n const text = `Memory: ${stats.semantic} semantic facts, ${stats.lessons} active lessons, ${stats.events} events logged\\nDB: ${resolvedDbPath}`;\n return ok(text);\n },\n });\n\n // \u2500\u2500\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.registerCommand(\"memory-consolidate\", {\n description: \"Manually trigger memory consolidation for the current session\",\n async handler(_args, ctx) {\n if (!store) {\n ctx.ui.notify(\"Memory store not initialized\", \"warning\");\n return;\n }\n\n if (pendingUserMessages.length < 2) {\n ctx.ui.notify(\"Not enough conversation to consolidate (need at least 2 user messages)\", \"warning\");\n return;\n }\n\n ctx.ui.notify(\"Consolidating session memory...\", \"info\");\n try {\n await consolidateSession();\n const stats = store.stats();\n ctx.ui.notify(`Memory updated: ${stats.semantic} facts, ${stats.lessons} lessons`, \"info\");\n } catch (err: any) {\n ctx.ui.notify(`Consolidation failed: ${err.message}`, \"error\");\n }\n },\n });\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction extractText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .filter((c: any) => c.type === \"text\" && typeof c.text === \"string\")\n .map((c: any) => c.text)\n .join(\"\\n\");\n }\n return \"\";\n}\n", "/**\n * SQLite-backed memory store using Node's built-in node:sqlite.\n * Three tables:\n * - semantic: key-value facts (preferences, project patterns, corrections)\n * - lessons: learned corrections with dedup\n * - events: audit log of all memory operations\n */\nimport { DatabaseSync } from \"node:sqlite\";\nimport { mkdirSync, existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SemanticEntry {\n key: string;\n value: string;\n confidence: number;\n source: \"user\" | \"consolidation\" | \"correction\";\n created_at: string;\n updated_at: string;\n last_accessed?: string;\n}\n\nexport interface LessonEntry {\n id: string;\n rule: string;\n category: string;\n source: string;\n negative: boolean;\n created_at: string;\n}\n\nexport interface MemoryEvent {\n id: number;\n event_type: string;\n memory_type: string;\n memory_key: string;\n details: string;\n created_at: string;\n}\n\n// \u2500\u2500\u2500 Store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MemoryStore {\n private db: DatabaseSync;\n private writeLock: Promise = Promise.resolve();\n private hasFTS5: boolean = false;\n\n constructor(dbPath: string) {\n const dir = dirname(dbPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n this.db = new DatabaseSync(dbPath);\n this.db.exec(\"PRAGMA journal_mode = WAL\");\n this.db.exec(\"PRAGMA busy_timeout = 5000\");\n this.db.exec(\"PRAGMA foreign_keys = ON\");\n this.migrate();\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS semantic (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.8,\n source TEXT NOT NULL DEFAULT 'consolidation',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS lessons (\n id TEXT PRIMARY KEY,\n rule TEXT NOT NULL,\n category TEXT NOT NULL DEFAULT 'general',\n source TEXT NOT NULL DEFAULT 'consolidation',\n negative INTEGER NOT NULL DEFAULT 0,\n is_deleted INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n event_type TEXT NOT NULL,\n memory_type TEXT NOT NULL,\n memory_key TEXT NOT NULL,\n details TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n `);\n\n // Migration: add last_accessed column if missing\n try {\n this.db.exec(`ALTER TABLE semantic ADD COLUMN last_accessed TEXT`);\n } catch {\n // Column already exists \u2014 ignore\n }\n\n // FTS5 virtual tables for semantic + lesson search (optional \u2014 node:sqlite may lack FTS5)\n try {\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS semantic_fts USING fts5(key, value, content='semantic', content_rowid='rowid');\n\n CREATE TRIGGER IF NOT EXISTS semantic_ai AFTER INSERT ON semantic BEGIN\n INSERT INTO semantic_fts(rowid, key, value) VALUES (new.rowid, new.key, new.value);\n END;\n CREATE TRIGGER IF NOT EXISTS semantic_ad AFTER DELETE ON semantic BEGIN\n INSERT INTO semantic_fts(semantic_fts, rowid, key, value) VALUES('delete', old.rowid, old.key, old.value);\n END;\n CREATE TRIGGER IF NOT EXISTS semantic_au AFTER UPDATE ON semantic BEGIN\n INSERT INTO semantic_fts(semantic_fts, rowid, key, value) VALUES('delete', old.rowid, old.key, old.value);\n INSERT INTO semantic_fts(rowid, key, value) VALUES (new.rowid, new.key, new.value);\n END;\n `);\n\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS lessons_fts USING fts5(rule, category, content='lessons', content_rowid='rowid');\n\n CREATE TRIGGER IF NOT EXISTS lessons_fts_ai AFTER INSERT ON lessons BEGIN\n INSERT INTO lessons_fts(rowid, rule, category) VALUES (new.rowid, new.rule, new.category);\n END;\n CREATE TRIGGER IF NOT EXISTS lessons_fts_ad AFTER DELETE ON lessons BEGIN\n INSERT INTO lessons_fts(lessons_fts, rowid, rule, category) VALUES('delete', old.rowid, old.rule, old.category);\n END;\n CREATE TRIGGER IF NOT EXISTS lessons_fts_au AFTER UPDATE ON lessons BEGIN\n INSERT INTO lessons_fts(lessons_fts, rowid, rule, category) VALUES('delete', old.rowid, old.rule, old.category);\n INSERT INTO lessons_fts(rowid, rule, category) VALUES (new.rowid, new.rule, new.category);\n END;\n `);\n\n // Rebuild FTS indexes from existing data (idempotent)\n this.db.exec(`INSERT INTO semantic_fts(semantic_fts) VALUES('rebuild')`);\n this.db.exec(`INSERT INTO lessons_fts(lessons_fts) VALUES('rebuild')`);\n this.hasFTS5 = true;\n } catch {\n // FTS5 not available (node:sqlite compiled without SQLITE_ENABLE_FTS5).\n // Search will use substring fallback \u2014 fine for typical memory store sizes.\n this.hasFTS5 = false;\n }\n }\n\n /**\n * Serialize async callers so concurrent read-modify-write cycles\n * (e.g. two consolidation calls) don't clobber each other.\n */\n private withLock(fn: () => T): T {\n // DatabaseSync is synchronous, so we just need to ensure\n // transactional integrity. Wrap in a SQLite transaction.\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = fn();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n throw err;\n }\n }\n\n // \u2500\u2500\u2500 Semantic \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getSemantic(key: string): SemanticEntry | undefined {\n const normalized = key.toLowerCase();\n return this.db.prepare(\"SELECT * FROM semantic WHERE key = ?\").get(normalized) as unknown as SemanticEntry | undefined;\n }\n\n setSemantic(key: string, value: string, confidence: number = 0.8, source: SemanticEntry[\"source\"] = \"consolidation\"): void {\n const normalized = key.toLowerCase();\n this.withLock(() => {\n const existing = this.db.prepare(\"SELECT * FROM semantic WHERE key = ?\").get(normalized) as unknown as SemanticEntry | undefined;\n if (existing && existing.confidence > confidence) return; // higher confidence wins\n\n this.db.prepare(`\n INSERT INTO semantic (key, value, confidence, source, updated_at)\n VALUES (?, ?, ?, ?, datetime('now'))\n ON CONFLICT(key) DO UPDATE SET\n value = excluded.value,\n confidence = excluded.confidence,\n source = excluded.source,\n updated_at = datetime('now')\n `).run(normalized, value, confidence, source);\n\n this.logEvent(existing ? \"update\" : \"create\", \"semantic\", normalized);\n });\n }\n\n deleteSemantic(key: string): boolean {\n const normalized = key.toLowerCase();\n return this.withLock(() => {\n const result = this.db.prepare(\"DELETE FROM semantic WHERE key = ?\").run(normalized);\n if (result.changes > 0) this.logEvent(\"delete\", \"semantic\", normalized);\n return result.changes > 0;\n });\n }\n\n listSemantic(prefix?: string, limit: number = 100): SemanticEntry[] {\n if (prefix) {\n return this.db.prepare(\"SELECT * FROM semantic WHERE key LIKE ? ORDER BY updated_at DESC LIMIT ?\")\n .all(`${prefix}%`, limit) as unknown as SemanticEntry[];\n }\n return this.db.prepare(\"SELECT * FROM semantic ORDER BY updated_at DESC LIMIT ?\")\n .all(limit) as unknown as SemanticEntry[];\n }\n\n searchSemantic(query: string, limit: number = 10): SemanticEntry[] {\n const terms = query.trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n if (!this.hasFTS5) return this._searchSemanticFallback(query, limit);\n\n // Build FTS5 query \u2014 quote each term for safety\n const ftsQuery = terms.map(t => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n\n try {\n const rows = this.db.prepare(`\n SELECT s.key, s.value, s.confidence, s.source, s.created_at, s.updated_at, s.last_accessed\n FROM semantic s\n JOIN semantic_fts fts ON s.rowid = fts.rowid\n WHERE semantic_fts MATCH ?\n ORDER BY bm25(semantic_fts)\n LIMIT ?\n `).all(ftsQuery, limit) as unknown as SemanticEntry[];\n\n return rows;\n } catch {\n // FTS query failed \u2014 fall back to substring matching\n return this._searchSemanticFallback(query, limit);\n }\n }\n\n private _searchSemanticFallback(query: string, limit: number): SemanticEntry[] {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n const all = this.db.prepare(\"SELECT * FROM semantic\").all() as unknown as SemanticEntry[];\n return all\n .map(entry => {\n const text = `${entry.key} ${entry.value}`.toLowerCase();\n const matches = terms.filter(t => text.includes(t)).length;\n return { entry, score: matches / terms.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => entry);\n }\n\n touchAccessed(keys: string[]): void {\n if (keys.length === 0) return;\n const stmt = this.db.prepare(\"UPDATE semantic SET last_accessed = datetime('now') WHERE key = ?\");\n for (const key of keys) {\n stmt.run(key.toLowerCase());\n }\n }\n\n // \u2500\u2500\u2500 Lessons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n addLesson(rule: string, category: string = \"general\", source: string = \"consolidation\", negative: boolean = false): { success: boolean; id?: string; reason?: string } {\n const trimmed = rule.trim();\n if (!trimmed) return { success: false, reason: \"empty rule\" };\n\n const normalizedCategory = category.trim().toLowerCase() || \"general\";\n\n return this.withLock(() => {\n // Exact-match dedup (case-insensitive)\n const existing = this.db.prepare(\n \"SELECT id FROM lessons WHERE LOWER(TRIM(rule)) = LOWER(?) AND is_deleted = 0\"\n ).get(trimmed.toLowerCase()) as { id: string } | undefined;\n if (existing) return { success: false as const, reason: \"duplicate\" as const, id: existing.id };\n\n // Jaccard dedup\n const allRules = this.db.prepare(\"SELECT id, rule FROM lessons WHERE is_deleted = 0\").all() as { id: string; rule: string }[];\n for (const r of allRules) {\n if (jaccard(trimmed, r.rule) >= 0.7) {\n return { success: false as const, reason: \"similar\" as const, id: r.id };\n }\n }\n\n const id = crypto.randomUUID();\n this.db.prepare(\n \"INSERT INTO lessons (id, rule, category, source, negative) VALUES (?, ?, ?, ?, ?)\"\n ).run(id, trimmed, normalizedCategory, source, negative ? 1 : 0);\n\n this.logEvent(\"create\", \"lesson\", id, trimmed.slice(0, 100));\n return { success: true as const, id };\n });\n }\n\n getLesson(id: string): LessonEntry | undefined {\n const row = this.db.prepare(\"SELECT * FROM lessons WHERE id = ? AND is_deleted = 0\").get(id) as any;\n if (!row) return undefined;\n return { ...row, negative: !!row.negative };\n }\n\n listLessons(category?: string, limit: number = 50): LessonEntry[] {\n let rows: any[];\n if (category) {\n const normalizedCategory = category.trim().toLowerCase();\n rows = this.db.prepare(\"SELECT * FROM lessons WHERE category = ? AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?\")\n .all(normalizedCategory, limit);\n } else {\n rows = this.db.prepare(\"SELECT * FROM lessons WHERE is_deleted = 0 ORDER BY created_at DESC LIMIT ?\")\n .all(limit);\n }\n return rows.map(r => ({ ...r, negative: !!r.negative }));\n }\n\n /**\n * Search lessons by relevance to a query. Uses FTS5 when available,\n * falls back to substring matching. Returns lessons ranked by relevance.\n */\n searchLessons(query: string, limit: number = 20): LessonEntry[] {\n const terms = query.trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n if (!this.hasFTS5) return this._searchLessonsFallback(query, limit);\n\n const ftsQuery = terms.map(t => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n\n try {\n const rows = this.db.prepare(`\n SELECT l.id, l.rule, l.category, l.source, l.negative, l.created_at\n FROM lessons l\n JOIN lessons_fts fts ON l.rowid = fts.rowid\n WHERE lessons_fts MATCH ? AND l.is_deleted = 0\n ORDER BY bm25(lessons_fts)\n LIMIT ?\n `).all(ftsQuery, limit) as any[];\n\n return rows.map(r => ({ ...r, negative: !!r.negative }));\n } catch {\n return this._searchLessonsFallback(query, limit);\n }\n }\n\n private _searchLessonsFallback(query: string, limit: number): LessonEntry[] {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n const all = this.db.prepare(\"SELECT * FROM lessons WHERE is_deleted = 0\").all() as any[];\n return all\n .map(entry => {\n const text = `${entry.rule} ${entry.category}`.toLowerCase();\n const matches = terms.filter(t => text.includes(t)).length;\n return { entry: { ...entry, negative: !!entry.negative } as LessonEntry, score: matches / terms.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => entry);\n }\n\n deleteLesson(id: string): boolean {\n return this.withLock(() => {\n // Support both full UUIDs and prefix matches (e.g. first 8 chars)\n let result = this.db.prepare(\"UPDATE lessons SET is_deleted = 1 WHERE id = ? AND is_deleted = 0\").run(id);\n if (result.changes === 0 && id.length < 36) {\n // Try prefix match \u2014 ensure it's unambiguous\n const matches = this.db.prepare(\"SELECT id FROM lessons WHERE id LIKE ? AND is_deleted = 0\").all(`${id}%`) as { id: string }[];\n if (matches.length === 1) {\n result = this.db.prepare(\"UPDATE lessons SET is_deleted = 1 WHERE id = ? AND is_deleted = 0\").run(matches[0].id);\n if (result.changes > 0) this.logEvent(\"delete\", \"lesson\", matches[0].id);\n return true;\n }\n }\n if (result.changes > 0) this.logEvent(\"delete\", \"lesson\", id);\n return result.changes > 0;\n });\n }\n\n // \u2500\u2500\u2500 Events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private logEvent(eventType: string, memoryType: string, key: string, details: string = \"\"): void {\n this.db.prepare(\n \"INSERT INTO events (event_type, memory_type, memory_key, details) VALUES (?, ?, ?, ?)\"\n ).run(eventType, memoryType, key, details);\n }\n\n listEvents(limit: number = 50): MemoryEvent[] {\n return this.db.prepare(\"SELECT * FROM events ORDER BY id DESC LIMIT ?\").all(limit) as unknown as MemoryEvent[];\n }\n\n // \u2500\u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n stats(): { semantic: number; lessons: number; events: number } {\n const semantic = (this.db.prepare(\"SELECT COUNT(*) as c FROM semantic\").get() as any).c;\n const lessons = (this.db.prepare(\"SELECT COUNT(*) as c FROM lessons WHERE is_deleted = 0\").get() as any).c;\n const events = (this.db.prepare(\"SELECT COUNT(*) as c FROM events\").get() as any).c;\n return { semantic, lessons, events };\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction jaccard(a: string, b: string): number {\n const setA = new Set(a.toLowerCase().split(/\\s+/).filter(Boolean));\n const setB = new Set(b.toLowerCase().split(/\\s+/).filter(Boolean));\n if (setA.size === 0 && setB.size === 0) return 1;\n const intersection = new Set([...setA].filter(x => setB.has(x)));\n const union = new Set([...setA, ...setB]);\n return intersection.size / union.size;\n}\n", "/**\n * Builds a context block from memory for injection into the system prompt.\n *\n * Two modes:\n * - Selective (prompt provided): search semantic memory for entries relevant\n * to the user's current prompt, plus always-inject lessons.\n * - Fallback (no prompt): dump top entries by prefix (old behavior).\n */\nimport type { MemoryStore, SemanticEntry, LessonEntry } from \"./store.js\";\nimport os from \"node:os\";\n\nconst MAX_CONTEXT_CHARS = 8000;\nconst SEARCH_LIMIT = 15;\nconst LESSON_SEARCH_LIMIT = 15;\n\nexport interface ContextBlock {\n text: string;\n stats: { semantic: number; lessons: number };\n}\n\n/**\n * Configuration for lesson injection behavior.\n * - \"all\": inject all lessons (original behavior, default)\n * - \"selective\": use semantic search to pick relevant lessons + category filtering\n */\nexport type LessonInjectionMode = \"all\" | \"selective\";\n\nexport interface InjectorConfig {\n lessonInjection?: LessonInjectionMode;\n /**\n * Opt-in: restore per-user-message selective injection.\n *\n * When false (default), pi-memory injects a one-shot fallback block at\n * session_start (correct message ordering, stable prefix cache).\n *\n * When true, the session_start dump is skipped and each turn runs a\n * semantic search against the user's current prompt; the result is\n * appended to `event.systemPrompt` in `before_agent_start`.\n *\n * Tradeoffs:\n * - Pro: per-query relevance \u2014 facts outside the 8KB fallback dump reach\n * the model when they match the current prompt.\n * - Con: the system prompt mutates every turn, invalidating the provider's\n * prefix cache after the system block (Bedrock / Anthropic cache_control).\n * Conversation suffix gets re-written at cacheWrite rates on every user\n * turn boundary (~12.5x cacheRead on Claude).\n *\n * Correctness is preserved either way: systemPrompt is a separate field\n * from the messages list, so the user's question remains the last\n * user-role message and the model responds to it.\n */\n perTurnInjection?: boolean;\n /**\n * Model string passed to `pi --model` for session-end consolidation.\n * When omitted, the built-in default is used. Useful for users on\n * non-Anthropic providers (OpenAI/Codex/OpenRouter/Ollama/local),\n * or for picking a cheaper/faster model for background extraction.\n *\n * Invalid model strings will cause the consolidation sub-process to\n * fail \u2014 the existing try/catch swallows that silently, so the worst\n * case is that consolidation skips this session.\n */\n consolidationModel?: string;\n}\n\n/**\n * Build context block. When `prompt` is provided, uses selective injection\n * (search-based). Otherwise falls back to prefix-based dump.\n */\nexport function buildContextBlock(store: MemoryStore, cwd?: string, prompt?: string, config?: InjectorConfig): ContextBlock {\n if (prompt?.trim()) {\n return buildSelectiveBlock(store, prompt, cwd, config);\n }\n return buildFallbackBlock(store, cwd);\n}\n\n// \u2500\u2500\u2500 Selective injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildSelectiveBlock(store: MemoryStore, prompt: string, cwd?: string, config?: InjectorConfig): ContextBlock {\n const sections: string[] = [];\n let semanticCount = 0;\n let lessonCount = 0;\n const mode = config?.lessonInjection ?? \"all\";\n\n // Search semantic memory using the user's prompt\n const results = store.searchSemantic(prompt, SEARCH_LIMIT);\n\n // Also search with project slug if we have a cwd, to pull in project context\n const slug = cwd ? projectSlug(cwd) : \"\";\n if (slug) {\n const projectResults = store.searchSemantic(slug, 5);\n // Merge, dedup by key\n const seen = new Set(results.map(r => r.key));\n for (const r of projectResults) {\n if (!seen.has(r.key)) {\n results.push(r);\n seen.add(r.key);\n }\n }\n }\n\n if (results.length > 0) {\n sections.push(formatSection(\"Relevant Memory\", results.map(formatSemantic)));\n semanticCount = results.length;\n\n // Track access time for these memories\n store.touchAccessed(results.map(r => r.key));\n }\n\n // Inject lessons \u2014 either all or filtered by relevance\n const lessons = mode === \"selective\"\n ? getRelevantLessons(store, prompt, cwd)\n : store.listLessons(undefined, 50);\n\n if (lessons.length > 0) {\n const corrections = lessons.filter(l => l.negative);\n const positives = lessons.filter(l => !l.negative);\n\n if (corrections.length > 0) {\n const formatted = corrections.map(l =>\n `DON'T: ${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Learned Corrections\", formatted));\n }\n if (positives.length > 0) {\n const formatted = positives.map(l =>\n `${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Validated Approaches\", formatted));\n }\n lessonCount = lessons.length;\n }\n\n if (sections.length === 0) {\n return { text: \"\", stats: { semantic: 0, lessons: 0 } };\n }\n\n let text = `\\n${sections.join(\"\\n\")}\\n\\n${MEMORY_DRIFT_CAVEAT}\\n`;\n\n if (text.length > MAX_CONTEXT_CHARS) {\n text = text.slice(0, MAX_CONTEXT_CHARS - 20) + \"\\n... (truncated)\\n\";\n }\n\n return { text, stats: { semantic: semanticCount, lessons: lessonCount } };\n}\n\n// \u2500\u2500\u2500 Selective lesson injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Get lessons relevant to the current prompt + project context.\n *\n * Strategy:\n * 1. Search lessons by prompt terms (semantic/FTS match)\n * 2. If cwd implies a project, also search by project slug\n * 3. Always include \"general\" category lessons (broadly applicable)\n * 4. Dedup and cap at LESSON_SEARCH_LIMIT\n */\nfunction getRelevantLessons(store: MemoryStore, prompt: string, cwd?: string): LessonEntry[] {\n const seen = new Set();\n const result: LessonEntry[] = [];\n\n function add(lessons: LessonEntry[]) {\n for (const l of lessons) {\n if (!seen.has(l.id)) {\n seen.add(l.id);\n result.push(l);\n }\n }\n }\n\n // 1. Search by prompt relevance (FTS across rule text + category)\n add(store.searchLessons(prompt, LESSON_SEARCH_LIMIT));\n\n // 2. Search by project slug if we have a cwd\n const slug = cwd ? projectSlug(cwd) : \"\";\n if (slug) {\n add(store.searchLessons(slug, 5));\n }\n\n // 3. Always include general lessons (they're broadly applicable)\n add(store.listLessons(\"general\", 10));\n\n return result.slice(0, LESSON_SEARCH_LIMIT);\n}\n\n// \u2500\u2500\u2500 Fallback (no prompt) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildFallbackBlock(store: MemoryStore, cwd?: string): ContextBlock {\n const sections: string[] = [];\n let semanticCount = 0;\n let lessonCount = 0;\n\n const prefs = store.listSemantic(\"pref.\", 50);\n if (prefs.length > 0) {\n sections.push(formatSection(\"User Preferences\", prefs.map(formatSemantic)));\n semanticCount += prefs.length;\n }\n\n const projects = store.listSemantic(\"project.\", 50);\n const relevant = cwd\n ? projects.filter(p => p.key.includes(projectSlug(cwd)) || p.confidence >= 0.9)\n : projects;\n if (relevant.length > 0) {\n sections.push(formatSection(\"Project Context\", relevant.map(formatSemantic)));\n semanticCount += relevant.length;\n }\n\n const tools = store.listSemantic(\"tool.\", 20);\n if (tools.length > 0) {\n sections.push(formatSection(\"Tool Preferences\", tools.map(formatSemantic)));\n semanticCount += tools.length;\n }\n\n const lessons = store.listLessons(undefined, 50);\n if (lessons.length > 0) {\n const corrections = lessons.filter(l => l.negative);\n const positives = lessons.filter(l => !l.negative);\n\n if (corrections.length > 0) {\n const formatted = corrections.map(l =>\n `DON'T: ${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Learned Corrections\", formatted));\n }\n if (positives.length > 0) {\n const formatted = positives.map(l =>\n `${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Validated Approaches\", formatted));\n }\n lessonCount = lessons.length;\n }\n\n const user = store.listSemantic(\"user.\", 10);\n if (user.length > 0) {\n sections.push(formatSection(\"User\", user.map(formatSemantic)));\n semanticCount += user.length;\n }\n\n if (sections.length === 0) {\n return { text: \"\", stats: { semantic: 0, lessons: 0 } };\n }\n\n let text = `\\n${sections.join(\"\\n\")}\\n\\n${MEMORY_DRIFT_CAVEAT}\\n`;\n\n if (text.length > MAX_CONTEXT_CHARS) {\n text = text.slice(0, MAX_CONTEXT_CHARS - 20) + \"\\n... (truncated)\\n\";\n }\n\n return { text, stats: { semantic: semanticCount, lessons: lessonCount } };\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Staleness thresholds (in days) */\nconst STALE_WARNING_DAYS = 30;\nconst VERY_STALE_DAYS = 90;\n\nfunction formatSection(title: string, items: string[]): string {\n return `## ${title}\\n${items.map(i => `- ${i}`).join(\"\\n\")}`;\n}\n\n/**\n * Format a semantic entry with staleness indicator.\n * Memories older than 30 days get a warning; older than 90 days get a strong warning.\n * This prevents the agent from treating stale facts as current truth.\n */\nfunction formatSemantic(entry: SemanticEntry): string {\n const key = entry.key.split(\".\").slice(1).join(\".\");\n const ageDays = daysSince(entry.updated_at);\n const staleTag = ageDays >= VERY_STALE_DAYS\n ? ` \u26A0\uFE0F ${ageDays}d old \u2014 verify before acting on this`\n : ageDays >= STALE_WARNING_DAYS\n ? ` (${ageDays}d ago)`\n : \"\";\n return `${key}: ${entry.value}${staleTag}`;\n}\n\n/**\n * Calculate days since a date string.\n */\nfunction daysSince(dateStr: string): number {\n try {\n const then = new Date(dateStr).getTime();\n const now = Date.now();\n return Math.floor((now - then) / (1000 * 60 * 60 * 24));\n } catch {\n return 0;\n }\n}\n\n/**\n * Memory drift caveat \u2014 appended to the memory block so the agent knows\n * to verify recalled facts against current state before acting on them.\n */\nconst MEMORY_DRIFT_CAVEAT = `## Before acting on memory\n- Memory records can become stale. If a memory names a file, function, or flag \u2014 verify it still exists before recommending it. \"The memory says X exists\" is not the same as \"X exists now.\"\n- If a recalled memory conflicts with what you observe in the current code or project state, trust what you observe now.\n- Memories about project state (deadlines, decisions, architecture) decay fastest \u2014 check if still relevant.`;\n\nfunction projectSlug(cwd: string): string {\n const parts = cwd.split(\"/\").filter(Boolean);\n const skip = new Set([\"workplace\", \"local\", \"home\", \"src\", \"scratch\", os.userInfo().username]);\n for (const p of parts.reverse()) {\n if (!skip.has(p.toLowerCase()) && p.length > 1) return p.toLowerCase();\n }\n return \"\";\n}\n", "/**\n * Consolidator \u2014 extracts structured knowledge from session conversations.\n *\n * After a session ends (or on demand), reads the conversation and uses an\n * LLM to extract:\n * - Preferences (\u2192 semantic memory, pref.*)\n * - Project patterns (\u2192 semantic memory, project.*)\n * - Corrections/lessons (\u2192 lessons table)\n * - Tool preferences (\u2192 semantic memory, tool.*)\n *\n * Uses the pi SDK's createAgentSession for the LLM call, or falls back\n * to a simple extraction when no LLM is available.\n */\nimport type { MemoryStore } from \"./store.js\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ConsolidationInput {\n /** User messages from the session */\n userMessages: string[];\n /** Assistant messages from the session */\n assistantMessages: string[];\n /** Working directory of the session */\n cwd?: string;\n /** Session ID for provenance */\n sessionId?: string;\n}\n\nexport interface ExtractedMemory {\n semantic: Array<{ key: string; value: string; confidence: number }>;\n lessons: Array<{ rule: string; category: string; negative: boolean }>;\n}\n\nexport const CONSOLIDATION_PROMPT = `You are a memory extraction system. Analyze this conversation and extract structured knowledge.\n\nExtract ONLY concrete, reusable facts \u2014 not summaries of what happened. Focus on:\n\n1. **User preferences** (key prefix: pref.) \u2014 coding style, tool preferences, workflow habits\n Example: { \"key\": \"pref.commit_style\", \"value\": \"conventional commits\", \"confidence\": 0.9 }\n\n2. **Project patterns** (key prefix: project..) \u2014 languages, frameworks, architecture decisions\n Example: { \"key\": \"project.rosie.di\", \"value\": \"Dagger dependency injection\", \"confidence\": 0.95 }\n\n3. **Tool preferences** (key prefix: tool.) \u2014 which tools to prefer/avoid, how to use them\n Example: { \"key\": \"tool.sed\", \"value\": \"use for daily note insertion, not echo >>\", \"confidence\": 0.9 }\n\n4. **Corrections/lessons** \u2014 things the user corrected, mistakes to avoid\n Example: { \"rule\": \"Use sed to insert after ## Notes heading, not echo >> which appends after Tags\", \"category\": \"vault\", \"negative\": true }\n\n5. **Validated approaches** \u2014 things the user explicitly confirmed worked well (positive signal)\n Example: { \"rule\": \"When deploying wiki changes, draft first and let user preview before publishing\", \"category\": \"wiki-edit\", \"negative\": false }\n\n## What NOT to extract \u2014 these are derivable or ephemeral, and pollute memory:\n\n- **Code patterns, architecture, file paths, project structure** \u2014 these can be derived by reading the current project state (grep, git, file reads)\n- **Git history, recent changes, who-changed-what** \u2014 git log/blame are authoritative\n- **Debugging solutions or fix recipes** \u2014 the fix is in the code; the commit message has context\n- **Anything already documented in AGENTS.md, CLAUDE.md, or project config files**\n- **Ephemeral task details** \u2014 in-progress work, temporary state, current conversation context\n- **Activity summaries** \u2014 \"today we worked on X\" is not a lasting fact. Instead ask: what was *surprising* or *non-obvious* about it?\n- **File contents or code snippets** \u2014 the file itself is the source of truth\n- **Exact commands that worked once** \u2014 unless they encode a non-obvious pattern that the agent consistently gets wrong\n\nThese exclusions apply even if the user asks to save such things. If asked, extract what was *surprising* or *non-obvious* \u2014 that is the part worth keeping.\n\nRules:\n- Only extract if confidence >= 0.8 (you're reasonably sure this is a lasting preference, not a one-off)\n- Key format: lowercase, dots as separators, no spaces\n- Keep values concise (under 200 chars)\n- For corrections, set negative=true if it's something to AVOID\n- For validated approaches (user confirmed something works), set negative=false\n\nRespond with ONLY valid JSON matching this schema:\n{\n \"semantic\": [{ \"key\": \"string\", \"value\": \"string\", \"confidence\": number }],\n \"lessons\": [{ \"rule\": \"string\", \"category\": \"string\", \"negative\": boolean }]\n}\n\nIf nothing worth extracting, return: { \"semantic\": [], \"lessons\": [] }`;\n\n// \u2500\u2500\u2500 Consolidation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Build the consolidation prompt for an LLM call.\n */\nexport function buildConsolidationPrompt(\n input: ConsolidationInput,\n currentFacts?: { key: string; value: string }[],\n currentLessons?: { rule: string; category: string }[]\n): string {\n const messages: string[] = [];\n\n // Current memory state section \u2014 helps the LLM avoid duplicates\n let memorySection = \"\";\n if ((currentFacts && currentFacts.length > 0) || (currentLessons && currentLessons.length > 0)) {\n const parts: string[] = [\"## Current Memory State\"];\n if (currentFacts && currentFacts.length > 0) {\n parts.push(\"The user already has these facts stored (avoid duplicating, update if changed):\");\n let chars = 0;\n for (const f of currentFacts) {\n const line = `- ${f.key}: ${f.value.length > 120 ? f.value.slice(0, 120) + \"\u2026\" : f.value}`;\n if (chars + line.length > 1500) { parts.push(\"- ... (truncated)\"); break; }\n parts.push(line);\n chars += line.length;\n }\n }\n if (currentLessons && currentLessons.length > 0) {\n parts.push(\"\\nAnd these lessons (avoid duplicating):\");\n let chars = 0;\n for (const l of currentLessons) {\n const line = `- [${l.category}] ${l.rule.length > 120 ? l.rule.slice(0, 120) + \"\u2026\" : l.rule}`;\n if (chars + line.length > 500) { parts.push(\"- ... (truncated)\"); break; }\n parts.push(line);\n chars += line.length;\n }\n }\n memorySection = parts.join(\"\\n\") + \"\\n\\n\";\n }\n\n // Interleave user/assistant messages for context\n const maxPairs = 30; // cap to avoid huge prompts\n const len = Math.min(input.userMessages.length, maxPairs);\n for (let i = 0; i < len; i++) {\n const userMsg = input.userMessages[i];\n if (userMsg) messages.push(`User: ${truncate(userMsg, 1000)}`);\n const assistantMsg = input.assistantMessages[i];\n if (assistantMsg) messages.push(`Assistant: ${truncate(assistantMsg, 500)}`);\n }\n\n return `${CONSOLIDATION_PROMPT}\n\n${memorySection}${input.cwd ? `Working directory: ${input.cwd}\\n` : \"\"}\n## Conversation\n\n${messages.join(\"\\n\\n\")}`;\n}\n\n/**\n * Parse the LLM's JSON response into structured memory.\n */\nexport function parseConsolidationResponse(text: string): ExtractedMemory {\n // Extract JSON from response (may be wrapped in markdown code blocks)\n const jsonMatch = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/) || text.match(/(\\{[\\s\\S]*\\})/);\n if (!jsonMatch) return { semantic: [], lessons: [] };\n\n try {\n const parsed = JSON.parse(jsonMatch[1].trim());\n const result: ExtractedMemory = { semantic: [], lessons: [] };\n\n if (Array.isArray(parsed.semantic)) {\n for (const s of parsed.semantic) {\n if (typeof s.key === \"string\" && typeof s.value === \"string\" && typeof s.confidence === \"number\") {\n if (s.confidence >= 0.8 && isValidKey(s.key) && s.value.length <= 500) {\n result.semantic.push({ key: s.key, value: s.value, confidence: s.confidence });\n }\n }\n }\n }\n\n if (Array.isArray(parsed.lessons)) {\n for (const l of parsed.lessons) {\n if (typeof l.rule === \"string\" && l.rule.trim().length > 0) {\n result.lessons.push({\n rule: l.rule.trim(),\n category: typeof l.category === \"string\" ? l.category : \"general\",\n negative: !!l.negative,\n });\n }\n }\n }\n\n return result;\n } catch {\n return { semantic: [], lessons: [] };\n }\n}\n\n/**\n * Apply extracted memory to the store, filtering out derivable/ephemeral entries.\n */\nexport function applyExtracted(store: MemoryStore, extracted: ExtractedMemory, source: string = \"consolidation\"): { semantic: number; lessons: number } {\n let semanticCount = 0;\n let lessonCount = 0;\n\n for (const s of extracted.semantic) {\n if (isDerivableOrEphemeral(s.key, s.value)) continue;\n store.setSemantic(s.key, s.value, s.confidence, \"consolidation\");\n semanticCount++;\n }\n\n for (const l of extracted.lessons) {\n if (isDerivableLesson(l.rule)) continue;\n const result = store.addLesson(l.rule, l.category, source, l.negative);\n if (result.success) lessonCount++;\n }\n\n return { semantic: semanticCount, lessons: lessonCount };\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VALID_KEY_RE = /^[a-z][a-z0-9._-]*$/;\n\nfunction isValidKey(key: string): boolean {\n return VALID_KEY_RE.test(key) && key.length <= 100 && key.length >= 2;\n}\n\n/**\n * Reject semantic entries that store derivable or ephemeral information.\n * These pollute memory \u2014 the project itself is the source of truth.\n */\nfunction isDerivableOrEphemeral(key: string, value: string): boolean {\n const kl = key.toLowerCase();\n const vl = value.toLowerCase();\n\n // File paths, architecture, project structure \u2014 derivable from the project\n if (kl.includes(\"filepath\") || kl.includes(\"file_path\") || kl.includes(\"directory\")) return true;\n if (/^project\\.\\w+\\.(path|dir|location|structure|layout|architecture)$/.test(kl)) return true;\n\n // Git history \u2014 git log/blame is authoritative\n if (kl.includes(\"commit\") || kl.includes(\"git.history\") || kl.includes(\"git.recent\")) return true;\n\n // Activity summaries \u2014 \"today we worked on X\" is not a lasting fact\n if (vl.startsWith(\"today \") || vl.startsWith(\"we worked on\") || vl.startsWith(\"this session\")) return true;\n\n // Exact file contents or long code snippets\n if (vl.includes(\"```\") && vl.length > 300) return true;\n\n // Temporary investigation state\n if (kl.includes(\"current_task\") || kl.includes(\"in_progress\") || kl.includes(\"investigating\")) return true;\n\n return false;\n}\n\n/**\n * Reject lesson entries that are derivable from code or too ephemeral.\n */\nfunction isDerivableLesson(rule: string): boolean {\n const rl = rule.toLowerCase();\n\n // \"File X is at path Y\" \u2014 derivable\n if (/file .+ is (at|in|located) /.test(rl)) return true;\n\n // \"The project uses X\" when X is obvious from package.json/build files\n if (/^the (project|codebase|repo) (uses|is written in) /.test(rl)) return true;\n\n // Pure activity logging \u2014 \"we fixed X\" or \"we deployed Y\"\n if (/^(we|i|the agent) (fixed|deployed|updated|changed|modified|ran|executed) /.test(rl)) return true;\n\n // Error\u2192fix recipes \u2014 \"when error X, run command Y\" \u2014 these are specific\n // debugging commands from one session, not generalizable lessons\n if (/^when (encountering|bash fails|edit fails|.*error)/.test(rl) && /\\b(run:|fix with:)/.test(rl)) return true;\n\n // Literal command sequences \u2014 rules that are mostly a shell command\n if (/^run: /.test(rl)) return true;\n if (rl.includes(\"command exited with code\") && rl.length < 100) return true;\n\n return false;\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max) + \"\u2026\" : text;\n}\n"], - "mappings": "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", - "names": [] + "sources": ["../src/index.ts", "../src/store.ts", "../src/embedder.ts", "../src/injector.ts", "../src/consolidator.ts"], + "sourcesContent": ["/**\n * pi-memory \u2014 Persistent memory extension for pi.\n *\n * Learns corrections, preferences, and patterns from sessions.\n * Injects relevant memory into future conversations.\n *\n * Lifecycle:\n * - session_start: open store, inject memory as a one-shot custom message\n * - (memory context is no longer injected per-turn \u2014 see v1.2.0 changelog)\n * - agent_end: queue messages for consolidation\n * - session_shutdown: consolidate and close store\n *\n * Tools:\n * - memory_search: search semantic memory\n * - memory_remember: manually add a memory\n * - memory_forget: delete a memory\n * - memory_lessons: list learned corrections\n * - memory_stats: show memory statistics\n */\nimport type { ExtensionAPI, AgentToolResult, SessionEntry } from \"@earendil-works/pi-coding-agent\";\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { readFileSync } from \"node:fs\";\nimport { MemoryStore } from \"./store.js\";\nimport { buildContextBlock, projectSlug, type InjectorConfig } from \"./injector.js\";\nimport { embed } from \"./embedder.js\";\n\n// Re-export internals so consumers (e.g. pi-dashboard's system-prompt route)\n// can build their own context blocks without reaching into ./dist/store.js.\n// The bundled `dist/index.js` inlines these, so prior `req('./dist/store.js')`\n// callers were always broken.\nexport { MemoryStore } from \"./store.js\";\nexport { buildContextBlock, projectSlug, type InjectorConfig } from \"./injector.js\";\n\ntype ToolResult = AgentToolResult;\nfunction ok(text: string): ToolResult { return { content: [{ type: \"text\", text }], details: {} }; }\n\n/**\n * Strip one layer of surrounding quotes from a string value.\n * Some local models (e.g. Qwen on certain runners) double-JSON-encode tool\n * arguments, emitting `\"\\\"fact\\\"\"` instead of `\"fact\"`. We defensively\n * unwrap so these calls don't fail schema validation / equality checks.\n */\nfunction stripQuotes(v: T): T {\n if (typeof v !== \"string\") return v;\n const s = v.trim();\n if (s.length >= 2) {\n const first = s[0];\n const last = s[s.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n try {\n // Prefer JSON.parse for double-quoted (handles escapes)\n if (first === '\"') return JSON.parse(s) as unknown as T;\n } catch { /* fall through */ }\n return s.slice(1, -1) as unknown as T;\n }\n }\n return v;\n}\nimport {\n buildConsolidationPrompt,\n parseConsolidationResponse,\n applyExtracted,\n type ConsolidationInput,\n} from \"./consolidator.js\";\n\nconst DEFAULT_MEMORY_DIR = join(homedir(), \".pi\", \"memory\");\nconst DEFAULT_DB_PATH = join(DEFAULT_MEMORY_DIR, \"memory.db\");\nconst GLOBAL_SETTINGS_PATH = join(homedir(), \".pi\", \"agent\", \"settings.json\");\n\n/**\n * Default model used for session-end consolidation when no user override is\n * present in settings.json. Preserves the historical behavior for existing\n * users \u2014 overridable via `memory.consolidationModel` (global or project).\n */\nexport const DEFAULT_CONSOLIDATION_MODEL = \"claude-sonnet-4-20250514\";\n\n/**\n * Resolve the memory DB path for a given working directory.\n *\n * Priority (highest first):\n * 1. \"pi-memory\".localPath from {cwd}/.pi/settings.json \u2192 {localPath}/memory.db\n * 2. \"pi-total-recall\".localPath cascade \u2192 {localPath}/memory/memory.db\n * 3. Global default: ~/.pi/memory/memory.db (preserves existing behavior)\n */\n/**\n * Emit a warning when a settings block contains keys outside a known\n * schema. Catches silent typos like `LocalPath` vs `localPath` \u2014 an unknown\n * key is usually a misspelled known key that got silently ignored, leaving\n * the user wondering why their config didn't take effect.\n *\n * Logs to stderr (console.error) since this runs inside module-level code\n * at session_start; ctx.ui isn't reliably available yet.\n */\nfunction warnUnknownKeys(block: unknown, blockName: string, knownKeys: readonly string[]): void {\n if (!block || typeof block !== \"object\") return;\n const unknown = Object.keys(block as Record).filter((k) => !knownKeys.includes(k));\n if (unknown.length === 0) return;\n console.error(\n `pi-memory: ignoring unknown key(s) in settings.json \"${blockName}\" block: ${unknown.join(\", \")} (expected: ${knownKeys.join(\", \")})`,\n );\n}\n\nconst PI_MEMORY_KNOWN_KEYS = [\"localPath\", \"lessonInjection\", \"consolidationModel\", \"perTurnInjection\"] as const;\nconst PI_TOTAL_RECALL_KNOWN_KEYS = [\"localPath\"] as const;\n\nexport function resolveDbPath(cwd: string): string {\n // Try reading the local project settings for an explicit localPath override\n try {\n const localSettingsPath = join(cwd, \".pi\", \"settings.json\");\n const raw = readFileSync(localSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw);\n\n // Package-specific override wins.\n const piMemory = settings?.[\"pi-memory\"];\n warnUnknownKeys(piMemory, \"pi-memory\", PI_MEMORY_KNOWN_KEYS);\n if (piMemory && typeof piMemory === \"object\" && typeof piMemory.localPath === \"string\" && piMemory.localPath) {\n // resolve() handles both absolute and relative localPath values:\n // absolute: resolve(cwd, '/abs/path', 'memory.db') \u2192 '/abs/path/memory.db'\n // relative: resolve(cwd, '.pi/local', 'memory.db') \u2192 '{cwd}/.pi/local/memory.db'\n return resolve(cwd, piMemory.localPath, \"memory.db\");\n }\n\n // pi-total-recall cascade.\n const piTotalRecall = settings?.[\"pi-total-recall\"];\n warnUnknownKeys(piTotalRecall, \"pi-total-recall\", PI_TOTAL_RECALL_KNOWN_KEYS);\n if (piTotalRecall && typeof piTotalRecall === \"object\" && typeof piTotalRecall.localPath === \"string\" && piTotalRecall.localPath) {\n return resolve(cwd, piTotalRecall.localPath, \"memory\", \"memory.db\");\n }\n } catch {\n // No local settings or parse error \u2014 use global default\n }\n // Default: global shared memory (preserves existing behavior)\n return DEFAULT_DB_PATH;\n}\n\n/**\n * Apply a single settings-block (the object under `memory` / `pi-memory`) to\n * `config`. Invalid fields are ignored so a malformed value for one key\n * cannot clobber a valid value already set by a higher-priority source.\n */\nfunction mergeMemorySettings(config: InjectorConfig, memorySettings: unknown): void {\n if (!memorySettings || typeof memorySettings !== \"object\") return;\n const m = memorySettings as Record;\n\n if (m.lessonInjection === \"all\" || m.lessonInjection === \"selective\") {\n config.lessonInjection = m.lessonInjection;\n }\n if (typeof m.perTurnInjection === \"boolean\") {\n config.perTurnInjection = m.perTurnInjection;\n }\n if (typeof m.consolidationModel === \"string\" && m.consolidationModel.trim()) {\n config.consolidationModel = m.consolidationModel.trim();\n }\n}\n\n/**\n * Read pi-memory config from settings.json.\n * Looks for a \"memory\" (or project-local \"pi-memory\") key with\n * extension-specific settings.\n *\n * Example settings.json:\n * {\n * \"memory\": {\n * \"perTurnInjection\": true,\n * \"lessonInjection\": \"selective\",\n * \"consolidationModel\": \"openai/gpt-4.1-mini\"\n * }\n * }\n *\n * Exported for tests.\n */\nexport function readSettingsConfig(cwd?: string): InjectorConfig {\n const config: InjectorConfig = {};\n\n // Read global settings\n try {\n const raw = readFileSync(GLOBAL_SETTINGS_PATH, \"utf-8\");\n const settings = JSON.parse(raw);\n mergeMemorySettings(config, settings?.memory);\n } catch {\n // no global settings\n }\n\n // Override with local project settings if available\n if (cwd) {\n try {\n const raw = readFileSync(join(cwd, \".pi\", \"settings.json\"), \"utf-8\");\n const settings = JSON.parse(raw);\n // Accept either `memory` (preferred) or `pi-memory` (package-scoped).\n mergeMemorySettings(config, settings?.memory ?? settings?.[\"pi-memory\"]);\n } catch {\n // no local settings\n }\n }\n\n return config;\n}\n\nexport default function (pi: ExtensionAPI) {\n let store: MemoryStore | null = null;\n let pendingUserMessages: string[] = [];\n let pendingAssistantMessages: string[] = [];\n let sessionCwd: string = \"\";\n let sessionId: string | undefined;\n let cachedCtx: any = null;\n let resolvedDbPath: string = DEFAULT_DB_PATH;\n let injectorConfig: InjectorConfig = readSettingsConfig();\n\n // \u2500\u2500\u2500 Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.on(\"session_start\", async (_event, ctx) => {\n try {\n sessionCwd = ctx.cwd;\n cachedCtx = ctx;\n sessionId = (ctx as any).sessionId ?? (ctx as any).session?.id;\n\n // Resolve per-agent DB path from local settings or cwd\n resolvedDbPath = resolveDbPath(sessionCwd);\n injectorConfig = readSettingsConfig(sessionCwd);\n\n store = new MemoryStore(resolvedDbPath);\n\n // Seed pending messages from existing session history so that\n // /memory-consolidate works even when resuming a session (the\n // historical messages never fire agent_end). See #5.\n pendingUserMessages = [];\n pendingAssistantMessages = [];\n try {\n const branch = ctx.sessionManager.getBranch();\n for (const entry of branch) {\n if (entry.type !== \"message\") continue;\n const msg = (entry as any).message;\n if (!msg) continue;\n if (msg.role === \"user\") {\n const text = extractText(msg.content);\n if (text) pendingUserMessages.push(text);\n } else if (msg.role === \"assistant\") {\n const text = extractText(msg.content);\n if (text) pendingAssistantMessages.push(text);\n }\n }\n } catch {\n // Session may not have entries yet (brand-new session)\n }\n\n const stats = store.stats();\n if (stats.semantic + stats.lessons > 0) {\n ctx.ui.setStatus(\"pi-memory\", `Memory: ${stats.semantic} facts, ${stats.lessons} lessons`);\n // The captured ctx may be stale by the time this fires (resume,\n // /new, /fork, /reload). Stale-ctx access throws synchronously;\n // swallow it \u2014 by then the new session has set its own status.\n setTimeout(() => {\n try { ctx.ui.setStatus(\"pi-memory\", \"\"); } catch { /* ctx stale: harmless */ }\n }, 5000);\n }\n\n // Inject stored memory as a one-shot custom message BEFORE any user\n // message arrives. Only used when `perTurnInjection: false` is explicitly\n // configured (session_start mode, opt-out from adaptive injection).\n //\n // Historical note: v1.0.x mutated event.systemPrompt in before_agent_start.\n // That broke provider prefix caches on every turn boundary (any drift in\n // the system block re-writes the conversation suffix at cacheWrite rates).\n //\n // v1.1.x returned { message } from before_agent_start. That was worse: the\n // custom message landed AFTER the user's question in history, so the model\n // responded to the memory block instead of the user.\n //\n // v1.2.0 injects once at session_start using fallback mode (all facts +\n // lessons, 8KB cap). Correct ordering, stable cache, simpler model.\n //\n // v1.3.x adds `perTurnInjection: true` as an opt-in to restore v1.0.x\n // per-turn selective behavior.\n //\n // v1.4.0 flips the default: per-turn semantic injection via systemPrompt\n // mutation in before_agent_start.\n //\n // v1.5.0 introduces injectionMode: \"context-hook\" as the new default.\n // Memory is injected as an ephemeral message via the context hook instead\n // of mutating systemPrompt. System prompt is now permanently stable,\n // guaranteeing cache hits on the system prompt prefix regardless of topic.\n // The session_start fallback dump is opt-in via `perTurnInjection: false`.\n if (injectorConfig.perTurnInjection === false) {\n try {\n const alreadyInjected = ctx.sessionManager\n .getEntries()\n .some(\n (e: SessionEntry) =>\n e.type === \"custom_message\" && e.customType === \"pi-memory-context\",\n );\n if (!alreadyInjected) {\n const { text, stats: injStats } = await buildContextBlock(\n store,\n sessionCwd,\n undefined, // no prompt \u2192 fallback: dump all relevant memory\n injectorConfig,\n );\n if (text) {\n pi.sendMessage({\n customType: \"pi-memory-context\",\n content: text,\n display: false,\n details: injStats,\n });\n }\n }\n } catch {\n // Injection is nice-to-have; never break startup over it.\n }\n }\n } catch (err: any) {\n ctx.ui.notify(`pi-memory: failed to open store: ${err.message}`, \"warning\");\n }\n });\n\n // ----------------------------------------------------------------\n // Per-turn semantic injection (v1.4.0 default).\n //\n // Runs on every user turn, injecting memories relevant to the current\n // prompt into event.systemPrompt. This is now the DEFAULT behavior;\n // session_start fallback mode requires `perTurnInjection: false`.\n //\n // Cache stability: when the same entries are relevant across consecutive\n // turns (stable topic), the injected text is identical and the provider's\n // prefix cache hits. Entries are sorted deterministically in the injector\n // so identical sets always produce identical text.\n //\n // MUST use systemPrompt (not { message }) \u2014 returning { message } puts the\n // content AFTER the user message and causes the model to respond to the\n // injected memory instead of the user. See v1.1.x postmortem.\n // ----------------------------------------------------------------\n pi.on(\"before_agent_start\", async (event, ctx) => {\n if (!store) return;\n if (injectorConfig.perTurnInjection === false) return;\n\n const { text } = await buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig);\n if (!text) return;\n\n return {\n systemPrompt: `${event.systemPrompt}\\n\\n${text}`,\n };\n });\n\n\n pi.on(\"agent_end\", async (event, _ctx) => {\n // Collect messages for consolidation at shutdown\n for (const msg of event.messages) {\n if (msg.role === \"user\" && \"content\" in msg) {\n const text = extractText(msg.content);\n if (text) {\n pendingUserMessages.push(text);\n if (pendingUserMessages.length > 60) pendingUserMessages.shift();\n }\n } else if (msg.role === \"assistant\" && \"content\" in msg) {\n const text = extractText(msg.content);\n if (text) {\n pendingAssistantMessages.push(text);\n if (pendingAssistantMessages.length > 60) pendingAssistantMessages.shift();\n }\n }\n }\n });\n\n // Consolidate memory when switching sessions (/new, /resume)\n pi.on(\"session_before_switch\", async (_event, ctx) => {\n if (!store) return;\n\n if (pendingUserMessages.length >= 3) {\n ctx.ui.setStatus(\"pi-memory\", \"\uD83E\uDDE0 Consolidating memory...\");\n try {\n await consolidateSession();\n } catch {\n // Best-effort\n } finally {\n // Always clear \u2014 even if consolidateSession threw synchronously or\n // ctx went stale. Stuck indicator otherwise pins for the rest of\n // the session. See samfoy/pi-total-recall#5.\n try { ctx.ui.setStatus(\"pi-memory\", \"\"); } catch { /* ctx stale: harmless */ }\n }\n }\n\n // Reset for the next session\n pendingUserMessages = [];\n pendingAssistantMessages = [];\n });\n\n pi.on(\"session_shutdown\", async () => {\n if (!store) return;\n\n // Immediate visual feedback \u2014 user sees this as soon as C-c C-c fires\n if (cachedCtx) {\n cachedCtx.ui.setStatus(\"pi-memory\", \"\uD83E\uDDE0 Consolidating memory...\");\n }\n\n // Consolidate if we have enough conversation\n if (pendingUserMessages.length >= 3) {\n try {\n await consolidateSession();\n } catch {\n // Best-effort \u2014 don't crash on shutdown\n }\n }\n\n store.close();\n store = null;\n });\n\n // \u2500\u2500\u2500 Consolidation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async function consolidateSession(): Promise {\n if (!store) return;\n\n const input: ConsolidationInput = {\n userMessages: pendingUserMessages,\n assistantMessages: pendingAssistantMessages,\n cwd: sessionCwd,\n sessionId,\n };\n\n const currentFacts = store.listSemantic(undefined, 200).map(f => ({ key: f.key, value: f.value }));\n const currentLessons = store.listLessons(undefined, 100).map(l => ({ rule: l.rule, category: l.category }));\n const prompt = buildConsolidationPrompt(input, currentFacts, currentLessons);\n\n // Use pi's exec to call the LLM via a lightweight pi session.\n // Use a fast model to avoid blocking shutdown for too long.\n //\n // Defence in depth: pi.exec has a 45s timeout, but we also wrap the\n // whole call in a hard 60s backstop. If pi.exec's timeout ever fails\n // to kill the child (e.g. stuck in syscall), the Promise.race below\n // still rejects and lets the caller clear its status indicator.\n const EXEC_TIMEOUT_MS = 45_000;\n const HARD_TIMEOUT_MS = 60_000;\n let backstopHandle: ReturnType | undefined;\n try {\n const execPromise = pi.exec(\"pi\", [\n \"-p\", prompt,\n \"--print\",\n \"--no-extensions\",\n \"--no-tools\",\n \"--no-session\",\n \"--model\", injectorConfig.consolidationModel ?? DEFAULT_CONSOLIDATION_MODEL,\n ], {\n timeout: EXEC_TIMEOUT_MS,\n cwd: sessionCwd,\n });\n\n const result = await Promise.race([\n execPromise,\n new Promise((_, reject) => {\n backstopHandle = setTimeout(\n () => reject(new Error(\"consolidation backstop timeout\")),\n HARD_TIMEOUT_MS,\n );\n }),\n ]);\n\n if (result.code === 0 && result.stdout) {\n const extracted = parseConsolidationResponse(result.stdout);\n const slug = sessionCwd ? projectSlug(sessionCwd) : undefined;\n const applied = applyExtracted(store!, extracted, `session:${sessionId ?? \"unknown\"}`, slug || undefined);\n if (applied.semantic + applied.lessons > 0) {\n // Log but don't notify \u2014 we're shutting down\n console.error(`pi-memory: consolidated ${applied.semantic} facts, ${applied.lessons} lessons`);\n }\n }\n } catch {\n // Timeout or exec failure \u2014 skip consolidation this session\n } finally {\n if (backstopHandle) clearTimeout(backstopHandle);\n }\n }\n\n // \u2500\u2500\u2500 Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.registerTool({\n name: \"memory_search\",\n label: \"Memory Search\",\n description: \"Search persistent memory for facts, preferences, and project patterns the user has established across sessions.\",\n parameters: Type.Object({\n query: Type.String({ description: \"Search query\" }),\n limit: Type.Optional(Type.Number({ description: \"Max results (default 10)\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const results = store.searchSemantic(params.query, params.limit ?? 10);\n if (results.length === 0) {\n return ok(\"No matching memories found.\");\n }\n\n const text = results.map(r =>\n `${r.key}: ${r.value} (confidence: ${r.confidence}, source: ${r.source})`\n ).join(\"\\n\");\n\n return ok(text);\n },\n });\n\n pi.registerTool({\n name: \"memory_remember\",\n label: \"Memory Remember\",\n description: \"Store a fact, preference, or lesson in persistent memory. Use dotted keys like pref.editor, project.rosie.lang, tool.sed.usage. For corrections, use type='lesson'.\",\n parameters: Type.Object({\n type: Type.String({ description: \"'fact' for key-value, 'lesson' for a correction\" }),\n key: Type.Optional(Type.String({ description: \"Dotted key for facts (e.g. pref.commit_style)\" })),\n value: Type.Optional(Type.String({ description: \"Value for facts\" })),\n rule: Type.Optional(Type.String({ description: \"Rule text for lessons\" })),\n category: Type.Optional(Type.String({ description: \"Category for lessons (default: general)\" })),\n negative: Type.Optional(Type.Boolean({ description: \"True if this is something to AVOID\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n // Defensively unwrap double-quoted string args from misbehaving model runners.\n params = {\n ...params,\n type: stripQuotes(params.type),\n key: stripQuotes(params.key),\n value: stripQuotes(params.value),\n rule: stripQuotes(params.rule),\n category: stripQuotes(params.category),\n };\n\n if (params.type !== \"fact\" && params.type !== \"lesson\") {\n return ok(`Invalid type: ${params.type}. Must be 'fact' or 'lesson'.`);\n }\n\n if (params.type === \"fact\") {\n if (!params.key || !params.value) {\n return ok(\"Both key and value required for facts\");\n }\n store.setSemantic(params.key, params.value, 0.95, \"user\");\n // Fire-and-forget: compute and store embedding for the new/updated entry\n // so it's available for semantic search in future sessions.\n const _key = params.key as string;\n const _val = params.value as string;\n embed(`${_key.split(\".\").slice(1).join(\" \")} ${_val}`)\n .then(vec => { if (vec) store!.setEmbedding(_key, vec); })\n .catch(() => {});\n return ok(`Remembered: ${params.key} = ${params.value}`);\n }\n\n if (params.type === \"lesson\") {\n if (!params.rule) {\n return ok(\"Rule text required for lessons\");\n }\n const result = store.addLesson(params.rule, params.category ?? \"general\", \"user\", params.negative ?? false);\n if (result.success) {\n return ok(`Lesson learned: ${params.rule}`);\n }\n return ok(`Already known (${result.reason}): ${params.rule}`);\n }\n\n return ok(\"Unknown type\");\n },\n });\n\n pi.registerTool({\n name: \"memory_forget\",\n label: \"Memory Forget\",\n description: \"Remove a fact or lesson from persistent memory.\",\n parameters: Type.Object({\n type: Type.String(),\n key: Type.Optional(Type.String({ description: \"Key for facts\" })),\n id: Type.Optional(Type.String({ description: \"ID for lessons\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n params = {\n ...params,\n type: stripQuotes(params.type),\n key: stripQuotes(params.key),\n id: stripQuotes(params.id),\n };\n\n if (params.type !== \"fact\" && params.type !== \"lesson\") {\n return ok(`Invalid type: ${params.type}. Must be 'fact' or 'lesson'.`);\n }\n\n if (params.type === \"fact\" && params.key) {\n const deleted = store.deleteSemantic(params.key);\n return ok(deleted ? `Forgot: ${params.key}` : `Not found: ${params.key}`);\n }\n\n if (params.type === \"lesson\" && params.id) {\n const deleted = store.deleteLesson(params.id);\n return ok(deleted ? `Forgot lesson ${params.id}` : `Not found: ${params.id}`);\n }\n\n return ok(\"Provide key (for facts) or id (for lessons)\");\n },\n });\n\n pi.registerTool({\n name: \"memory_lessons\",\n label: \"Memory Lessons\",\n description: \"List learned corrections and lessons from past sessions.\",\n parameters: Type.Object({\n category: Type.Optional(Type.String({ description: \"Filter by category\" })),\n limit: Type.Optional(Type.Number({ description: \"Max results (default 50)\" })),\n }) as any,\n async execute(_id, params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const lessons = store.listLessons(params.category, params.limit ?? 50);\n if (lessons.length === 0) {\n return ok(\"No lessons learned yet.\");\n }\n\n const text = lessons.map(l =>\n `${l.negative ? \"\u274C\" : \"\u2705\"} [${l.category}] ${l.rule} (id: ${l.id.slice(0, 8)})`\n ).join(\"\\n\");\n\n return ok(text);\n },\n });\n\n pi.registerTool({\n name: \"memory_stats\",\n label: \"Memory Stats\",\n description: \"Show memory statistics \u2014 how many facts, lessons, and events are stored.\",\n parameters: Type.Object({}) as any,\n async execute(_id, _params, _signal, _update, _ctx) {\n if (!store) return ok(\"Memory store not initialized\");\n\n const stats = store.stats();\n const text = `Memory: ${stats.semantic} semantic facts, ${stats.lessons} active lessons, ${stats.events} events logged\\nDB: ${resolvedDbPath}`;\n return ok(text);\n },\n });\n\n // \u2500\u2500\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n pi.registerCommand(\"memory-consolidate\", {\n description: \"Manually trigger memory consolidation for the current session\",\n async handler(_args, ctx) {\n if (!store) {\n ctx.ui.notify(\"Memory store not initialized\", \"warning\");\n return;\n }\n\n if (pendingUserMessages.length < 2) {\n ctx.ui.notify(\"Not enough conversation to consolidate (need at least 2 user messages)\", \"warning\");\n return;\n }\n\n ctx.ui.notify(\"Consolidating session memory...\", \"info\");\n try {\n await consolidateSession();\n const stats = store.stats();\n ctx.ui.notify(`Memory updated: ${stats.semantic} facts, ${stats.lessons} lessons`, \"info\");\n } catch (err: any) {\n ctx.ui.notify(`Consolidation failed: ${err.message}`, \"error\");\n }\n },\n });\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction extractText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .filter((c: any) => c.type === \"text\" && typeof c.text === \"string\")\n .map((c: any) => c.text)\n .join(\"\\n\");\n }\n return \"\";\n}\n", "/**\n * SQLite-backed memory store using Node's built-in node:sqlite.\n * Three tables:\n * - semantic: key-value facts (preferences, project patterns, corrections)\n * - lessons: learned corrections with dedup\n * - events: audit log of all memory operations\n */\nimport { DatabaseSync } from \"node:sqlite\";\nimport { mkdirSync, existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SemanticEntry {\n key: string;\n value: string;\n confidence: number;\n source: \"user\" | \"consolidation\" | \"correction\";\n created_at: string;\n updated_at: string;\n last_accessed?: string;\n}\n\nexport interface LessonEntry {\n id: string;\n rule: string;\n category: string;\n source: string;\n negative: boolean;\n created_at: string;\n /** Project slug this lesson was extracted from, or null for user-authored / global lessons */\n project: string | null;\n}\n\nexport interface MemoryEvent {\n id: number;\n event_type: string;\n memory_type: string;\n memory_key: string;\n details: string;\n created_at: string;\n}\n\n// \u2500\u2500\u2500 Store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MemoryStore {\n private db: DatabaseSync;\n private writeLock: Promise = Promise.resolve();\n private hasFTS5: boolean = false;\n\n constructor(dbPath: string) {\n const dir = dirname(dbPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n this.db = new DatabaseSync(dbPath);\n this.db.exec(\"PRAGMA journal_mode = WAL\");\n this.db.exec(\"PRAGMA busy_timeout = 5000\");\n this.db.exec(\"PRAGMA foreign_keys = ON\");\n this.migrate();\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS semantic (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.8,\n source TEXT NOT NULL DEFAULT 'consolidation',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS lessons (\n id TEXT PRIMARY KEY,\n rule TEXT NOT NULL,\n category TEXT NOT NULL DEFAULT 'general',\n source TEXT NOT NULL DEFAULT 'consolidation',\n negative INTEGER NOT NULL DEFAULT 0,\n is_deleted INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n event_type TEXT NOT NULL,\n memory_type TEXT NOT NULL,\n memory_key TEXT NOT NULL,\n details TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n `);\n\n // Migration: add last_accessed column if missing\n try {\n this.db.exec(`ALTER TABLE semantic ADD COLUMN last_accessed TEXT`);\n } catch {\n // Column already exists \u2014 ignore\n }\n // Migration: add project column to lessons if missing\n try {\n this.db.exec(`ALTER TABLE lessons ADD COLUMN project TEXT`);\n } catch {\n // Column already exists \u2014 ignore\n }\n // Migration: add embedding column for semantic vector search\n try {\n this.db.exec(`ALTER TABLE semantic ADD COLUMN embedding BLOB`);\n } catch {\n // Column already exists \u2014 ignore\n }\n\n // FTS5 virtual tables for semantic + lesson search (optional \u2014 node:sqlite may lack FTS5)\n try {\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS semantic_fts USING fts5(key, value, content='semantic', content_rowid='rowid');\n\n CREATE TRIGGER IF NOT EXISTS semantic_ai AFTER INSERT ON semantic BEGIN\n INSERT INTO semantic_fts(rowid, key, value) VALUES (new.rowid, new.key, new.value);\n END;\n CREATE TRIGGER IF NOT EXISTS semantic_ad AFTER DELETE ON semantic BEGIN\n INSERT INTO semantic_fts(semantic_fts, rowid, key, value) VALUES('delete', old.rowid, old.key, old.value);\n END;\n CREATE TRIGGER IF NOT EXISTS semantic_au AFTER UPDATE ON semantic BEGIN\n INSERT INTO semantic_fts(semantic_fts, rowid, key, value) VALUES('delete', old.rowid, old.key, old.value);\n INSERT INTO semantic_fts(rowid, key, value) VALUES (new.rowid, new.key, new.value);\n END;\n `);\n\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS lessons_fts USING fts5(rule, category, content='lessons', content_rowid='rowid');\n\n CREATE TRIGGER IF NOT EXISTS lessons_fts_ai AFTER INSERT ON lessons BEGIN\n INSERT INTO lessons_fts(rowid, rule, category) VALUES (new.rowid, new.rule, new.category);\n END;\n CREATE TRIGGER IF NOT EXISTS lessons_fts_ad AFTER DELETE ON lessons BEGIN\n INSERT INTO lessons_fts(lessons_fts, rowid, rule, category) VALUES('delete', old.rowid, old.rule, old.category);\n END;\n CREATE TRIGGER IF NOT EXISTS lessons_fts_au AFTER UPDATE ON lessons BEGIN\n INSERT INTO lessons_fts(lessons_fts, rowid, rule, category) VALUES('delete', old.rowid, old.rule, old.category);\n INSERT INTO lessons_fts(rowid, rule, category) VALUES (new.rowid, new.rule, new.category);\n END;\n `);\n\n // Rebuild FTS indexes from existing data (idempotent)\n this.db.exec(`INSERT INTO semantic_fts(semantic_fts) VALUES('rebuild')`);\n this.db.exec(`INSERT INTO lessons_fts(lessons_fts) VALUES('rebuild')`);\n this.hasFTS5 = true;\n } catch {\n // FTS5 not available (node:sqlite compiled without SQLITE_ENABLE_FTS5).\n // Search will use substring fallback \u2014 fine for typical memory store sizes.\n this.hasFTS5 = false;\n }\n }\n\n /**\n * Serialize async callers so concurrent read-modify-write cycles\n * (e.g. two consolidation calls) don't clobber each other.\n */\n private withLock(fn: () => T): T {\n // DatabaseSync is synchronous, so we just need to ensure\n // transactional integrity. Wrap in a SQLite transaction.\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = fn();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n throw err;\n }\n }\n\n // \u2500\u2500\u2500 Semantic \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getSemantic(key: string): SemanticEntry | undefined {\n const normalized = key.toLowerCase();\n return this.db.prepare(\"SELECT * FROM semantic WHERE key = ?\").get(normalized) as unknown as SemanticEntry | undefined;\n }\n\n setSemantic(key: string, value: string, confidence: number = 0.8, source: SemanticEntry[\"source\"] = \"consolidation\"): void {\n const normalized = key.toLowerCase();\n this.withLock(() => {\n const existing = this.db.prepare(\"SELECT * FROM semantic WHERE key = ?\").get(normalized) as unknown as SemanticEntry | undefined;\n if (existing && existing.confidence > confidence) return; // higher confidence wins\n\n this.db.prepare(`\n INSERT INTO semantic (key, value, confidence, source, updated_at)\n VALUES (?, ?, ?, ?, datetime('now'))\n ON CONFLICT(key) DO UPDATE SET\n value = excluded.value,\n confidence = excluded.confidence,\n source = excluded.source,\n updated_at = datetime('now')\n `).run(normalized, value, confidence, source);\n\n this.logEvent(existing ? \"update\" : \"create\", \"semantic\", normalized);\n });\n }\n\n deleteSemantic(key: string): boolean {\n const normalized = key.toLowerCase();\n return this.withLock(() => {\n const result = this.db.prepare(\"DELETE FROM semantic WHERE key = ?\").run(normalized);\n if (result.changes > 0) this.logEvent(\"delete\", \"semantic\", normalized);\n return result.changes > 0;\n });\n }\n\n /**\n * Store a pre-computed embedding for a key.\n * Converts Float32Array \u2192 Buffer for SQLite BLOB storage.\n */\n setEmbedding(key: string, embedding: Float32Array): void {\n const normalized = key.toLowerCase();\n const blob = Buffer.from(new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength));\n this.db.prepare(\"UPDATE semantic SET embedding = ? WHERE key = ?\").run(blob, normalized);\n }\n\n /**\n * Return all semantic keys with their raw embedding BLOBs.\n * Used for in-memory cosine similarity at query time.\n * Entries without an embedding have embedding = null.\n */\n getAllEmbeddings(): Array<{ key: string; embedding: Buffer | null }> {\n return this.db\n .prepare(\"SELECT key, embedding FROM semantic ORDER BY updated_at DESC\")\n .all() as unknown as Array<{ key: string; embedding: Buffer | null }>;\n }\n\n listSemantic(prefix?: string, limit: number = 100): SemanticEntry[] {\n if (prefix) {\n return this.db.prepare(\"SELECT * FROM semantic WHERE key LIKE ? ORDER BY updated_at DESC LIMIT ?\")\n .all(`${prefix}%`, limit) as unknown as SemanticEntry[];\n }\n return this.db.prepare(\"SELECT * FROM semantic ORDER BY updated_at DESC LIMIT ?\")\n .all(limit) as unknown as SemanticEntry[];\n }\n\n searchSemantic(query: string, limit: number = 10): SemanticEntry[] {\n const terms = query.trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n if (!this.hasFTS5) return this._searchSemanticFallback(query, limit);\n\n // Build FTS5 query \u2014 quote each term for safety\n const ftsQuery = terms.map(t => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n\n try {\n const rows = this.db.prepare(`\n SELECT s.key, s.value, s.confidence, s.source, s.created_at, s.updated_at, s.last_accessed\n FROM semantic s\n JOIN semantic_fts fts ON s.rowid = fts.rowid\n WHERE semantic_fts MATCH ?\n ORDER BY bm25(semantic_fts)\n LIMIT ?\n `).all(ftsQuery, limit) as unknown as SemanticEntry[];\n\n return rows;\n } catch {\n // FTS query failed \u2014 fall back to substring matching\n return this._searchSemanticFallback(query, limit);\n }\n }\n\n private _searchSemanticFallback(query: string, limit: number): SemanticEntry[] {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n const all = this.db.prepare(\"SELECT * FROM semantic\").all() as unknown as SemanticEntry[];\n return all\n .map(entry => {\n const text = `${entry.key} ${entry.value}`.toLowerCase();\n const matches = terms.filter(t => text.includes(t)).length;\n return { entry, score: matches / terms.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => entry);\n }\n\n touchAccessed(keys: string[]): void {\n if (keys.length === 0) return;\n const stmt = this.db.prepare(\"UPDATE semantic SET last_accessed = datetime('now') WHERE key = ?\");\n for (const key of keys) {\n stmt.run(key.toLowerCase());\n }\n }\n\n // \u2500\u2500\u2500 Lessons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n addLesson(rule: string, category: string = \"general\", source: string = \"consolidation\", negative: boolean = false, project?: string): { success: boolean; id?: string; reason?: string } {\n const trimmed = rule.trim();\n if (!trimmed) return { success: false, reason: \"empty rule\" };\n\n const normalizedCategory = category.trim().toLowerCase() || \"general\";\n\n return this.withLock(() => {\n // Exact-match dedup (case-insensitive)\n const existing = this.db.prepare(\n \"SELECT id FROM lessons WHERE LOWER(TRIM(rule)) = LOWER(?) AND is_deleted = 0\"\n ).get(trimmed.toLowerCase()) as { id: string } | undefined;\n if (existing) return { success: false as const, reason: \"duplicate\" as const, id: existing.id };\n\n // Jaccard dedup\n const allRules = this.db.prepare(\"SELECT id, rule FROM lessons WHERE is_deleted = 0\").all() as { id: string; rule: string }[];\n for (const r of allRules) {\n if (jaccard(trimmed, r.rule) >= 0.7) {\n return { success: false as const, reason: \"similar\" as const, id: r.id };\n }\n }\n\n const id = crypto.randomUUID();\n this.db.prepare(\n \"INSERT INTO lessons (id, rule, category, source, negative, project) VALUES (?, ?, ?, ?, ?, ?)\"\n ).run(id, trimmed, normalizedCategory, source, negative ? 1 : 0, project ?? null);\n\n this.logEvent(\"create\", \"lesson\", id, trimmed.slice(0, 100));\n return { success: true as const, id };\n });\n }\n\n getLesson(id: string): LessonEntry | undefined {\n const row = this.db.prepare(\"SELECT * FROM lessons WHERE id = ? AND is_deleted = 0\").get(id) as any;\n if (!row) return undefined;\n return { ...row, negative: !!row.negative };\n }\n\n /**\n * List lessons, optionally filtered by category and/or project.\n *\n * Project filtering:\n * - If `project` is provided, returns lessons where `project = slug` OR `project IS NULL`\n * (NULL = user-authored or pre-migration lessons, treated as global).\n * - If `project` is not provided, returns all lessons (no project filter).\n */\n listLessons(category?: string, limit: number = 50, project?: string): LessonEntry[] {\n let rows: any[];\n if (category && project) {\n const normalizedCategory = category.trim().toLowerCase();\n rows = this.db.prepare(\n \"SELECT * FROM lessons WHERE category = ? AND (project = ? OR project IS NULL) AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?\"\n ).all(normalizedCategory, project, limit);\n } else if (category) {\n const normalizedCategory = category.trim().toLowerCase();\n rows = this.db.prepare(\"SELECT * FROM lessons WHERE category = ? AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?\")\n .all(normalizedCategory, limit);\n } else if (project) {\n rows = this.db.prepare(\n \"SELECT * FROM lessons WHERE (project = ? OR project IS NULL) AND is_deleted = 0 ORDER BY created_at DESC LIMIT ?\"\n ).all(project, limit);\n } else {\n rows = this.db.prepare(\"SELECT * FROM lessons WHERE is_deleted = 0 ORDER BY created_at DESC LIMIT ?\")\n .all(limit);\n }\n return rows.map(r => ({ ...r, negative: !!r.negative, project: r.project ?? null }));\n }\n\n /**\n * Search lessons by relevance to a query. Uses FTS5 when available,\n * falls back to substring matching. Returns lessons ranked by relevance.\n */\n searchLessons(query: string, limit: number = 20): LessonEntry[] {\n const terms = query.trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n if (!this.hasFTS5) return this._searchLessonsFallback(query, limit);\n\n const ftsQuery = terms.map(t => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n\n try {\n const rows = this.db.prepare(`\n SELECT l.id, l.rule, l.category, l.source, l.negative, l.created_at, l.project\n FROM lessons l\n JOIN lessons_fts fts ON l.rowid = fts.rowid\n WHERE lessons_fts MATCH ? AND l.is_deleted = 0\n ORDER BY bm25(lessons_fts)\n LIMIT ?\n `).all(ftsQuery, limit) as any[];\n\n return rows.map(r => ({ ...r, negative: !!r.negative, project: r.project ?? null }));\n } catch {\n return this._searchLessonsFallback(query, limit);\n }\n }\n\n private _searchLessonsFallback(query: string, limit: number): LessonEntry[] {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return [];\n\n const all = this.db.prepare(\"SELECT * FROM lessons WHERE is_deleted = 0\").all() as any[];\n return all\n .map(entry => {\n const text = `${entry.rule} ${entry.category}`.toLowerCase();\n const matches = terms.filter(t => text.includes(t)).length;\n return { entry: { ...entry, negative: !!entry.negative, project: entry.project ?? null } as LessonEntry, score: matches / terms.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => entry);\n }\n\n deleteLesson(id: string): boolean {\n return this.withLock(() => {\n // Support both full UUIDs and prefix matches (e.g. first 8 chars)\n let result = this.db.prepare(\"UPDATE lessons SET is_deleted = 1 WHERE id = ? AND is_deleted = 0\").run(id);\n if (result.changes === 0 && id.length < 36) {\n // Try prefix match \u2014 ensure it's unambiguous\n const matches = this.db.prepare(\"SELECT id FROM lessons WHERE id LIKE ? AND is_deleted = 0\").all(`${id}%`) as { id: string }[];\n if (matches.length === 1) {\n result = this.db.prepare(\"UPDATE lessons SET is_deleted = 1 WHERE id = ? AND is_deleted = 0\").run(matches[0].id);\n if (result.changes > 0) this.logEvent(\"delete\", \"lesson\", matches[0].id);\n return true;\n }\n }\n if (result.changes > 0) this.logEvent(\"delete\", \"lesson\", id);\n return result.changes > 0;\n });\n }\n\n // \u2500\u2500\u2500 Events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private logEvent(eventType: string, memoryType: string, key: string, details: string = \"\"): void {\n this.db.prepare(\n \"INSERT INTO events (event_type, memory_type, memory_key, details) VALUES (?, ?, ?, ?)\"\n ).run(eventType, memoryType, key, details);\n }\n\n listEvents(limit: number = 50): MemoryEvent[] {\n return this.db.prepare(\"SELECT * FROM events ORDER BY id DESC LIMIT ?\").all(limit) as unknown as MemoryEvent[];\n }\n\n // \u2500\u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n stats(): { semantic: number; lessons: number; events: number } {\n const semantic = (this.db.prepare(\"SELECT COUNT(*) as c FROM semantic\").get() as any).c;\n const lessons = (this.db.prepare(\"SELECT COUNT(*) as c FROM lessons WHERE is_deleted = 0\").get() as any).c;\n const events = (this.db.prepare(\"SELECT COUNT(*) as c FROM events\").get() as any).c;\n return { semantic, lessons, events };\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction jaccard(a: string, b: string): number {\n const setA = new Set(a.toLowerCase().split(/\\s+/).filter(Boolean));\n const setB = new Set(b.toLowerCase().split(/\\s+/).filter(Boolean));\n if (setA.size === 0 && setB.size === 0) return 1;\n const intersection = new Set([...setA].filter(x => setB.has(x)));\n const union = new Set([...setA, ...setB]);\n return intersection.size / union.size;\n}\n", "/**\n * Lazy embedding pipeline using @xenova/transformers (optional dependency).\n * Gracefully degrades to FTS-only when the package is unavailable or the\n * model fails to load.\n *\n * Model: Xenova/all-MiniLM-L6-v2 (quantized int8, ~6 MB download, 384 dims).\n * Cached in ~/.cache/huggingface/hub/ after first download.\n */\n\nconst MODEL = \"Xenova/all-MiniLM-L6-v2\";\nconst LOAD_TIMEOUT_MS = 30_000;\nconst INFER_TIMEOUT_MS = 5_000;\nconst TEXT_CHAR_LIMIT = 512;\n\nlet _pipe: unknown = null;\nlet _failed = false;\n\nasync function getPipe(): Promise {\n if (_failed) return null;\n if (_pipe) return _pipe;\n try {\n // Dynamic import \u2014 @xenova/transformers is optional; catch if absent.\n // String variable prevents TypeScript from resolving the type (it's optional).\n const pkg = \"@xenova/transformers\";\n const mod = await import(pkg).catch(() => null) as any;\n if (!mod) {\n console.error(\"pi-memory: @xenova/transformers not installed, semantic search disabled\");\n _failed = true;\n return null;\n }\n const { pipeline, env } = mod;\n env.allowRemoteModels = true;\n env.useBrowserCache = false;\n _pipe = await withTimeout(\n pipeline(\"feature-extraction\", MODEL, { quantized: true }),\n LOAD_TIMEOUT_MS,\n \"model load\",\n );\n return _pipe;\n } catch (err: unknown) {\n console.error(`pi-memory: embedder unavailable (${(err as any)?.message ?? err}), using FTS-only`);\n _failed = true;\n return null;\n }\n}\n\n/** Compute a normalized embedding for text. Returns null on any failure. */\nexport async function embed(text: string): Promise {\n const pipe = await getPipe();\n if (!pipe) return null;\n try {\n const out = await withTimeout(\n (pipe as any)(text.slice(0, TEXT_CHAR_LIMIT), { pooling: \"mean\", normalize: true }),\n INFER_TIMEOUT_MS,\n \"inference\",\n );\n return new Float32Array((out as any).data);\n } catch {\n return null;\n }\n}\n\n/**\n * Cosine similarity of two normalized unit vectors (dot product).\n * Both vectors must have been produced with normalize:true.\n */\nexport function similarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n const len = Math.min(a.length, b.length);\n for (let i = 0; i < len; i++) dot += a[i] * b[i];\n return dot;\n}\n\n/**\n * Serialize a Float32Array to a Buffer for SQLite BLOB storage.\n * Creates a copy to avoid shared-buffer aliasing issues.\n */\nexport function toBlob(v: Float32Array): Buffer {\n return Buffer.from(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));\n}\n\n/**\n * Deserialize a SQLite BLOB back to Float32Array.\n * Returns null for null/undefined input.\n * Uses Uint8Array.from to produce a fresh, owned ArrayBuffer \u2014 safe when\n * node:sqlite returns a Buffer whose .buffer is a shared backing store.\n */\nexport function fromBlob(b: Buffer | null | undefined): Float32Array | null {\n if (!b) return null;\n const raw = Uint8Array.from(b); // copy \u2014 handles non-zero byteOffset\n return new Float32Array(raw.buffer);\n}\n\nfunction withTimeout(p: Promise, ms: number, label: string): Promise {\n return Promise.race([\n p,\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms),\n ),\n ]);\n}\n", "/**\n * Builds a context block from memory for injection into the system prompt.\n *\n * Two modes:\n * - Selective (prompt provided): search semantic memory for entries relevant\n * to the user's current prompt, plus always-inject lessons.\n * - Fallback (no prompt): dump top entries by prefix (old behavior).\n */\nimport type { MemoryStore, SemanticEntry, LessonEntry } from \"./store.js\";\nimport { embed, similarity, fromBlob } from \"./embedder.js\";\nimport os from \"node:os\";\n\nconst MAX_CONTEXT_CHARS = 8000;\nconst SEARCH_LIMIT = 15;\nconst LESSON_SEARCH_LIMIT = 15;\n\nexport interface ContextBlock {\n text: string;\n stats: { semantic: number; lessons: number };\n}\n\n/**\n * Configuration for lesson injection behavior.\n * - \"all\": inject all lessons (original behavior, default)\n * - \"selective\": use semantic search to pick relevant lessons + category filtering\n */\nexport type LessonInjectionMode = \"all\" | \"selective\";\n\nexport interface InjectorConfig {\n lessonInjection?: LessonInjectionMode;\n /**\n * Opt-in: restore per-user-message selective injection.\n *\n * When false (default), pi-memory injects a one-shot fallback block at\n * session_start (correct message ordering, stable prefix cache).\n *\n * When true, the session_start dump is skipped and each turn runs a\n * semantic search against the user's current prompt. The injection\n * strategy is then controlled by `injectionMode`.\n *\n * Correctness is preserved either way: systemPrompt is a separate field\n * from the messages list, so the user's question remains the last\n * user-role message and the model responds to it.\n */\n perTurnInjection?: boolean;\n /**\n * Controls how per-turn memory is spliced into the LLM context.\n * Only relevant when perTurnInjection is not false.\n *\n * \"context-hook\" (default): memory is injected as an ephemeral custom\n * message immediately before the latest user message, via the\n * pi.on(\"context\") hook. The system prompt is never modified \u2014 it\n * caches unconditionally. A memory content change only causes a cache\n * miss at the injection point and forward (not from the system prompt\n * root). Injected messages are NOT persisted to session history or\n * fed back into consolidation.\n *\n * \"system-prompt\" (legacy v1.4.0 behavior): memory is appended to\n * event.systemPrompt in before_agent_start. Cache-stable when memory\n * content is unchanged, but a topic shift causes a cache miss that\n * cascades from the system prompt root through all downstream messages.\n */\n injectionMode?: \"system-prompt\" | \"context-hook\";\n /**\n * Model string passed to `pi --model` for session-end consolidation.\n * When omitted, the built-in default is used. Useful for users on\n * non-Anthropic providers (OpenAI/Codex/OpenRouter/Ollama/local),\n * or for picking a cheaper/faster model for background extraction.\n *\n * Invalid model strings will cause the consolidation sub-process to\n * fail \u2014 the existing try/catch swallows that silently, so the worst\n * case is that consolidation skips this session.\n */\n consolidationModel?: string;\n}\n\n/**\n * Build context block. When `prompt` is provided, uses selective injection\n * (search-based). Otherwise falls back to prefix-based dump.\n */\nexport async function buildContextBlock(store: MemoryStore, cwd?: string, prompt?: string, config?: InjectorConfig): Promise {\n if (prompt?.trim()) {\n return buildSelectiveBlock(store, prompt, cwd, config);\n }\n return buildFallbackBlock(store, cwd);\n}\n\n// \u2500\u2500\u2500 Selective injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function buildSelectiveBlock(store: MemoryStore, prompt: string, cwd?: string, config?: InjectorConfig): Promise {\n const sections: string[] = [];\n let semanticCount = 0;\n let lessonCount = 0;\n const mode = config?.lessonInjection ?? \"all\";\n\n // Search semantic memory using the user's prompt\n const results = store.searchSemantic(prompt, SEARCH_LIMIT);\n\n // Also search with project slug if we have a cwd, to pull in project context\n const slug = cwd ? projectSlug(cwd) : \"\";\n if (slug) {\n const projectResults = store.searchSemantic(slug, 5);\n // Merge, dedup by key\n const seen = new Set(results.map(r => r.key));\n for (const r of projectResults) {\n if (!seen.has(r.key)) {\n results.push(r);\n seen.add(r.key);\n }\n }\n }\n\n // Filter out project.* facts that belong to other projects.\n // FTS can match project facts from unrelated projects when their text\n // coincidentally matches the prompt (e.g. a prompt about \"prisma\" pulling\n // in rise.testing.fabricca). Keep only facts whose project segment\n // matches the current slug; non-project facts (pref.*, tool.*, user.*)\n // are always kept.\n const filteredResults = slug\n ? results.filter(r => {\n if (!r.key.startsWith(\"project.\")) return true;\n const parts = r.key.split(\".\");\n return parts.length >= 2 && parts[1] === slug;\n })\n : results;\n\n // Shared dedup set \u2014 used by both semantic search and prefix expansion below.\n const seen = new Set(filteredResults.map(r => r.key));\n\n // \u2500\u2500 Semantic similarity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Embed the prompt and compare against stored embeddings to surface entries\n // that are conceptually related but share no keywords with the query.\n // Example: \"I'm hungry\" \u2192 finds user.health.diet via vector proximity.\n //\n // Gracefully degrades: if @xenova/transformers is unavailable or the model\n // hasn't been downloaded yet, embed() returns null and we skip this step.\n const SEMANTIC_THRESHOLD = 0.25;\n const SEMANTIC_LIMIT = 8;\n const allEmbs = store.getAllEmbeddings();\n const promptVec = await embed(prompt);\n const semanticKeys = new Set(); // track entries surfaced by embedding search\n\n if (promptVec) {\n const semanticHits = allEmbs\n .flatMap(({ key, embedding }) => {\n const vec = fromBlob(embedding);\n if (!vec) return [];\n const score = similarity(promptVec, vec);\n return score >= SEMANTIC_THRESHOLD ? [{ key, score }] : [];\n })\n .sort((a, b) => b.score - a.score)\n .slice(0, SEMANTIC_LIMIT);\n\n for (const { key } of semanticHits) {\n // Always mark as a semantic hit for priority sorting, even if FTS already\n // added this key \u2014 that way the reorder step promotes it to the front.\n semanticKeys.add(key);\n if (!seen.has(key)) {\n const entry = store.getSemantic(key);\n if (entry) {\n filteredResults.push(entry);\n seen.add(key);\n }\n }\n }\n\n // Background: compute and store embeddings for entries that lack them.\n // Fire-and-forget \u2014 does not block injection.\n backfillEmbeddings(store, allEmbs.filter(r => !r.embedding)).catch(() => {});\n }\n\n // \u2500\u2500 Prefix co-expansion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // When any key in a sibling group appears in results (FTS or semantic),\n // pull siblings under the same prefix. Semantic hits get full expansion (20);\n // FTS hits are capped at 5 to prevent noisy matches from flooding context.\n const expandedPrefixes = new Set();\n for (const r of [...filteredResults]) { // snapshot \u2014 we push into filteredResults below\n const prefix = keyDomainPrefix(r.key);\n if (!prefix || expandedPrefixes.has(prefix)) continue;\n expandedPrefixes.add(prefix);\n const limit = semanticKeys.has(r.key) ? 20 : 5;\n for (const sibling of store.listSemantic(prefix, limit)) {\n if (!seen.has(sibling.key)) {\n filteredResults.push(sibling);\n seen.add(sibling.key);\n }\n }\n }\n\n // Reorder: semantic-related entries float to the front so they survive\n // MAX_CONTEXT_CHARS truncation even when FTS-matched noise fills the list.\n if (semanticKeys.size > 0) {\n const semanticPrefixes = new Set();\n for (const k of semanticKeys) {\n const p = keyDomainPrefix(k);\n if (p) semanticPrefixes.add(p);\n }\n const isSemanticRelated = (key: string): boolean => {\n if (semanticKeys.has(key)) return true;\n const p = keyDomainPrefix(key);\n return p ? semanticPrefixes.has(p) : false;\n };\n const priority = filteredResults.filter(r => isSemanticRelated(r.key));\n const rest = filteredResults.filter(r => !isSemanticRelated(r.key));\n // Deterministic key order within each group: same entries \u2192 same text \u2192\n // provider prefix cache hits when the topic doesn't change between turns.\n priority.sort((a, b) => a.key.localeCompare(b.key));\n rest.sort((a, b) => a.key.localeCompare(b.key));\n filteredResults.length = 0;\n filteredResults.push(...priority, ...rest);\n }\n\n if (filteredResults.length > 0) {\n sections.push(formatSection(\"Relevant Memory\", filteredResults.map(formatSemantic)));\n semanticCount = filteredResults.length;\n\n // Track access time for these memories\n store.touchAccessed(filteredResults.map(r => r.key));\n }\n\n // Inject lessons \u2014 either all or filtered by relevance + project scope\n const lessons = mode === \"selective\"\n ? getRelevantLessons(store, prompt, cwd)\n : store.listLessons(undefined, 50, slug || undefined);\n\n if (lessons.length > 0) {\n const corrections = lessons.filter(l => l.negative);\n const positives = lessons.filter(l => !l.negative);\n\n if (corrections.length > 0) {\n const formatted = corrections.map(l =>\n `DON'T: ${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Learned Corrections\", formatted));\n }\n if (positives.length > 0) {\n const formatted = positives.map(l =>\n `${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Validated Approaches\", formatted));\n }\n lessonCount = lessons.length;\n }\n\n if (sections.length === 0) {\n return { text: \"\", stats: { semantic: 0, lessons: 0 } };\n }\n\n let text = `\\n${sections.join(\"\\n\")}\\n\\n${MEMORY_DRIFT_CAVEAT}\\n`;\n\n if (text.length > MAX_CONTEXT_CHARS) {\n text = text.slice(0, MAX_CONTEXT_CHARS - 20) + \"\\n... (truncated)\\n\";\n }\n\n return { text, stats: { semantic: semanticCount, lessons: lessonCount } };\n}\n\n// \u2500\u2500\u2500 Selective lesson injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Get lessons relevant to the current prompt + project context.\n *\n * Strategy:\n * 1. Search lessons by prompt terms (semantic/FTS match)\n * 2. If cwd implies a project, also search by project slug\n * 3. Always include \"general\" category lessons (broadly applicable)\n * 4. Dedup and cap at LESSON_SEARCH_LIMIT\n */\nfunction getRelevantLessons(store: MemoryStore, prompt: string, cwd?: string): LessonEntry[] {\n const seen = new Set();\n const result: LessonEntry[] = [];\n\n function add(lessons: LessonEntry[]) {\n for (const l of lessons) {\n if (!seen.has(l.id)) {\n seen.add(l.id);\n result.push(l);\n }\n }\n }\n\n // 1. Search by prompt relevance (FTS across rule text + category)\n add(store.searchLessons(prompt, LESSON_SEARCH_LIMIT));\n\n // 2. Search by project slug if we have a cwd\n const slug = cwd ? projectSlug(cwd) : \"\";\n if (slug) {\n add(store.searchLessons(slug, 5));\n }\n\n // 3. Always include general lessons (they're broadly applicable)\n add(store.listLessons(\"general\", 10));\n\n return result.slice(0, LESSON_SEARCH_LIMIT);\n}\n\n// \u2500\u2500\u2500 Fallback (no prompt) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildFallbackBlock(store: MemoryStore, cwd?: string): ContextBlock {\n const sections: string[] = [];\n let semanticCount = 0;\n let lessonCount = 0;\n\n const prefs = store.listSemantic(\"pref.\", 50);\n if (prefs.length > 0) {\n sections.push(formatSection(\"User Preferences\", prefs.map(formatSemantic)));\n semanticCount += prefs.length;\n }\n\n const projects = store.listSemantic(\"project.\", 50);\n // Filter project facts to the current project only.\n // Match by exact second key segment (project..) rather than\n // substring \u2014 avoids \"pi\" matching \"project.pipefittingjobs.*\" and prevents\n // user-set facts (confidence 0.95) from bleeding into unrelated sessions.\n const slug = cwd ? projectSlug(cwd) : \"\";\n const relevant = slug\n ? projects.filter(p => {\n const parts = p.key.split(\".\");\n return parts.length >= 2 && parts[1] === slug;\n })\n : projects;\n if (relevant.length > 0) {\n sections.push(formatSection(\"Project Context\", relevant.map(formatSemantic)));\n semanticCount += relevant.length;\n }\n\n const tools = store.listSemantic(\"tool.\", 20);\n if (tools.length > 0) {\n sections.push(formatSection(\"Tool Preferences\", tools.map(formatSemantic)));\n semanticCount += tools.length;\n }\n\n const lessons = store.listLessons(undefined, 50, slug || undefined);\n if (lessons.length > 0) {\n const corrections = lessons.filter(l => l.negative);\n const positives = lessons.filter(l => !l.negative);\n\n if (corrections.length > 0) {\n const formatted = corrections.map(l =>\n `DON'T: ${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Learned Corrections\", formatted));\n }\n if (positives.length > 0) {\n const formatted = positives.map(l =>\n `${l.rule}${l.category !== \"general\" ? ` [${l.category}]` : \"\"}`\n );\n sections.push(formatSection(\"Validated Approaches\", formatted));\n }\n lessonCount = lessons.length;\n }\n\n const user = store.listSemantic(\"user.\", 10);\n if (user.length > 0) {\n sections.push(formatSection(\"User\", user.map(formatSemantic)));\n semanticCount += user.length;\n }\n\n if (sections.length === 0) {\n return { text: \"\", stats: { semantic: 0, lessons: 0 } };\n }\n\n let text = `\\n${sections.join(\"\\n\")}\\n\\n${MEMORY_DRIFT_CAVEAT}\\n`;\n\n if (text.length > MAX_CONTEXT_CHARS) {\n text = text.slice(0, MAX_CONTEXT_CHARS - 20) + \"\\n... (truncated)\\n\";\n }\n\n return { text, stats: { semantic: semanticCount, lessons: lessonCount } };\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Staleness thresholds (in days) */\nconst STALE_WARNING_DAYS = 30;\nconst VERY_STALE_DAYS = 90;\n\nfunction formatSection(title: string, items: string[]): string {\n return `## ${title}\\n${items.map(i => `- ${i}`).join(\"\\n\")}`;\n}\n\n/**\n * Format a semantic entry with staleness indicator.\n * Memories older than 30 days get a warning; older than 90 days get a strong warning.\n * This prevents the agent from treating stale facts as current truth.\n */\nfunction formatSemantic(entry: SemanticEntry): string {\n const key = entry.key.split(\".\").slice(1).join(\".\");\n const ageDays = daysSince(entry.updated_at);\n const staleTag = ageDays >= VERY_STALE_DAYS\n ? ` \u26A0\uFE0F ${ageDays}d old \u2014 verify before acting on this`\n : ageDays >= STALE_WARNING_DAYS\n ? ` (${ageDays}d ago)`\n : \"\";\n return `${key}: ${entry.value}${staleTag}`;\n}\n\n/**\n * Calculate days since a date string.\n */\nfunction daysSince(dateStr: string): number {\n try {\n const then = new Date(dateStr).getTime();\n const now = Date.now();\n return Math.floor((now - then) / (1000 * 60 * 60 * 24));\n } catch {\n return 0;\n }\n}\n\n/**\n * Memory drift caveat \u2014 appended to the memory block so the agent knows\n * to verify recalled facts against current state before acting on them.\n */\nconst MEMORY_DRIFT_CAVEAT = `## Before acting on memory\n- Memory records can become stale. If a memory names a file, function, or flag \u2014 verify it still exists before recommending it. \"The memory says X exists\" is not the same as \"X exists now.\"\n- If a recalled memory conflicts with what you observe in the current code or project state, trust what you observe now.\n- Memories about project state (deadlines, decisions, architecture) decay fastest \u2014 check if still relevant.`;\n\n/**\n * Extract domain prefix for sibling expansion.\n * Only keys with 3+ segments expand: `user.health.diet` \u2192 `user.health`.\n * 2-segment keys like `pref.editor` or `user.fitness` are leaf-level.\n */\nfunction keyDomainPrefix(key: string): string | null {\n const parts = key.split(\".\");\n return parts.length >= 3 ? parts.slice(0, 2).join(\".\") : null;\n}\n\n/**\n * Background: compute and store embeddings for entries that are missing them.\n * Runs after a successful semantic search, populating the DB for future use.\n * Capped at 10 entries per call to avoid blocking the event loop.\n */\nasync function backfillEmbeddings(\n store: MemoryStore,\n missing: Array<{ key: string }>,\n): Promise {\n if (missing.length === 0) return;\n for (const { key } of missing.slice(0, 10)) {\n const entry = store.getSemantic(key);\n if (!entry) continue;\n // Use the human-readable key suffix + value as embedding input\n const displayKey = key.split(\".\").slice(1).join(\" \");\n const vec = await embed(`${displayKey} ${entry.value}`);\n if (vec) store.setEmbedding(key, vec);\n }\n}\n\nexport function projectSlug(cwd: string): string {\n const parts = cwd.split(\"/\").filter(Boolean);\n const skip = new Set([\"workplace\", \"local\", \"home\", \"src\", \"scratch\", os.userInfo().username]);\n for (const p of parts.reverse()) {\n if (!skip.has(p.toLowerCase()) && p.length > 1) return p.toLowerCase();\n }\n return \"\";\n}\n", "/**\n * Consolidator \u2014 extracts structured knowledge from session conversations.\n *\n * After a session ends (or on demand), reads the conversation and uses an\n * LLM to extract:\n * - Preferences (\u2192 semantic memory, pref.*)\n * - Project patterns (\u2192 semantic memory, project.*)\n * - Corrections/lessons (\u2192 lessons table)\n * - Tool preferences (\u2192 semantic memory, tool.*)\n *\n * Uses the pi SDK's createAgentSession for the LLM call, or falls back\n * to a simple extraction when no LLM is available.\n */\nimport type { MemoryStore } from \"./store.js\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ConsolidationInput {\n /** User messages from the session */\n userMessages: string[];\n /** Assistant messages from the session */\n assistantMessages: string[];\n /** Working directory of the session */\n cwd?: string;\n /** Session ID for provenance */\n sessionId?: string;\n}\n\nexport interface ExtractedMemory {\n semantic: Array<{ key: string; value: string; confidence: number }>;\n lessons: Array<{ rule: string; category: string; negative: boolean }>;\n}\n\nexport const CONSOLIDATION_PROMPT = `You are a memory extraction system. Analyze this conversation and extract structured knowledge.\n\nExtract ONLY concrete, reusable facts \u2014 not summaries of what happened. Focus on:\n\n1. **User preferences** (key prefix: pref.) \u2014 coding style, tool preferences, workflow habits\n Example: { \"key\": \"pref.commit_style\", \"value\": \"conventional commits\", \"confidence\": 0.9 }\n\n2. **Project patterns** (key prefix: project..) \u2014 languages, frameworks, architecture decisions\n Example: { \"key\": \"project.rosie.di\", \"value\": \"Dagger dependency injection\", \"confidence\": 0.95 }\n\n3. **Tool preferences** (key prefix: tool.) \u2014 which tools to prefer/avoid, how to use them\n Example: { \"key\": \"tool.sed\", \"value\": \"use for daily note insertion, not echo >>\", \"confidence\": 0.9 }\n\n4. **Corrections/lessons** \u2014 things the user corrected, mistakes to avoid\n Example: { \"rule\": \"Use sed to insert after ## Notes heading, not echo >> which appends after Tags\", \"category\": \"vault\", \"negative\": true }\n\n5. **Validated approaches** \u2014 things the user explicitly confirmed worked well (positive signal)\n Example: { \"rule\": \"When deploying wiki changes, draft first and let user preview before publishing\", \"category\": \"wiki-edit\", \"negative\": false }\n\n## What NOT to extract \u2014 these are derivable or ephemeral, and pollute memory:\n\n- **Code patterns, architecture, file paths, project structure** \u2014 these can be derived by reading the current project state (grep, git, file reads)\n- **Git history, recent changes, who-changed-what** \u2014 git log/blame are authoritative\n- **Debugging solutions or fix recipes** \u2014 the fix is in the code; the commit message has context\n- **Anything already documented in AGENTS.md, CLAUDE.md, or project config files**\n- **Ephemeral task details** \u2014 in-progress work, temporary state, current conversation context\n- **Activity summaries** \u2014 \"today we worked on X\" is not a lasting fact. Instead ask: what was *surprising* or *non-obvious* about it?\n- **File contents or code snippets** \u2014 the file itself is the source of truth\n- **Exact commands that worked once** \u2014 unless they encode a non-obvious pattern that the agent consistently gets wrong\n\nThese exclusions apply even if the user asks to save such things. If asked, extract what was *surprising* or *non-obvious* \u2014 that is the part worth keeping.\n\nRules:\n- Only extract if confidence >= 0.8 (you're reasonably sure this is a lasting preference, not a one-off)\n- Key format: lowercase, dots as separators, no spaces\n- Keep values concise (under 200 chars)\n- For corrections, set negative=true if it's something to AVOID\n- For validated approaches (user confirmed something works), set negative=false\n\nRespond with ONLY valid JSON matching this schema:\n{\n \"semantic\": [{ \"key\": \"string\", \"value\": \"string\", \"confidence\": number }],\n \"lessons\": [{ \"rule\": \"string\", \"category\": \"string\", \"negative\": boolean }]\n}\n\nIf nothing worth extracting, return: { \"semantic\": [], \"lessons\": [] }`;\n\n// \u2500\u2500\u2500 Consolidation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Build the consolidation prompt for an LLM call.\n */\nexport function buildConsolidationPrompt(\n input: ConsolidationInput,\n currentFacts?: { key: string; value: string }[],\n currentLessons?: { rule: string; category: string }[]\n): string {\n const messages: string[] = [];\n\n // Current memory state section \u2014 helps the LLM avoid duplicates\n let memorySection = \"\";\n if ((currentFacts && currentFacts.length > 0) || (currentLessons && currentLessons.length > 0)) {\n const parts: string[] = [\"## Current Memory State\"];\n if (currentFacts && currentFacts.length > 0) {\n parts.push(\"The user already has these facts stored (avoid duplicating, update if changed):\");\n let chars = 0;\n for (const f of currentFacts) {\n const line = `- ${f.key}: ${f.value.length > 120 ? f.value.slice(0, 120) + \"\u2026\" : f.value}`;\n if (chars + line.length > 1500) { parts.push(\"- ... (truncated)\"); break; }\n parts.push(line);\n chars += line.length;\n }\n }\n if (currentLessons && currentLessons.length > 0) {\n parts.push(\"\\nAnd these lessons (avoid duplicating):\");\n let chars = 0;\n for (const l of currentLessons) {\n const line = `- [${l.category}] ${l.rule.length > 120 ? l.rule.slice(0, 120) + \"\u2026\" : l.rule}`;\n if (chars + line.length > 500) { parts.push(\"- ... (truncated)\"); break; }\n parts.push(line);\n chars += line.length;\n }\n }\n memorySection = parts.join(\"\\n\") + \"\\n\\n\";\n }\n\n // Interleave user/assistant messages for context\n const maxPairs = 30; // cap to avoid huge prompts\n const len = Math.min(input.userMessages.length, maxPairs);\n for (let i = 0; i < len; i++) {\n const userMsg = input.userMessages[i];\n if (userMsg) messages.push(`User: ${truncate(userMsg, 1000)}`);\n const assistantMsg = input.assistantMessages[i];\n if (assistantMsg) messages.push(`Assistant: ${truncate(assistantMsg, 500)}`);\n }\n\n return `${CONSOLIDATION_PROMPT}\n\n${memorySection}${input.cwd ? `Working directory: ${input.cwd}\\n` : \"\"}\n## Conversation\n\n${messages.join(\"\\n\\n\")}`;\n}\n\n/**\n * Parse the LLM's JSON response into structured memory.\n */\nexport function parseConsolidationResponse(text: string): ExtractedMemory {\n // Extract JSON from response (may be wrapped in markdown code blocks)\n const jsonMatch = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/) || text.match(/(\\{[\\s\\S]*\\})/);\n if (!jsonMatch) return { semantic: [], lessons: [] };\n\n try {\n const parsed = JSON.parse(jsonMatch[1].trim());\n const result: ExtractedMemory = { semantic: [], lessons: [] };\n\n if (Array.isArray(parsed.semantic)) {\n for (const s of parsed.semantic) {\n if (typeof s.key === \"string\" && typeof s.value === \"string\" && typeof s.confidence === \"number\") {\n if (s.confidence >= 0.8 && isValidKey(s.key) && s.value.length <= 500) {\n result.semantic.push({ key: s.key, value: s.value, confidence: s.confidence });\n }\n }\n }\n }\n\n if (Array.isArray(parsed.lessons)) {\n for (const l of parsed.lessons) {\n if (typeof l.rule === \"string\" && l.rule.trim().length > 0) {\n result.lessons.push({\n rule: l.rule.trim(),\n category: typeof l.category === \"string\" ? l.category : \"general\",\n negative: !!l.negative,\n });\n }\n }\n }\n\n return result;\n } catch {\n return { semantic: [], lessons: [] };\n }\n}\n\n/**\n * Apply extracted memory to the store, filtering out derivable/ephemeral entries.\n * Project-scoped lessons (from consolidation) are tagged with the current project slug\n * so they are only injected when working in that project.\n */\nexport function applyExtracted(store: MemoryStore, extracted: ExtractedMemory, source: string = \"consolidation\", project?: string): { semantic: number; lessons: number } {\n let semanticCount = 0;\n let lessonCount = 0;\n\n for (const s of extracted.semantic) {\n if (isDerivableOrEphemeral(s.key, s.value)) continue;\n store.setSemantic(s.key, s.value, s.confidence, \"consolidation\");\n semanticCount++;\n }\n\n for (const l of extracted.lessons) {\n if (isDerivableLesson(l.rule)) continue;\n // Tag consolidated lessons with the project slug so they don't leak into\n // unrelated project sessions. User-authored lessons (source=\"user\") are\n // not tagged \u2014 they're intentionally global.\n const lessonProject = source === \"user\" ? undefined : project;\n const result = store.addLesson(l.rule, l.category, source, l.negative, lessonProject);\n if (result.success) lessonCount++;\n }\n\n return { semantic: semanticCount, lessons: lessonCount };\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VALID_KEY_RE = /^[a-z][a-z0-9._-]*$/;\n\nfunction isValidKey(key: string): boolean {\n return VALID_KEY_RE.test(key) && key.length <= 100 && key.length >= 2;\n}\n\n/**\n * Reject semantic entries that store derivable or ephemeral information.\n * These pollute memory \u2014 the project itself is the source of truth.\n */\nfunction isDerivableOrEphemeral(key: string, value: string): boolean {\n const kl = key.toLowerCase();\n const vl = value.toLowerCase();\n\n // File paths, architecture, project structure \u2014 derivable from the project\n if (kl.includes(\"filepath\") || kl.includes(\"file_path\") || kl.includes(\"directory\")) return true;\n if (/^project\\.\\w+\\.(path|dir|location|structure|layout|architecture)$/.test(kl)) return true;\n\n // Git history \u2014 git log/blame is authoritative\n if (kl.includes(\"commit\") || kl.includes(\"git.history\") || kl.includes(\"git.recent\")) return true;\n\n // Activity summaries \u2014 \"today we worked on X\" is not a lasting fact\n if (vl.startsWith(\"today \") || vl.startsWith(\"we worked on\") || vl.startsWith(\"this session\")) return true;\n\n // Exact file contents or long code snippets\n if (vl.includes(\"```\") && vl.length > 300) return true;\n\n // Temporary investigation state\n if (kl.includes(\"current_task\") || kl.includes(\"in_progress\") || kl.includes(\"investigating\")) return true;\n\n return false;\n}\n\n/**\n * Reject lesson entries that are derivable from code or too ephemeral.\n */\nfunction isDerivableLesson(rule: string): boolean {\n const rl = rule.toLowerCase();\n\n // \"File X is at path Y\" \u2014 derivable\n if (/file .+ is (at|in|located) /.test(rl)) return true;\n\n // \"The project uses X\" when X is obvious from package.json/build files\n if (/^the (project|codebase|repo) (uses|is written in) /.test(rl)) return true;\n\n // Pure activity logging \u2014 \"we fixed X\" or \"we deployed Y\"\n if (/^(we|i|the agent) (fixed|deployed|updated|changed|modified|ran|executed) /.test(rl)) return true;\n\n // Error\u2192fix recipes \u2014 \"when error X, run command Y\" \u2014 these are specific\n // debugging commands from one session, not generalizable lessons\n if (/^when (encountering|bash fails|edit fails|.*error)/.test(rl) && /\\b(run:|fix with:)/.test(rl)) return true;\n\n // Literal command sequences \u2014 rules that are mostly a shell command\n if (/^run: /.test(rl)) return true;\n if (rl.includes(\"command exited with code\") && rl.length < 100) return true;\n\n return false;\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max) + \"\u2026\" : text;\n}\n"], + "mappings": ";AAoBA,SAAS,YAA0B;AACnC,SAAS,MAAM,eAAe;AAC9B,SAAS,eAAe;AACxB,SAAS,oBAAoB;;;AChB7B,SAAS,oBAAoB;AAC7B,SAAS,WAAW,kBAAkB;AACtC,SAAS,eAAe;AAoCjB,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA,YAA2B,QAAQ,QAAQ;AAAA,EAC3C,UAAmB;AAAA,EAE3B,YAAY,QAAgB;AAC1B,UAAM,MAAM,QAAQ,MAAM;AAC1B,QAAI,CAAC,WAAW,GAAG,EAAG,WAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAExD,SAAK,KAAK,IAAI,aAAa,MAAM;AACjC,SAAK,GAAG,KAAK,2BAA2B;AACxC,SAAK,GAAG,KAAK,4BAA4B;AACzC,SAAK,GAAG,KAAK,0BAA0B;AACvC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,UAAgB;AACtB,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA4BZ;AAGD,QAAI;AACF,WAAK,GAAG,KAAK,oDAAoD;AAAA,IACnE,QAAQ;AAAA,IAER;AAEA,QAAI;AACF,WAAK,GAAG,KAAK,6CAA6C;AAAA,IAC5D,QAAQ;AAAA,IAER;AAEA,QAAI;AACF,WAAK,GAAG,KAAK,gDAAgD;AAAA,IAC/D,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAaZ;AAED,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAaZ;AAGD,WAAK,GAAG,KAAK,0DAA0D;AACvE,WAAK,GAAG,KAAK,wDAAwD;AACrE,WAAK,UAAU;AAAA,IACjB,QAAQ;AAGN,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAY,IAAgB;AAGlC,SAAK,GAAG,KAAK,iBAAiB;AAC9B,QAAI;AACF,YAAM,SAAS,GAAG;AAClB,WAAK,GAAG,KAAK,QAAQ;AACrB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,GAAG,KAAK,UAAU;AACvB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAIA,YAAY,KAAwC;AAClD,UAAM,aAAa,IAAI,YAAY;AACnC,WAAO,KAAK,GAAG,QAAQ,sCAAsC,EAAE,IAAI,UAAU;AAAA,EAC/E;AAAA,EAEA,YAAY,KAAa,OAAe,aAAqB,KAAK,SAAkC,iBAAuB;AACzH,UAAM,aAAa,IAAI,YAAY;AACnC,SAAK,SAAS,MAAM;AAClB,YAAM,WAAW,KAAK,GAAG,QAAQ,sCAAsC,EAAE,IAAI,UAAU;AACvF,UAAI,YAAY,SAAS,aAAa,WAAY;AAElD,WAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQf,EAAE,IAAI,YAAY,OAAO,YAAY,MAAM;AAE5C,WAAK,SAAS,WAAW,WAAW,UAAU,YAAY,UAAU;AAAA,IACtE,CAAC;AAAA,EACH;AAAA,EAEA,eAAe,KAAsB;AACnC,UAAM,aAAa,IAAI,YAAY;AACnC,WAAO,KAAK,SAAS,MAAM;AACzB,YAAM,SAAS,KAAK,GAAG,QAAQ,oCAAoC,EAAE,IAAI,UAAU;AACnF,UAAI,OAAO,UAAU,EAAG,MAAK,SAAS,UAAU,YAAY,UAAU;AACtE,aAAO,OAAO,UAAU;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,KAAa,WAA+B;AACvD,UAAM,aAAa,IAAI,YAAY;AACnC,UAAM,OAAO,OAAO,KAAK,IAAI,WAAW,UAAU,QAAQ,UAAU,YAAY,UAAU,UAAU,CAAC;AACrG,SAAK,GAAG,QAAQ,iDAAiD,EAAE,IAAI,MAAM,UAAU;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAqE;AACnE,WAAO,KAAK,GACT,QAAQ,8DAA8D,EACtE,IAAI;AAAA,EACT;AAAA,EAEA,aAAa,QAAiB,QAAgB,KAAsB;AAClE,QAAI,QAAQ;AACV,aAAO,KAAK,GAAG,QAAQ,0EAA0E,EAC9F,IAAI,GAAG,MAAM,KAAK,KAAK;AAAA,IAC5B;AACA,WAAO,KAAK,GAAG,QAAQ,yDAAyD,EAC7E,IAAI,KAAK;AAAA,EACd;AAAA,EAEA,eAAe,OAAe,QAAgB,IAAqB;AACjE,UAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AACtD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAI,CAAC,KAAK,QAAS,QAAO,KAAK,wBAAwB,OAAO,KAAK;AAGnE,UAAM,WAAW,MAAM,IAAI,OAAK,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM;AAEzE,QAAI;AACF,YAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAO5B,EAAE,IAAI,UAAU,KAAK;AAEtB,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO,KAAK,wBAAwB,OAAO,KAAK;AAAA,IAClD;AAAA,EACF;AAAA,EAEQ,wBAAwB,OAAe,OAAgC;AAC7E,UAAM,QAAQ,MAAM,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC7D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,MAAM,KAAK,GAAG,QAAQ,wBAAwB,EAAE,IAAI;AAC1D,WAAO,IACJ,IAAI,WAAS;AACZ,YAAM,OAAO,GAAG,MAAM,GAAG,IAAI,MAAM,KAAK,GAAG,YAAY;AACvD,YAAM,UAAU,MAAM,OAAO,OAAK,KAAK,SAAS,CAAC,CAAC,EAAE;AACpD,aAAO,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO;AAAA,IAChD,CAAC,EACA,OAAO,CAAC,EAAE,MAAM,MAAM,QAAQ,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,KAAK,EACd,IAAI,CAAC,EAAE,MAAM,MAAM,KAAK;AAAA,EAC7B;AAAA,EAEA,cAAc,MAAsB;AAClC,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,OAAO,KAAK,GAAG,QAAQ,mEAAmE;AAChG,eAAW,OAAO,MAAM;AACtB,WAAK,IAAI,IAAI,YAAY,CAAC;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAIA,UAAU,MAAc,WAAmB,WAAW,SAAiB,iBAAiB,WAAoB,OAAO,SAAsE;AACvL,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS,QAAO,EAAE,SAAS,OAAO,QAAQ,aAAa;AAE5D,UAAM,qBAAqB,SAAS,KAAK,EAAE,YAAY,KAAK;AAE5D,WAAO,KAAK,SAAS,MAAM;AAEzB,YAAM,WAAW,KAAK,GAAG;AAAA,QACvB;AAAA,MACF,EAAE,IAAI,QAAQ,YAAY,CAAC;AAC3B,UAAI,SAAU,QAAO,EAAE,SAAS,OAAgB,QAAQ,aAAsB,IAAI,SAAS,GAAG;AAG9F,YAAM,WAAW,KAAK,GAAG,QAAQ,mDAAmD,EAAE,IAAI;AAC1F,iBAAW,KAAK,UAAU;AACxB,YAAI,QAAQ,SAAS,EAAE,IAAI,KAAK,KAAK;AACnC,iBAAO,EAAE,SAAS,OAAgB,QAAQ,WAAoB,IAAI,EAAE,GAAG;AAAA,QACzE;AAAA,MACF;AAEA,YAAM,KAAK,OAAO,WAAW;AAC7B,WAAK,GAAG;AAAA,QACN;AAAA,MACF,EAAE,IAAI,IAAI,SAAS,oBAAoB,QAAQ,WAAW,IAAI,GAAG,WAAW,IAAI;AAEhF,WAAK,SAAS,UAAU,UAAU,IAAI,QAAQ,MAAM,GAAG,GAAG,CAAC;AAC3D,aAAO,EAAE,SAAS,MAAe,GAAG;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,IAAqC;AAC7C,UAAM,MAAM,KAAK,GAAG,QAAQ,uDAAuD,EAAE,IAAI,EAAE;AAC3F,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,EAAE,GAAG,KAAK,UAAU,CAAC,CAAC,IAAI,SAAS;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,YAAY,UAAmB,QAAgB,IAAI,SAAiC;AAClF,QAAI;AACJ,QAAI,YAAY,SAAS;AACvB,YAAM,qBAAqB,SAAS,KAAK,EAAE,YAAY;AACvD,aAAO,KAAK,GAAG;AAAA,QACb;AAAA,MACF,EAAE,IAAI,oBAAoB,SAAS,KAAK;AAAA,IAC1C,WAAW,UAAU;AACnB,YAAM,qBAAqB,SAAS,KAAK,EAAE,YAAY;AACvD,aAAO,KAAK,GAAG,QAAQ,8FAA8F,EAClH,IAAI,oBAAoB,KAAK;AAAA,IAClC,WAAW,SAAS;AAClB,aAAO,KAAK,GAAG;AAAA,QACb;AAAA,MACF,EAAE,IAAI,SAAS,KAAK;AAAA,IACtB,OAAO;AACL,aAAO,KAAK,GAAG,QAAQ,6EAA6E,EACjG,IAAI,KAAK;AAAA,IACd;AACA,WAAO,KAAK,IAAI,QAAM,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC,EAAE,UAAU,SAAS,EAAE,WAAW,KAAK,EAAE;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,OAAe,QAAgB,IAAmB;AAC9D,UAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AACtD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAI,CAAC,KAAK,QAAS,QAAO,KAAK,uBAAuB,OAAO,KAAK;AAElE,UAAM,WAAW,MAAM,IAAI,OAAK,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM;AAEzE,QAAI;AACF,YAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAO5B,EAAE,IAAI,UAAU,KAAK;AAEtB,aAAO,KAAK,IAAI,QAAM,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC,EAAE,UAAU,SAAS,EAAE,WAAW,KAAK,EAAE;AAAA,IACrF,QAAQ;AACN,aAAO,KAAK,uBAAuB,OAAO,KAAK;AAAA,IACjD;AAAA,EACF;AAAA,EAEQ,uBAAuB,OAAe,OAA8B;AAC1E,UAAM,QAAQ,MAAM,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC7D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,MAAM,KAAK,GAAG,QAAQ,4CAA4C,EAAE,IAAI;AAC9E,WAAO,IACJ,IAAI,WAAS;AACZ,YAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,QAAQ,GAAG,YAAY;AAC3D,YAAM,UAAU,MAAM,OAAO,OAAK,KAAK,SAAS,CAAC,CAAC,EAAE;AACpD,aAAO,EAAE,OAAO,EAAE,GAAG,OAAO,UAAU,CAAC,CAAC,MAAM,UAAU,SAAS,MAAM,WAAW,KAAK,GAAkB,OAAO,UAAU,MAAM,OAAO;AAAA,IACzI,CAAC,EACA,OAAO,CAAC,EAAE,MAAM,MAAM,QAAQ,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,KAAK,EACd,IAAI,CAAC,EAAE,MAAM,MAAM,KAAK;AAAA,EAC7B;AAAA,EAEA,aAAa,IAAqB;AAChC,WAAO,KAAK,SAAS,MAAM;AAEzB,UAAI,SAAS,KAAK,GAAG,QAAQ,mEAAmE,EAAE,IAAI,EAAE;AACxG,UAAI,OAAO,YAAY,KAAK,GAAG,SAAS,IAAI;AAE1C,cAAM,UAAU,KAAK,GAAG,QAAQ,2DAA2D,EAAE,IAAI,GAAG,EAAE,GAAG;AACzG,YAAI,QAAQ,WAAW,GAAG;AACxB,mBAAS,KAAK,GAAG,QAAQ,mEAAmE,EAAE,IAAI,QAAQ,CAAC,EAAE,EAAE;AAC/G,cAAI,OAAO,UAAU,EAAG,MAAK,SAAS,UAAU,UAAU,QAAQ,CAAC,EAAE,EAAE;AACvE,iBAAO;AAAA,QACT;AAAA,MACF;AACA,UAAI,OAAO,UAAU,EAAG,MAAK,SAAS,UAAU,UAAU,EAAE;AAC5D,aAAO,OAAO,UAAU;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,SAAS,WAAmB,YAAoB,KAAa,UAAkB,IAAU;AAC/F,SAAK,GAAG;AAAA,MACN;AAAA,IACF,EAAE,IAAI,WAAW,YAAY,KAAK,OAAO;AAAA,EAC3C;AAAA,EAEA,WAAW,QAAgB,IAAmB;AAC5C,WAAO,KAAK,GAAG,QAAQ,+CAA+C,EAAE,IAAI,KAAK;AAAA,EACnF;AAAA;AAAA,EAIA,QAA+D;AAC7D,UAAM,WAAY,KAAK,GAAG,QAAQ,oCAAoC,EAAE,IAAI,EAAU;AACtF,UAAM,UAAW,KAAK,GAAG,QAAQ,wDAAwD,EAAE,IAAI,EAAU;AACzG,UAAM,SAAU,KAAK,GAAG,QAAQ,kCAAkC,EAAE,IAAI,EAAU;AAClF,WAAO,EAAE,UAAU,SAAS,OAAO;AAAA,EACrC;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;AAIA,SAAS,QAAQ,GAAW,GAAmB;AAC7C,QAAM,OAAO,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC;AACjE,QAAM,OAAO,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC;AACjE,MAAI,KAAK,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO;AAC/C,QAAM,eAAe,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,OAAK,KAAK,IAAI,CAAC,CAAC,CAAC;AAC/D,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;AACxC,SAAO,aAAa,OAAO,MAAM;AACnC;;;AC/bA,IAAM,QAAQ;AACd,IAAM,kBAAkB;AACxB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AAExB,IAAI,QAAiB;AACrB,IAAI,UAAU;AAEd,eAAe,UAA4B;AACzC,MAAI,QAAS,QAAO;AACpB,MAAI,MAAO,QAAO;AAClB,MAAI;AAGF,UAAM,MAAM;AACZ,UAAM,MAAM,MAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAC9C,QAAI,CAAC,KAAK;AACR,cAAQ,MAAM,yEAAyE;AACvF,gBAAU;AACV,aAAO;AAAA,IACT;AACA,UAAM,EAAE,UAAU,IAAI,IAAI;AAC1B,QAAI,oBAAoB;AACxB,QAAI,kBAAkB;AACtB,YAAQ,MAAM;AAAA,MACZ,SAAS,sBAAsB,OAAO,EAAE,WAAW,KAAK,CAAC;AAAA,MACzD;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,YAAQ,MAAM,oCAAqC,KAAa,WAAW,GAAG,mBAAmB;AACjG,cAAU;AACV,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,MAAM,MAA4C;AACtE,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MACf,KAAa,KAAK,MAAM,GAAG,eAAe,GAAG,EAAE,SAAS,QAAQ,WAAW,KAAK,CAAC;AAAA,MAClF;AAAA,MACA;AAAA,IACF;AACA,WAAO,IAAI,aAAc,IAAY,IAAI;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,WAAW,GAAiB,GAAyB;AACnE,MAAI,MAAM;AACV,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,IAAK,QAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/C,SAAO;AACT;AAgBO,SAAS,SAAS,GAAmD;AAC1E,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,MAAM,WAAW,KAAK,CAAC;AAC7B,SAAO,IAAI,aAAa,IAAI,MAAM;AACpC;AAEA,SAAS,YAAe,GAAe,IAAY,OAA2B;AAC5E,SAAO,QAAQ,KAAK;AAAA,IAClB;AAAA,IACA,IAAI;AAAA,MAAe,CAAC,GAAG,WACrB,WAAW,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE;AAAA,IAC1E;AAAA,EACF,CAAC;AACH;;;AC1FA,OAAO,QAAQ;AAEf,IAAM,oBAAoB;AAC1B,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAkE5B,eAAsB,kBAAkB,OAAoB,KAAc,QAAiB,QAAgD;AACzI,MAAI,QAAQ,KAAK,GAAG;AAClB,WAAO,oBAAoB,OAAO,QAAQ,KAAK,MAAM;AAAA,EACvD;AACA,SAAO,mBAAmB,OAAO,GAAG;AACtC;AAIA,eAAe,oBAAoB,OAAoB,QAAgB,KAAc,QAAgD;AACnI,QAAM,WAAqB,CAAC;AAC5B,MAAI,gBAAgB;AACpB,MAAI,cAAc;AAClB,QAAM,OAAO,QAAQ,mBAAmB;AAGxC,QAAM,UAAU,MAAM,eAAe,QAAQ,YAAY;AAGzD,QAAM,OAAO,MAAM,YAAY,GAAG,IAAI;AACtC,MAAI,MAAM;AACR,UAAM,iBAAiB,MAAM,eAAe,MAAM,CAAC;AAEnD,UAAMA,QAAO,IAAI,IAAI,QAAQ,IAAI,OAAK,EAAE,GAAG,CAAC;AAC5C,eAAW,KAAK,gBAAgB;AAC9B,UAAI,CAACA,MAAK,IAAI,EAAE,GAAG,GAAG;AACpB,gBAAQ,KAAK,CAAC;AACd,QAAAA,MAAK,IAAI,EAAE,GAAG;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAQA,QAAM,kBAAkB,OACpB,QAAQ,OAAO,OAAK;AAClB,QAAI,CAAC,EAAE,IAAI,WAAW,UAAU,EAAG,QAAO;AAC1C,UAAM,QAAQ,EAAE,IAAI,MAAM,GAAG;AAC7B,WAAO,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM;AAAA,EAC3C,CAAC,IACD;AAGJ,QAAM,OAAO,IAAI,IAAI,gBAAgB,IAAI,OAAK,EAAE,GAAG,CAAC;AASpD,QAAM,qBAAqB;AAC3B,QAAM,iBAAiB;AACvB,QAAM,UAAU,MAAM,iBAAiB;AACvC,QAAM,YAAY,MAAM,MAAM,MAAM;AACpC,QAAM,eAAe,oBAAI,IAAY;AAErC,MAAI,WAAW;AACb,UAAM,eAAe,QAClB,QAAQ,CAAC,EAAE,KAAK,UAAU,MAAM;AAC/B,YAAM,MAAM,SAAS,SAAS;AAC9B,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,YAAM,QAAQ,WAAW,WAAW,GAAG;AACvC,aAAO,SAAS,qBAAqB,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC;AAAA,IAC3D,CAAC,EACA,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,cAAc;AAE1B,eAAW,EAAE,IAAI,KAAK,cAAc;AAGlC,mBAAa,IAAI,GAAG;AACpB,UAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,cAAM,QAAQ,MAAM,YAAY,GAAG;AACnC,YAAI,OAAO;AACT,0BAAgB,KAAK,KAAK;AAC1B,eAAK,IAAI,GAAG;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAIA,uBAAmB,OAAO,QAAQ,OAAO,OAAK,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC7E;AAMA,QAAM,mBAAmB,oBAAI,IAAY;AACzC,aAAW,KAAK,CAAC,GAAG,eAAe,GAAG;AACpC,UAAM,SAAS,gBAAgB,EAAE,GAAG;AACpC,QAAI,CAAC,UAAU,iBAAiB,IAAI,MAAM,EAAG;AAC7C,qBAAiB,IAAI,MAAM;AAC3B,UAAM,QAAQ,aAAa,IAAI,EAAE,GAAG,IAAI,KAAK;AAC7C,eAAW,WAAW,MAAM,aAAa,QAAQ,KAAK,GAAG;AACvD,UAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,GAAG;AAC1B,wBAAgB,KAAK,OAAO;AAC5B,aAAK,IAAI,QAAQ,GAAG;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAIA,MAAI,aAAa,OAAO,GAAG;AACzB,UAAM,mBAAmB,oBAAI,IAAY;AACzC,eAAW,KAAK,cAAc;AAC5B,YAAM,IAAI,gBAAgB,CAAC;AAC3B,UAAI,EAAG,kBAAiB,IAAI,CAAC;AAAA,IAC/B;AACA,UAAM,oBAAoB,CAAC,QAAyB;AAClD,UAAI,aAAa,IAAI,GAAG,EAAG,QAAO;AAClC,YAAM,IAAI,gBAAgB,GAAG;AAC7B,aAAO,IAAI,iBAAiB,IAAI,CAAC,IAAI;AAAA,IACvC;AACA,UAAM,WAAW,gBAAgB,OAAO,OAAK,kBAAkB,EAAE,GAAG,CAAC;AACrE,UAAM,OAAO,gBAAgB,OAAO,OAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC;AAGlE,aAAS,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,cAAc,EAAE,GAAG,CAAC;AAClD,SAAK,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,cAAc,EAAE,GAAG,CAAC;AAC9C,oBAAgB,SAAS;AACzB,oBAAgB,KAAK,GAAG,UAAU,GAAG,IAAI;AAAA,EAC3C;AAEA,MAAI,gBAAgB,SAAS,GAAG;AAC9B,aAAS,KAAK,cAAc,mBAAmB,gBAAgB,IAAI,cAAc,CAAC,CAAC;AACnF,oBAAgB,gBAAgB;AAGhC,UAAM,cAAc,gBAAgB,IAAI,OAAK,EAAE,GAAG,CAAC;AAAA,EACrD;AAGA,QAAM,UAAU,SAAS,cACrB,mBAAmB,OAAO,QAAQ,GAAG,IACrC,MAAM,YAAY,QAAW,IAAI,QAAQ,MAAS;AAEtD,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,cAAc,QAAQ,OAAO,OAAK,EAAE,QAAQ;AAClD,UAAM,YAAY,QAAQ,OAAO,OAAK,CAAC,EAAE,QAAQ;AAEjD,QAAI,YAAY,SAAS,GAAG;AAC1B,YAAM,YAAY,YAAY;AAAA,QAAI,OAChC,UAAU,EAAE,IAAI,GAAG,EAAE,aAAa,YAAY,KAAK,EAAE,QAAQ,MAAM,EAAE;AAAA,MACvE;AACA,eAAS,KAAK,cAAc,uBAAuB,SAAS,CAAC;AAAA,IAC/D;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,YAAY,UAAU;AAAA,QAAI,OAC9B,GAAG,EAAE,IAAI,GAAG,EAAE,aAAa,YAAY,KAAK,EAAE,QAAQ,MAAM,EAAE;AAAA,MAChE;AACA,eAAS,KAAK,cAAc,wBAAwB,SAAS,CAAC;AAAA,IAChE;AACA,kBAAc,QAAQ;AAAA,EACxB;AAEA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,EAAE,MAAM,IAAI,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,EAAE;AAAA,EACxD;AAEA,MAAI,OAAO;AAAA,EAAa,SAAS,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,mBAAmB;AAAA;AAErE,MAAI,KAAK,SAAS,mBAAmB;AACnC,WAAO,KAAK,MAAM,GAAG,oBAAoB,EAAE,IAAI;AAAA,EACjD;AAEA,SAAO,EAAE,MAAM,OAAO,EAAE,UAAU,eAAe,SAAS,YAAY,EAAE;AAC1E;AAaA,SAAS,mBAAmB,OAAoB,QAAgB,KAA6B;AAC3F,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAwB,CAAC;AAE/B,WAAS,IAAI,SAAwB;AACnC,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG;AACnB,aAAK,IAAI,EAAE,EAAE;AACb,eAAO,KAAK,CAAC;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAGA,MAAI,MAAM,cAAc,QAAQ,mBAAmB,CAAC;AAGpD,QAAM,OAAO,MAAM,YAAY,GAAG,IAAI;AACtC,MAAI,MAAM;AACR,QAAI,MAAM,cAAc,MAAM,CAAC,CAAC;AAAA,EAClC;AAGA,MAAI,MAAM,YAAY,WAAW,EAAE,CAAC;AAEpC,SAAO,OAAO,MAAM,GAAG,mBAAmB;AAC5C;AAIA,SAAS,mBAAmB,OAAoB,KAA4B;AAC1E,QAAM,WAAqB,CAAC;AAC5B,MAAI,gBAAgB;AACpB,MAAI,cAAc;AAElB,QAAM,QAAQ,MAAM,aAAa,SAAS,EAAE;AAC5C,MAAI,MAAM,SAAS,GAAG;AACpB,aAAS,KAAK,cAAc,oBAAoB,MAAM,IAAI,cAAc,CAAC,CAAC;AAC1E,qBAAiB,MAAM;AAAA,EACzB;AAEA,QAAM,WAAW,MAAM,aAAa,YAAY,EAAE;AAKlD,QAAM,OAAO,MAAM,YAAY,GAAG,IAAI;AACtC,QAAM,WAAW,OACb,SAAS,OAAO,OAAK;AACnB,UAAM,QAAQ,EAAE,IAAI,MAAM,GAAG;AAC7B,WAAO,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM;AAAA,EAC3C,CAAC,IACD;AACJ,MAAI,SAAS,SAAS,GAAG;AACvB,aAAS,KAAK,cAAc,mBAAmB,SAAS,IAAI,cAAc,CAAC,CAAC;AAC5E,qBAAiB,SAAS;AAAA,EAC5B;AAEA,QAAM,QAAQ,MAAM,aAAa,SAAS,EAAE;AAC5C,MAAI,MAAM,SAAS,GAAG;AACpB,aAAS,KAAK,cAAc,oBAAoB,MAAM,IAAI,cAAc,CAAC,CAAC;AAC1E,qBAAiB,MAAM;AAAA,EACzB;AAEA,QAAM,UAAU,MAAM,YAAY,QAAW,IAAI,QAAQ,MAAS;AAClE,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,cAAc,QAAQ,OAAO,OAAK,EAAE,QAAQ;AAClD,UAAM,YAAY,QAAQ,OAAO,OAAK,CAAC,EAAE,QAAQ;AAEjD,QAAI,YAAY,SAAS,GAAG;AAC1B,YAAM,YAAY,YAAY;AAAA,QAAI,OAChC,UAAU,EAAE,IAAI,GAAG,EAAE,aAAa,YAAY,KAAK,EAAE,QAAQ,MAAM,EAAE;AAAA,MACvE;AACA,eAAS,KAAK,cAAc,uBAAuB,SAAS,CAAC;AAAA,IAC/D;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,YAAY,UAAU;AAAA,QAAI,OAC9B,GAAG,EAAE,IAAI,GAAG,EAAE,aAAa,YAAY,KAAK,EAAE,QAAQ,MAAM,EAAE;AAAA,MAChE;AACA,eAAS,KAAK,cAAc,wBAAwB,SAAS,CAAC;AAAA,IAChE;AACA,kBAAc,QAAQ;AAAA,EACxB;AAEA,QAAM,OAAO,MAAM,aAAa,SAAS,EAAE;AAC3C,MAAI,KAAK,SAAS,GAAG;AACnB,aAAS,KAAK,cAAc,QAAQ,KAAK,IAAI,cAAc,CAAC,CAAC;AAC7D,qBAAiB,KAAK;AAAA,EACxB;AAEA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,EAAE,MAAM,IAAI,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,EAAE;AAAA,EACxD;AAEA,MAAI,OAAO;AAAA,EAAa,SAAS,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,mBAAmB;AAAA;AAErE,MAAI,KAAK,SAAS,mBAAmB;AACnC,WAAO,KAAK,MAAM,GAAG,oBAAoB,EAAE,IAAI;AAAA,EACjD;AAEA,SAAO,EAAE,MAAM,OAAO,EAAE,UAAU,eAAe,SAAS,YAAY,EAAE;AAC1E;AAKA,IAAM,qBAAqB;AAC3B,IAAM,kBAAkB;AAExB,SAAS,cAAc,OAAe,OAAyB;AAC7D,SAAO,MAAM,KAAK;AAAA,EAAK,MAAM,IAAI,OAAK,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC;AAC5D;AAOA,SAAS,eAAe,OAA8B;AACpD,QAAM,MAAM,MAAM,IAAI,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAClD,QAAM,UAAU,UAAU,MAAM,UAAU;AAC1C,QAAM,WAAW,WAAW,kBACxB,iBAAO,OAAO,8CACd,WAAW,qBACT,KAAK,OAAO,WACZ;AACN,SAAO,GAAG,GAAG,KAAK,MAAM,KAAK,GAAG,QAAQ;AAC1C;AAKA,SAAS,UAAU,SAAyB;AAC1C,MAAI;AACF,UAAM,OAAO,IAAI,KAAK,OAAO,EAAE,QAAQ;AACvC,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,KAAK,OAAO,MAAM,SAAS,MAAO,KAAK,KAAK,GAAG;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAU5B,SAAS,gBAAgB,KAA4B;AACnD,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,SAAO,MAAM,UAAU,IAAI,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI;AAC3D;AAOA,eAAe,mBACb,OACA,SACe;AACf,MAAI,QAAQ,WAAW,EAAG;AAC1B,aAAW,EAAE,IAAI,KAAK,QAAQ,MAAM,GAAG,EAAE,GAAG;AAC1C,UAAM,QAAQ,MAAM,YAAY,GAAG;AACnC,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,IAAI,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AACnD,UAAM,MAAM,MAAM,MAAM,GAAG,UAAU,IAAI,MAAM,KAAK,EAAE;AACtD,QAAI,IAAK,OAAM,aAAa,KAAK,GAAG;AAAA,EACtC;AACF;AAEO,SAAS,YAAY,KAAqB;AAC/C,QAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO;AAC3C,QAAM,OAAO,oBAAI,IAAI,CAAC,aAAa,SAAS,QAAQ,OAAO,WAAW,GAAG,SAAS,EAAE,QAAQ,CAAC;AAC7F,aAAW,KAAK,MAAM,QAAQ,GAAG;AAC/B,QAAI,CAAC,KAAK,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAG,QAAO,EAAE,YAAY;AAAA,EACvE;AACA,SAAO;AACT;;;ACvaO,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoD7B,SAAS,yBACd,OACA,cACA,gBACQ;AACR,QAAM,WAAqB,CAAC;AAG5B,MAAI,gBAAgB;AACpB,MAAK,gBAAgB,aAAa,SAAS,KAAO,kBAAkB,eAAe,SAAS,GAAI;AAC9F,UAAM,QAAkB,CAAC,yBAAyB;AAClD,QAAI,gBAAgB,aAAa,SAAS,GAAG;AAC3C,YAAM,KAAK,iFAAiF;AAC5F,UAAI,QAAQ;AACZ,iBAAW,KAAK,cAAc;AAC5B,cAAM,OAAO,KAAK,EAAE,GAAG,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,MAAM,MAAM,GAAG,GAAG,IAAI,WAAM,EAAE,KAAK;AACxF,YAAI,QAAQ,KAAK,SAAS,MAAM;AAAE,gBAAM,KAAK,mBAAmB;AAAG;AAAA,QAAO;AAC1E,cAAM,KAAK,IAAI;AACf,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF;AACA,QAAI,kBAAkB,eAAe,SAAS,GAAG;AAC/C,YAAM,KAAK,0CAA0C;AACrD,UAAI,QAAQ;AACZ,iBAAW,KAAK,gBAAgB;AAC9B,cAAM,OAAO,MAAM,EAAE,QAAQ,KAAK,EAAE,KAAK,SAAS,MAAM,EAAE,KAAK,MAAM,GAAG,GAAG,IAAI,WAAM,EAAE,IAAI;AAC3F,YAAI,QAAQ,KAAK,SAAS,KAAK;AAAE,gBAAM,KAAK,mBAAmB;AAAG;AAAA,QAAO;AACzE,cAAM,KAAK,IAAI;AACf,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF;AACA,oBAAgB,MAAM,KAAK,IAAI,IAAI;AAAA,EACrC;AAGA,QAAM,WAAW;AACjB,QAAM,MAAM,KAAK,IAAI,MAAM,aAAa,QAAQ,QAAQ;AACxD,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,UAAU,MAAM,aAAa,CAAC;AACpC,QAAI,QAAS,UAAS,KAAK,SAAS,SAAS,SAAS,GAAI,CAAC,EAAE;AAC7D,UAAM,eAAe,MAAM,kBAAkB,CAAC;AAC9C,QAAI,aAAc,UAAS,KAAK,cAAc,SAAS,cAAc,GAAG,CAAC,EAAE;AAAA,EAC7E;AAEA,SAAO,GAAG,oBAAoB;AAAA;AAAA,EAE9B,aAAa,GAAG,MAAM,MAAM,sBAAsB,MAAM,GAAG;AAAA,IAAO,EAAE;AAAA;AAAA;AAAA,EAGpE,SAAS,KAAK,MAAM,CAAC;AACvB;AAKO,SAAS,2BAA2B,MAA+B;AAExE,QAAM,YAAY,KAAK,MAAM,8BAA8B,KAAK,KAAK,MAAM,eAAe;AAC1F,MAAI,CAAC,UAAW,QAAO,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE;AAEnD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,UAAU,CAAC,EAAE,KAAK,CAAC;AAC7C,UAAM,SAA0B,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE;AAE5D,QAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,iBAAW,KAAK,OAAO,UAAU;AAC/B,YAAI,OAAO,EAAE,QAAQ,YAAY,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,eAAe,UAAU;AAChG,cAAI,EAAE,cAAc,OAAO,WAAW,EAAE,GAAG,KAAK,EAAE,MAAM,UAAU,KAAK;AACrE,mBAAO,SAAS,KAAK,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,OAAO,YAAY,EAAE,WAAW,CAAC;AAAA,UAC/E;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,OAAO,OAAO,GAAG;AACjC,iBAAW,KAAK,OAAO,SAAS;AAC9B,YAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1D,iBAAO,QAAQ,KAAK;AAAA,YAClB,MAAM,EAAE,KAAK,KAAK;AAAA,YAClB,UAAU,OAAO,EAAE,aAAa,WAAW,EAAE,WAAW;AAAA,YACxD,UAAU,CAAC,CAAC,EAAE;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,EACrC;AACF;AAOO,SAAS,eAAe,OAAoB,WAA4B,SAAiB,iBAAiB,SAAyD;AACxK,MAAI,gBAAgB;AACpB,MAAI,cAAc;AAElB,aAAW,KAAK,UAAU,UAAU;AAClC,QAAI,uBAAuB,EAAE,KAAK,EAAE,KAAK,EAAG;AAC5C,UAAM,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,eAAe;AAC/D;AAAA,EACF;AAEA,aAAW,KAAK,UAAU,SAAS;AACjC,QAAI,kBAAkB,EAAE,IAAI,EAAG;AAI/B,UAAM,gBAAgB,WAAW,SAAS,SAAY;AACtD,UAAM,SAAS,MAAM,UAAU,EAAE,MAAM,EAAE,UAAU,QAAQ,EAAE,UAAU,aAAa;AACpF,QAAI,OAAO,QAAS;AAAA,EACtB;AAEA,SAAO,EAAE,UAAU,eAAe,SAAS,YAAY;AACzD;AAIA,IAAM,eAAe;AAErB,SAAS,WAAW,KAAsB;AACxC,SAAO,aAAa,KAAK,GAAG,KAAK,IAAI,UAAU,OAAO,IAAI,UAAU;AACtE;AAMA,SAAS,uBAAuB,KAAa,OAAwB;AACnE,QAAM,KAAK,IAAI,YAAY;AAC3B,QAAM,KAAK,MAAM,YAAY;AAG7B,MAAI,GAAG,SAAS,UAAU,KAAK,GAAG,SAAS,WAAW,KAAK,GAAG,SAAS,WAAW,EAAG,QAAO;AAC5F,MAAI,oEAAoE,KAAK,EAAE,EAAG,QAAO;AAGzF,MAAI,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS,aAAa,KAAK,GAAG,SAAS,YAAY,EAAG,QAAO;AAG7F,MAAI,GAAG,WAAW,QAAQ,KAAK,GAAG,WAAW,cAAc,KAAK,GAAG,WAAW,cAAc,EAAG,QAAO;AAGtG,MAAI,GAAG,SAAS,KAAK,KAAK,GAAG,SAAS,IAAK,QAAO;AAGlD,MAAI,GAAG,SAAS,cAAc,KAAK,GAAG,SAAS,aAAa,KAAK,GAAG,SAAS,eAAe,EAAG,QAAO;AAEtG,SAAO;AACT;AAKA,SAAS,kBAAkB,MAAuB;AAChD,QAAM,KAAK,KAAK,YAAY;AAG5B,MAAI,8BAA8B,KAAK,EAAE,EAAG,QAAO;AAGnD,MAAI,qDAAqD,KAAK,EAAE,EAAG,QAAO;AAG1E,MAAI,4EAA4E,KAAK,EAAE,EAAG,QAAO;AAIjG,MAAI,qDAAqD,KAAK,EAAE,KAAK,qBAAqB,KAAK,EAAE,EAAG,QAAO;AAG3G,MAAI,SAAS,KAAK,EAAE,EAAG,QAAO;AAC9B,MAAI,GAAG,SAAS,0BAA0B,KAAK,GAAG,SAAS,IAAK,QAAO;AAEvE,SAAO;AACT;AAEA,SAAS,SAAS,MAAc,KAAqB;AACnD,SAAO,KAAK,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,IAAI,WAAM;AACxD;;;AJxOA,SAAS,GAAG,MAA0B;AAAE,SAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC,EAAE;AAAG;AAQnG,SAAS,YAAe,GAAS;AAC/B,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,QAAM,IAAI,EAAE,KAAK;AACjB,MAAI,EAAE,UAAU,GAAG;AACjB,UAAM,QAAQ,EAAE,CAAC;AACjB,UAAM,OAAO,EAAE,EAAE,SAAS,CAAC;AAC3B,QAAK,UAAU,OAAO,SAAS,OAAS,UAAU,OAAO,SAAS,KAAM;AACtE,UAAI;AAEF,YAAI,UAAU,IAAK,QAAO,KAAK,MAAM,CAAC;AAAA,MACxC,QAAQ;AAAA,MAAqB;AAC7B,aAAO,EAAE,MAAM,GAAG,EAAE;AAAA,IACtB;AAAA,EACF;AACA,SAAO;AACT;AAQA,IAAM,qBAAqB,KAAK,QAAQ,GAAG,OAAO,QAAQ;AAC1D,IAAM,kBAAkB,KAAK,oBAAoB,WAAW;AAC5D,IAAM,uBAAuB,KAAK,QAAQ,GAAG,OAAO,SAAS,eAAe;AAOrE,IAAM,8BAA8B;AAmB3C,SAAS,gBAAgB,OAAgB,WAAmB,WAAoC;AAC9F,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,QAAM,UAAU,OAAO,KAAK,KAAgC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,SAAS,CAAC,CAAC;AAClG,MAAI,QAAQ,WAAW,EAAG;AAC1B,UAAQ;AAAA,IACN,wDAAwD,SAAS,YAAY,QAAQ,KAAK,IAAI,CAAC,eAAe,UAAU,KAAK,IAAI,CAAC;AAAA,EACpI;AACF;AAEA,IAAM,uBAAuB,CAAC,aAAa,mBAAmB,sBAAsB,kBAAkB;AACtG,IAAM,6BAA6B,CAAC,WAAW;AAExC,SAAS,cAAc,KAAqB;AAEjD,MAAI;AACF,UAAM,oBAAoB,KAAK,KAAK,OAAO,eAAe;AAC1D,UAAM,MAAM,aAAa,mBAAmB,OAAO;AACnD,UAAM,WAAW,KAAK,MAAM,GAAG;AAG/B,UAAM,WAAW,WAAW,WAAW;AACvC,oBAAgB,UAAU,aAAa,oBAAoB;AAC3D,QAAI,YAAY,OAAO,aAAa,YAAY,OAAO,SAAS,cAAc,YAAY,SAAS,WAAW;AAI5G,aAAO,QAAQ,KAAK,SAAS,WAAW,WAAW;AAAA,IACrD;AAGA,UAAM,gBAAgB,WAAW,iBAAiB;AAClD,oBAAgB,eAAe,mBAAmB,0BAA0B;AAC5E,QAAI,iBAAiB,OAAO,kBAAkB,YAAY,OAAO,cAAc,cAAc,YAAY,cAAc,WAAW;AAChI,aAAO,QAAQ,KAAK,cAAc,WAAW,UAAU,WAAW;AAAA,IACpE;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAOA,SAAS,oBAAoB,QAAwB,gBAA+B;AAClF,MAAI,CAAC,kBAAkB,OAAO,mBAAmB,SAAU;AAC3D,QAAM,IAAI;AAEV,MAAI,EAAE,oBAAoB,SAAS,EAAE,oBAAoB,aAAa;AACpE,WAAO,kBAAkB,EAAE;AAAA,EAC7B;AACA,MAAI,OAAO,EAAE,qBAAqB,WAAW;AAC3C,WAAO,mBAAmB,EAAE;AAAA,EAC9B;AACA,MAAI,OAAO,EAAE,uBAAuB,YAAY,EAAE,mBAAmB,KAAK,GAAG;AAC3E,WAAO,qBAAqB,EAAE,mBAAmB,KAAK;AAAA,EACxD;AACF;AAkBO,SAAS,mBAAmB,KAA8B;AAC/D,QAAM,SAAyB,CAAC;AAGhC,MAAI;AACF,UAAM,MAAM,aAAa,sBAAsB,OAAO;AACtD,UAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,wBAAoB,QAAQ,UAAU,MAAM;AAAA,EAC9C,QAAQ;AAAA,EAER;AAGA,MAAI,KAAK;AACP,QAAI;AACF,YAAM,MAAM,aAAa,KAAK,KAAK,OAAO,eAAe,GAAG,OAAO;AACnE,YAAM,WAAW,KAAK,MAAM,GAAG;AAE/B,0BAAoB,QAAQ,UAAU,UAAU,WAAW,WAAW,CAAC;AAAA,IACzE,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEe,SAAR,cAAkB,IAAkB;AACzC,MAAI,QAA4B;AAChC,MAAI,sBAAgC,CAAC;AACrC,MAAI,2BAAqC,CAAC;AAC1C,MAAI,aAAqB;AACzB,MAAI;AACJ,MAAI,YAAiB;AACrB,MAAI,iBAAyB;AAC7B,MAAI,iBAAiC,mBAAmB;AAIxD,KAAG,GAAG,iBAAiB,OAAO,QAAQ,QAAQ;AAC5C,QAAI;AACF,mBAAa,IAAI;AACjB,kBAAY;AACZ,kBAAa,IAAY,aAAc,IAAY,SAAS;AAG5D,uBAAiB,cAAc,UAAU;AACzC,uBAAiB,mBAAmB,UAAU;AAE9C,cAAQ,IAAI,YAAY,cAAc;AAKtC,4BAAsB,CAAC;AACvB,iCAA2B,CAAC;AAC5B,UAAI;AACF,cAAM,SAAS,IAAI,eAAe,UAAU;AAC5C,mBAAW,SAAS,QAAQ;AAC1B,cAAI,MAAM,SAAS,UAAW;AAC9B,gBAAM,MAAO,MAAc;AAC3B,cAAI,CAAC,IAAK;AACV,cAAI,IAAI,SAAS,QAAQ;AACvB,kBAAM,OAAO,YAAY,IAAI,OAAO;AACpC,gBAAI,KAAM,qBAAoB,KAAK,IAAI;AAAA,UACzC,WAAW,IAAI,SAAS,aAAa;AACnC,kBAAM,OAAO,YAAY,IAAI,OAAO;AACpC,gBAAI,KAAM,0BAAyB,KAAK,IAAI;AAAA,UAC9C;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,WAAW,MAAM,UAAU,GAAG;AACtC,YAAI,GAAG,UAAU,aAAa,WAAW,MAAM,QAAQ,WAAW,MAAM,OAAO,UAAU;AAIzF,mBAAW,MAAM;AACf,cAAI;AAAE,gBAAI,GAAG,UAAU,aAAa,EAAE;AAAA,UAAG,QAAQ;AAAA,UAA4B;AAAA,QAC/E,GAAG,GAAI;AAAA,MACT;AA4BA,UAAI,eAAe,qBAAqB,OAAO;AAC7C,YAAI;AACF,gBAAM,kBAAkB,IAAI,eACzB,WAAW,EACX;AAAA,YACC,CAAC,MACC,EAAE,SAAS,oBAAoB,EAAE,eAAe;AAAA,UACpD;AACF,cAAI,CAAC,iBAAiB;AACpB,kBAAM,EAAE,MAAM,OAAO,SAAS,IAAI,MAAM;AAAA,cACtC;AAAA,cACA;AAAA,cACA;AAAA;AAAA,cACA;AAAA,YACF;AACA,gBAAI,MAAM;AACR,iBAAG,YAAY;AAAA,gBACb,YAAY;AAAA,gBACZ,SAAS;AAAA,gBACT,SAAS;AAAA,gBACT,SAAS;AAAA,cACX,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,SAAS,KAAU;AACjB,UAAI,GAAG,OAAO,oCAAoC,IAAI,OAAO,IAAI,SAAS;AAAA,IAC5E;AAAA,EACF,CAAC;AAkBD,KAAG,GAAG,sBAAsB,OAAO,OAAO,QAAQ;AAChD,QAAI,CAAC,MAAO;AACZ,QAAI,eAAe,qBAAqB,MAAO;AAE/C,UAAM,EAAE,KAAK,IAAI,MAAM,kBAAkB,OAAO,IAAI,KAAK,MAAM,QAAQ,cAAc;AACrF,QAAI,CAAC,KAAM;AAEX,WAAO;AAAA,MACL,cAAc,GAAG,MAAM,YAAY;AAAA;AAAA,EAAO,IAAI;AAAA,IAChD;AAAA,EACF,CAAC;AAGD,KAAG,GAAG,aAAa,OAAO,OAAO,SAAS;AAExC,eAAW,OAAO,MAAM,UAAU;AAChC,UAAI,IAAI,SAAS,UAAU,aAAa,KAAK;AAC3C,cAAM,OAAO,YAAY,IAAI,OAAO;AACpC,YAAI,MAAM;AACR,8BAAoB,KAAK,IAAI;AAC7B,cAAI,oBAAoB,SAAS,GAAI,qBAAoB,MAAM;AAAA,QACjE;AAAA,MACF,WAAW,IAAI,SAAS,eAAe,aAAa,KAAK;AACvD,cAAM,OAAO,YAAY,IAAI,OAAO;AACpC,YAAI,MAAM;AACR,mCAAyB,KAAK,IAAI;AAClC,cAAI,yBAAyB,SAAS,GAAI,0BAAyB,MAAM;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAGD,KAAG,GAAG,yBAAyB,OAAO,QAAQ,QAAQ;AACpD,QAAI,CAAC,MAAO;AAEZ,QAAI,oBAAoB,UAAU,GAAG;AACnC,UAAI,GAAG,UAAU,aAAa,mCAA4B;AAC1D,UAAI;AACF,cAAM,mBAAmB;AAAA,MAC3B,QAAQ;AAAA,MAER,UAAE;AAIA,YAAI;AAAE,cAAI,GAAG,UAAU,aAAa,EAAE;AAAA,QAAG,QAAQ;AAAA,QAA4B;AAAA,MAC/E;AAAA,IACF;AAGA,0BAAsB,CAAC;AACvB,+BAA2B,CAAC;AAAA,EAC9B,CAAC;AAED,KAAG,GAAG,oBAAoB,YAAY;AACpC,QAAI,CAAC,MAAO;AAGZ,QAAI,WAAW;AACb,gBAAU,GAAG,UAAU,aAAa,mCAA4B;AAAA,IAClE;AAGA,QAAI,oBAAoB,UAAU,GAAG;AACnC,UAAI;AACF,cAAM,mBAAmB;AAAA,MAC3B,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,MAAM;AACZ,YAAQ;AAAA,EACV,CAAC;AAID,iBAAe,qBAAoC;AACjD,QAAI,CAAC,MAAO;AAEZ,UAAM,QAA4B;AAAA,MAChC,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,KAAK;AAAA,MACL;AAAA,IACF;AAEA,UAAM,eAAe,MAAM,aAAa,QAAW,GAAG,EAAE,IAAI,QAAM,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE;AACjG,UAAM,iBAAiB,MAAM,YAAY,QAAW,GAAG,EAAE,IAAI,QAAM,EAAE,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAC1G,UAAM,SAAS,yBAAyB,OAAO,cAAc,cAAc;AAS3E,UAAM,kBAAkB;AACxB,UAAM,kBAAkB;AACxB,QAAI;AACJ,QAAI;AACF,YAAM,cAAc,GAAG,KAAK,MAAM;AAAA,QAChC;AAAA,QAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QAAW,eAAe,sBAAsB;AAAA,MAClD,GAAG;AAAA,QACD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAED,YAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,QAChC;AAAA,QACA,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,2BAAiB;AAAA,YACf,MAAM,OAAO,IAAI,MAAM,gCAAgC,CAAC;AAAA,YACxD;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,UAAI,OAAO,SAAS,KAAK,OAAO,QAAQ;AACtC,cAAM,YAAY,2BAA2B,OAAO,MAAM;AAC1D,cAAM,OAAO,aAAa,YAAY,UAAU,IAAI;AACpD,cAAM,UAAU,eAAe,OAAQ,WAAW,WAAW,aAAa,SAAS,IAAI,QAAQ,MAAS;AACxG,YAAI,QAAQ,WAAW,QAAQ,UAAU,GAAG;AAE1C,kBAAQ,MAAM,2BAA2B,QAAQ,QAAQ,WAAW,QAAQ,OAAO,UAAU;AAAA,QAC/F;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER,UAAE;AACA,UAAI,eAAgB,cAAa,cAAc;AAAA,IACjD;AAAA,EACF;AAIA,KAAG,aAAa;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,KAAK,OAAO;AAAA,MACtB,OAAO,KAAK,OAAO,EAAE,aAAa,eAAe,CAAC;AAAA,MAClD,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,2BAA2B,CAAC,CAAC;AAAA,IAC/E,CAAC;AAAA,IACD,MAAM,QAAQ,KAAK,QAAQ,SAAS,SAAS,MAAM;AACjD,UAAI,CAAC,MAAO,QAAO,GAAG,8BAA8B;AAEpD,YAAM,UAAU,MAAM,eAAe,OAAO,OAAO,OAAO,SAAS,EAAE;AACrE,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO,GAAG,6BAA6B;AAAA,MACzC;AAEA,YAAM,OAAO,QAAQ;AAAA,QAAI,OACvB,GAAG,EAAE,GAAG,KAAK,EAAE,KAAK,iBAAiB,EAAE,UAAU,aAAa,EAAE,MAAM;AAAA,MACxE,EAAE,KAAK,IAAI;AAEX,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF,CAAC;AAED,KAAG,aAAa;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,KAAK,OAAO;AAAA,MACtB,MAAM,KAAK,OAAO,EAAE,aAAa,kDAAkD,CAAC;AAAA,MACpF,KAAK,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,gDAAgD,CAAC,CAAC;AAAA,MAChG,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAkB,CAAC,CAAC;AAAA,MACpE,MAAM,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,wBAAwB,CAAC,CAAC;AAAA,MACzE,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,0CAA0C,CAAC,CAAC;AAAA,MAC/F,UAAU,KAAK,SAAS,KAAK,QAAQ,EAAE,aAAa,qCAAqC,CAAC,CAAC;AAAA,IAC7F,CAAC;AAAA,IACD,MAAM,QAAQ,KAAK,QAAQ,SAAS,SAAS,MAAM;AACjD,UAAI,CAAC,MAAO,QAAO,GAAG,8BAA8B;AAGpD,eAAS;AAAA,QACP,GAAG;AAAA,QACH,MAAM,YAAY,OAAO,IAAI;AAAA,QAC7B,KAAK,YAAY,OAAO,GAAG;AAAA,QAC3B,OAAO,YAAY,OAAO,KAAK;AAAA,QAC/B,MAAM,YAAY,OAAO,IAAI;AAAA,QAC7B,UAAU,YAAY,OAAO,QAAQ;AAAA,MACvC;AAEA,UAAI,OAAO,SAAS,UAAU,OAAO,SAAS,UAAU;AACtD,eAAO,GAAG,iBAAiB,OAAO,IAAI,+BAA+B;AAAA,MACvE;AAEA,UAAI,OAAO,SAAS,QAAQ;AAC1B,YAAI,CAAC,OAAO,OAAO,CAAC,OAAO,OAAO;AAChC,iBAAO,GAAG,uCAAuC;AAAA,QACnD;AACA,cAAM,YAAY,OAAO,KAAK,OAAO,OAAO,MAAM,MAAM;AAGxD,cAAM,OAAO,OAAO;AACpB,cAAM,OAAO,OAAO;AACpB,cAAM,GAAG,KAAK,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,EAClD,KAAK,SAAO;AAAE,cAAI,IAAK,OAAO,aAAa,MAAM,GAAG;AAAA,QAAG,CAAC,EACxD,MAAM,MAAM;AAAA,QAAC,CAAC;AACjB,eAAO,GAAG,eAAe,OAAO,GAAG,MAAM,OAAO,KAAK,EAAE;AAAA,MACzD;AAEA,UAAI,OAAO,SAAS,UAAU;AAC5B,YAAI,CAAC,OAAO,MAAM;AAChB,iBAAO,GAAG,gCAAgC;AAAA,QAC5C;AACA,cAAM,SAAS,MAAM,UAAU,OAAO,MAAM,OAAO,YAAY,WAAW,QAAQ,OAAO,YAAY,KAAK;AAC1G,YAAI,OAAO,SAAS;AAClB,iBAAO,GAAG,mBAAmB,OAAO,IAAI,EAAE;AAAA,QAC5C;AACA,eAAO,GAAG,kBAAkB,OAAO,MAAM,MAAM,OAAO,IAAI,EAAE;AAAA,MAC9D;AAEA,aAAO,GAAG,cAAc;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,KAAG,aAAa;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,KAAK,OAAO;AAAA,MACtB,MAAM,KAAK,OAAO;AAAA,MAClB,KAAK,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,gBAAgB,CAAC,CAAC;AAAA,MAChE,IAAI,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,iBAAiB,CAAC,CAAC;AAAA,IAClE,CAAC;AAAA,IACD,MAAM,QAAQ,KAAK,QAAQ,SAAS,SAAS,MAAM;AACjD,UAAI,CAAC,MAAO,QAAO,GAAG,8BAA8B;AAEpD,eAAS;AAAA,QACP,GAAG;AAAA,QACH,MAAM,YAAY,OAAO,IAAI;AAAA,QAC7B,KAAK,YAAY,OAAO,GAAG;AAAA,QAC3B,IAAI,YAAY,OAAO,EAAE;AAAA,MAC3B;AAEA,UAAI,OAAO,SAAS,UAAU,OAAO,SAAS,UAAU;AACtD,eAAO,GAAG,iBAAiB,OAAO,IAAI,+BAA+B;AAAA,MACvE;AAEA,UAAI,OAAO,SAAS,UAAU,OAAO,KAAK;AACxC,cAAM,UAAU,MAAM,eAAe,OAAO,GAAG;AAC/C,eAAO,GAAG,UAAU,WAAW,OAAO,GAAG,KAAK,cAAc,OAAO,GAAG,EAAE;AAAA,MAC1E;AAEA,UAAI,OAAO,SAAS,YAAY,OAAO,IAAI;AACzC,cAAM,UAAU,MAAM,aAAa,OAAO,EAAE;AAC5C,eAAO,GAAG,UAAU,iBAAiB,OAAO,EAAE,KAAK,cAAc,OAAO,EAAE,EAAE;AAAA,MAC9E;AAEA,aAAO,GAAG,6CAA6C;AAAA,IACzD;AAAA,EACF,CAAC;AAED,KAAG,aAAa;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,KAAK,OAAO;AAAA,MACtB,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,qBAAqB,CAAC,CAAC;AAAA,MAC1E,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,2BAA2B,CAAC,CAAC;AAAA,IAC/E,CAAC;AAAA,IACD,MAAM,QAAQ,KAAK,QAAQ,SAAS,SAAS,MAAM;AACjD,UAAI,CAAC,MAAO,QAAO,GAAG,8BAA8B;AAEpD,YAAM,UAAU,MAAM,YAAY,OAAO,UAAU,OAAO,SAAS,EAAE;AACrE,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO,GAAG,yBAAyB;AAAA,MACrC;AAEA,YAAM,OAAO,QAAQ;AAAA,QAAI,OACvB,GAAG,EAAE,WAAW,WAAM,QAAG,KAAK,EAAE,QAAQ,KAAK,EAAE,IAAI,SAAS,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC;AAAA,MAC9E,EAAE,KAAK,IAAI;AAEX,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF,CAAC;AAED,KAAG,aAAa;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,KAAK,OAAO,CAAC,CAAC;AAAA,IAC1B,MAAM,QAAQ,KAAK,SAAS,SAAS,SAAS,MAAM;AAClD,UAAI,CAAC,MAAO,QAAO,GAAG,8BAA8B;AAEpD,YAAM,QAAQ,MAAM,MAAM;AAC1B,YAAM,OAAO,WAAW,MAAM,QAAQ,oBAAoB,MAAM,OAAO,oBAAoB,MAAM,MAAM;AAAA,MAAuB,cAAc;AAC5I,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF,CAAC;AAID,KAAG,gBAAgB,sBAAsB;AAAA,IACvC,aAAa;AAAA,IACb,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAI,CAAC,OAAO;AACV,YAAI,GAAG,OAAO,gCAAgC,SAAS;AACvD;AAAA,MACF;AAEA,UAAI,oBAAoB,SAAS,GAAG;AAClC,YAAI,GAAG,OAAO,0EAA0E,SAAS;AACjG;AAAA,MACF;AAEA,UAAI,GAAG,OAAO,mCAAmC,MAAM;AACvD,UAAI;AACF,cAAM,mBAAmB;AACzB,cAAM,QAAQ,MAAM,MAAM;AAC1B,YAAI,GAAG,OAAO,mBAAmB,MAAM,QAAQ,WAAW,MAAM,OAAO,YAAY,MAAM;AAAA,MAC3F,SAAS,KAAU;AACjB,YAAI,GAAG,OAAO,yBAAyB,IAAI,OAAO,IAAI,OAAO;AAAA,MAC/D;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAIA,SAAS,YAAY,SAA0B;AAC7C,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QACJ,OAAO,CAAC,MAAW,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,QAAQ,EAClE,IAAI,CAAC,MAAW,EAAE,IAAI,EACtB,KAAK,IAAI;AAAA,EACd;AACA,SAAO;AACT;", + "names": ["seen"] } diff --git a/package-lock.json b/package-lock.json index 351bf83..0dd1349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,13 @@ "name": "@samfp/pi-memory", "version": "1.3.3", "license": "MIT", + "dependencies": { + "@xenova/transformers": "^2.17.0" + }, "devDependencies": { "@sinclair/typebox": "^0.34.48", "@types/node": "^22.15.21", + "esbuild": "^0.27.0", "tsx": "^4.19.4", "typescript": "^5.8.3" }, @@ -573,9 +577,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -590,9 +594,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -607,9 +611,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -624,9 +628,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -641,9 +645,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -658,9 +662,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -675,9 +679,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -692,9 +696,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -709,9 +713,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -726,9 +730,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -743,9 +747,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -760,9 +764,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -777,9 +781,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -794,9 +798,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -811,9 +815,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -828,9 +832,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -845,9 +849,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -862,9 +866,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -879,9 +883,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -896,9 +900,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -913,9 +917,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -930,9 +934,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -947,9 +951,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -964,9 +968,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -981,9 +985,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -998,9 +1002,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -1039,6 +1043,15 @@ } } }, + "node_modules/@huggingface/jinja": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz", + "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@mariozechner/clipboard": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.6.tgz", @@ -1273,36 +1286,31 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1" } @@ -1311,36 +1319,31 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@silvia-odwyer/photon-node": { "version": "0.3.4", @@ -1485,6 +1488,12 @@ "node": ">=14.0.0" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", @@ -1501,6 +1510,20 @@ "license": "MIT", "peer": true }, + "node_modules/@xenova/transformers": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz", + "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.2.2", + "onnxruntime-web": "1.14.0", + "sharp": "^0.32.0" + }, + "optionalDependencies": { + "onnxruntime-node": "1.14.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1511,6 +1534,20 @@ "node": ">= 14" } }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1521,6 +1558,97 @@ "node": "18 || 20 || >=22" } }, + "node_modules/bare-events": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1539,8 +1667,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.3.1", @@ -1552,6 +1679,17 @@ "node": "*" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -1572,6 +1710,30 @@ "node": "18 || 20 || >=22" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1592,6 +1754,53 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -1620,6 +1829,39 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", @@ -1640,10 +1882,19 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1654,32 +1905,50 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" } }, "node_modules/extend": { @@ -1689,6 +1958,12 @@ "license": "MIT", "peer": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-xml-builder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", @@ -1752,6 +2027,12 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/flatbuffers": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", + "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==", + "license": "SEE LICENSE IN LICENSE.txt" + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1765,6 +2046,12 @@ "node": ">=12.20.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1823,6 +2110,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -1876,6 +2169,12 @@ "license": "ISC", "peer": true }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -1927,6 +2226,26 @@ "node": ">= 14" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -1937,6 +2256,24 @@ "node": ">= 4" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -2036,6 +2373,18 @@ "node": ">= 18" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2052,6 +2401,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -2062,6 +2420,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2069,6 +2433,30 @@ "license": "MIT", "peer": true }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -2109,6 +2497,97 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onnx-proto": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz", + "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==", + "license": "MIT", + "dependencies": { + "protobufjs": "^6.8.8" + } + }, + "node_modules/onnx-proto/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/onnx-proto/node_modules/protobufjs": { + "version": "6.11.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz", + "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/onnxruntime-common": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz", + "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz", + "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==", + "license": "MIT", + "optional": true, + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "onnxruntime-common": "~1.14.0" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz", + "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^1.12.0", + "guid-typescript": "^1.0.9", + "long": "^4.0.0", + "onnx-proto": "^4.0.4", + "onnxruntime-common": "~1.14.0", + "platform": "^1.3.6" + } + }, + "node_modules/onnxruntime-web/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/openai": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", @@ -2185,6 +2664,67 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -2232,6 +2772,45 @@ "node": ">=12.0.0" } }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -2260,8 +2839,42 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, "node_modules/signal-exit": { "version": "3.0.7", @@ -2270,6 +2883,89 @@ "license": "ISC", "peer": true }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/streamx": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strnum": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", @@ -2283,6 +2979,50 @@ "license": "MIT", "peer": true }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -2316,19 +3056,515 @@ "fsevents": "~2.3.3" } }, - "node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "peer": true + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "license": "MIT", + "peer": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2353,6 +3589,12 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -2363,6 +3605,12 @@ "node": ">= 8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", diff --git a/package.json b/package.json index c4f5a03..3667ff5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samfp/pi-memory", - "version": "1.3.3", + "version": "1.4.0", "description": "Persistent memory for pi — learns corrections, preferences, and patterns from sessions and injects them into future conversations.", "keywords": [ "pi-package", @@ -34,6 +34,9 @@ "dev": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --sourcemap --watch --packages=external --external:@mariozechner/pi-coding-agent --external:@earendil-works/pi-coding-agent --external:@sinclair/typebox", "prepare": "npm run build" }, + "dependencies": { + "@xenova/transformers": "^2.17.0" + }, "peerDependencies": { "@earendil-works/pi-coding-agent": "*", "@sinclair/typebox": "*" diff --git a/src/embedder.ts b/src/embedder.ts new file mode 100644 index 0000000..8391611 --- /dev/null +++ b/src/embedder.ts @@ -0,0 +1,101 @@ +/** + * Lazy embedding pipeline using @xenova/transformers (optional dependency). + * Gracefully degrades to FTS-only when the package is unavailable or the + * model fails to load. + * + * Model: Xenova/all-MiniLM-L6-v2 (quantized int8, ~6 MB download, 384 dims). + * Cached in ~/.cache/huggingface/hub/ after first download. + */ + +const MODEL = "Xenova/all-MiniLM-L6-v2"; +const LOAD_TIMEOUT_MS = 30_000; +const INFER_TIMEOUT_MS = 5_000; +const TEXT_CHAR_LIMIT = 512; + +let _pipe: unknown = null; +let _failed = false; + +async function getPipe(): Promise { + if (_failed) return null; + if (_pipe) return _pipe; + try { + // Dynamic import — @xenova/transformers is optional; catch if absent. + // String variable prevents TypeScript from resolving the type (it's optional). + const pkg = "@xenova/transformers"; + const mod = await import(pkg).catch(() => null) as any; + if (!mod) { + console.error("pi-memory: @xenova/transformers not installed, semantic search disabled"); + _failed = true; + return null; + } + const { pipeline, env } = mod; + env.allowRemoteModels = true; + env.useBrowserCache = false; + _pipe = await withTimeout( + pipeline("feature-extraction", MODEL, { quantized: true }), + LOAD_TIMEOUT_MS, + "model load", + ); + return _pipe; + } catch (err: unknown) { + console.error(`pi-memory: embedder unavailable (${(err as any)?.message ?? err}), using FTS-only`); + _failed = true; + return null; + } +} + +/** Compute a normalized embedding for text. Returns null on any failure. */ +export async function embed(text: string): Promise { + const pipe = await getPipe(); + if (!pipe) return null; + try { + const out = await withTimeout( + (pipe as any)(text.slice(0, TEXT_CHAR_LIMIT), { pooling: "mean", normalize: true }), + INFER_TIMEOUT_MS, + "inference", + ); + return new Float32Array((out as any).data); + } catch { + return null; + } +} + +/** + * Cosine similarity of two normalized unit vectors (dot product). + * Both vectors must have been produced with normalize:true. + */ +export function similarity(a: Float32Array, b: Float32Array): number { + let dot = 0; + const len = Math.min(a.length, b.length); + for (let i = 0; i < len; i++) dot += a[i] * b[i]; + return dot; +} + +/** + * Serialize a Float32Array to a Buffer for SQLite BLOB storage. + * Creates a copy to avoid shared-buffer aliasing issues. + */ +export function toBlob(v: Float32Array): Buffer { + return Buffer.from(new Uint8Array(v.buffer, v.byteOffset, v.byteLength)); +} + +/** + * Deserialize a SQLite BLOB back to Float32Array. + * Returns null for null/undefined input. + * Uses Uint8Array.from to produce a fresh, owned ArrayBuffer — safe when + * node:sqlite returns a Buffer whose .buffer is a shared backing store. + */ +export function fromBlob(b: Buffer | null | undefined): Float32Array | null { + if (!b) return null; + const raw = Uint8Array.from(b); // copy — handles non-zero byteOffset + return new Float32Array(raw.buffer); +} + +function withTimeout(p: Promise, ms: number, label: string): Promise { + return Promise.race([ + p, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms), + ), + ]); +} diff --git a/src/index.ts b/src/index.ts index a419ef1..e9a3116 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,14 @@ import { homedir } from "node:os"; import { readFileSync } from "node:fs"; import { MemoryStore } from "./store.js"; import { buildContextBlock, projectSlug, type InjectorConfig } from "./injector.js"; +import { embed } from "./embedder.js"; + +// Re-export internals so consumers (e.g. pi-dashboard's system-prompt route) +// can build their own context blocks without reaching into ./dist/store.js. +// The bundled `dist/index.js` inlines these, so prior `req('./dist/store.js')` +// callers were always broken. +export { MemoryStore } from "./store.js"; +export { buildContextBlock, projectSlug, type InjectorConfig } from "./injector.js"; type ToolResult = AgentToolResult; function ok(text: string): ToolResult { return { content: [{ type: "text", text }], details: {} }; } @@ -249,11 +257,8 @@ export default function (pi: ExtensionAPI) { } // Inject stored memory as a one-shot custom message BEFORE any user - // message arrives. Matches pi-knowledge-search's pattern. - // - // Skipped when `perTurnInjection: true` — in that mode the - // before_agent_start handler below takes over with per-turn semantic - // matching via systemPrompt mutation. + // message arrives. Only used when `perTurnInjection: false` is explicitly + // configured (session_start mode, opt-out from adaptive injection). // // Historical note: v1.0.x mutated event.systemPrompt in before_agent_start. // That broke provider prefix caches on every turn boundary (any drift in @@ -267,9 +272,17 @@ export default function (pi: ExtensionAPI) { // lessons, 8KB cap). Correct ordering, stable cache, simpler model. // // v1.3.x adds `perTurnInjection: true` as an opt-in to restore v1.0.x - // per-turn selective behavior (mutates systemPrompt, breaks cache on - // every turn boundary — users opt in knowing the tradeoff). - if (!injectorConfig.perTurnInjection) { + // per-turn selective behavior. + // + // v1.4.0 flips the default: per-turn semantic injection via systemPrompt + // mutation in before_agent_start. + // + // v1.5.0 introduces injectionMode: "context-hook" as the new default. + // Memory is injected as an ephemeral message via the context hook instead + // of mutating systemPrompt. System prompt is now permanently stable, + // guaranteeing cache hits on the system prompt prefix regardless of topic. + // The session_start fallback dump is opt-in via `perTurnInjection: false`. + if (injectorConfig.perTurnInjection === false) { try { const alreadyInjected = ctx.sessionManager .getEntries() @@ -278,7 +291,7 @@ export default function (pi: ExtensionAPI) { e.type === "custom_message" && e.customType === "pi-memory-context", ); if (!alreadyInjected) { - const { text, stats: injStats } = buildContextBlock( + const { text, stats: injStats } = await buildContextBlock( store, sessionCwd, undefined, // no prompt → fallback: dump all relevant memory @@ -303,22 +316,26 @@ export default function (pi: ExtensionAPI) { }); // ---------------------------------------------------------------- - // Opt-in per-turn selective injection (v1.3.0). + // Per-turn semantic injection (v1.4.0 default). + // + // Runs on every user turn, injecting memories relevant to the current + // prompt into event.systemPrompt. This is now the DEFAULT behavior; + // session_start fallback mode requires `perTurnInjection: false`. + // + // Cache stability: when the same entries are relevant across consecutive + // turns (stable topic), the injected text is identical and the provider's + // prefix cache hits. Entries are sorted deterministically in the injector + // so identical sets always produce identical text. // - // When `perTurnInjection: true` is set, run a semantic search against the - // current user prompt and append matching memory to event.systemPrompt. // MUST use systemPrompt (not { message }) — returning { message } puts the // content AFTER the user message and causes the model to respond to the // injected memory instead of the user. See v1.1.x postmortem. - // - // This breaks provider prefix caches on every turn boundary — an accepted - // cost for users who want per-query relevance from large memory stores. // ---------------------------------------------------------------- pi.on("before_agent_start", async (event, ctx) => { if (!store) return; - if (!injectorConfig.perTurnInjection) return; + if (injectorConfig.perTurnInjection === false) return; - const { text } = buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig); + const { text } = await buildContextBlock(store, ctx.cwd, event.prompt, injectorConfig); if (!text) return; return { @@ -515,6 +532,13 @@ export default function (pi: ExtensionAPI) { return ok("Both key and value required for facts"); } store.setSemantic(params.key, params.value, 0.95, "user"); + // Fire-and-forget: compute and store embedding for the new/updated entry + // so it's available for semantic search in future sessions. + const _key = params.key as string; + const _val = params.value as string; + embed(`${_key.split(".").slice(1).join(" ")} ${_val}`) + .then(vec => { if (vec) store!.setEmbedding(_key, vec); }) + .catch(() => {}); return ok(`Remembered: ${params.key} = ${params.value}`); } diff --git a/src/injector.test.ts b/src/injector.test.ts index 816d05c..a8e95ee 100644 --- a/src/injector.test.ts +++ b/src/injector.test.ts @@ -20,57 +20,57 @@ describe("buildContextBlock", () => { rmSync(tmpDir, { recursive: true, force: true }); }); - it("returns empty for empty store", () => { - const { text, stats } = buildContextBlock(store); + it("returns empty for empty store", async () => { + const { text, stats } = await buildContextBlock(store); assert.equal(text, ""); assert.equal(stats.semantic, 0); assert.equal(stats.lessons, 0); }); - it("includes preferences in fallback mode (no prompt)", () => { + it("includes preferences in fallback mode (no prompt)", async () => { store.setSemantic("pref.editor", "vim", 0.9, "user"); - const { text, stats } = buildContextBlock(store); + const { text, stats } = await buildContextBlock(store); assert.ok(text.includes("User Preferences")); assert.ok(text.includes("editor: vim")); assert.ok(stats.semantic > 0); }); - it("includes lessons with DON'T prefix for negative", () => { + it("includes lessons with DON'T prefix for negative", async () => { store.addLesson("Use sed for daily notes", "vault", "user", true); - const { text } = buildContextBlock(store); + const { text } = await buildContextBlock(store); assert.ok(text.includes("Learned Corrections")); assert.ok(text.includes("DON'T:")); }); - it("wraps in tags", () => { - const { text } = buildContextBlock(store); + it("wraps in tags", async () => { + const { text } = await buildContextBlock(store); assert.ok(text.startsWith("")); assert.ok(text.endsWith("")); }); - it("scopes project context to cwd in fallback mode", () => { + it("scopes project context to cwd in fallback mode", async () => { store.setSemantic("project.rosie.lang", "java", 0.9, "consolidation"); store.setSemantic("project.other.lang", "python", 0.5, "consolidation"); - const { text } = buildContextBlock(store, "/workplace/samfp/Rosie"); + const { text } = await buildContextBlock(store, "/workplace/samfp/Rosie"); assert.ok(text.includes("rosie.lang")); assert.ok(!text.includes("other.lang")); }); - it("fallback: excludes other-project facts even when user-set (confidence 0.95)", () => { + it("fallback: excludes other-project facts even when user-set (confidence 0.95)", async () => { // User-set facts have confidence 0.95 — the old code included ALL such // facts via `|| p.confidence >= 0.9`, bleeding unrelated project context. store.setSemantic("project.rise.hosting", "GitLab — use glab CLI", 0.95, "user"); store.setSemantic("project.ttrpg.npc", "Read Mechanics/Goons.md before generating combat stats", 0.95, "user"); store.setSemantic("project.myapp.lang", "typescript", 0.95, "user"); - const { text } = buildContextBlock(store, "/home/user/projects/myapp"); + const { text } = await buildContextBlock(store, "/home/user/projects/myapp"); assert.ok(text.includes("myapp.lang"), "should include current project fact"); assert.ok(!text.includes("rise.hosting"), "should NOT include rise facts in myapp session"); assert.ok(!text.includes("ttrpg.npc"), "should NOT include ttrpg facts in myapp session"); }); - it("fallback: exact slug match — short slug does not match longer key segment", () => { + it("fallback: exact slug match — short slug does not match longer key segment", async () => { // Regression: old substring check `key.includes('pi')` matched 'project.pipefittingjobs.*' store.setSemantic("project.pipefittingjobs.source", "adzuna + jooble", 0.9, "user"); store.setSemantic("project.pi-memory.store", "sqlite via node:sqlite", 0.9, "user"); @@ -78,7 +78,7 @@ describe("buildContextBlock", () => { // In a session with cwd slug 'pi' (project named just 'pi'), // pipefittingjobs should NOT appear. // We simulate a cwd whose slug resolves to 'pi' exactly. - const { text } = buildContextBlock(store, "/home/user/projects/pi"); + const { text } = await buildContextBlock(store, "/home/user/projects/pi"); assert.ok(!text.includes("pipefittingjobs.source"), "slug 'pi' should not match 'pipefittingjobs'"); // pi-memory also shouldn't match 'pi' slug (different slug: 'pi-memory') assert.ok(!text.includes("pi-memory.store"), "slug 'pi' should not match 'pi-memory'"); @@ -86,24 +86,24 @@ describe("buildContextBlock", () => { // ─── Selective injection tests ─────────────────────────────────── - it("selective: searches by prompt and returns relevant entries", () => { + it("selective: searches by prompt and returns relevant entries", async () => { store.setSemantic("pref.commit_style", "conventional commits", 0.9, "user"); store.setSemantic("project.rosie.di", "Dagger dependency injection", 0.95, "consolidation"); store.setSemantic("tool.sed", "use for daily note insertion", 0.9, "consolidation"); - const { text, stats } = buildContextBlock(store, undefined, "how do I make commits"); + const { text, stats } = await buildContextBlock(store, undefined, "how do I make commits"); assert.ok(text.includes("Relevant Memory")); assert.ok(text.includes("commit")); assert.ok(stats.semantic > 0); }); - it("selective: always includes lessons regardless of prompt", () => { - const { text } = buildContextBlock(store, undefined, "something totally unrelated xyz"); + it("selective: always includes lessons regardless of prompt", async () => { + const { text } = await buildContextBlock(store, undefined, "something totally unrelated xyz"); assert.ok(text.includes("Learned Corrections")); assert.ok(text.includes("DON'T:")); }); - it("selective: filters lessons by relevance when config is selective", () => { + it("selective: filters lessons by relevance when config is selective", async () => { // Add lessons in different categories store.addLesson("Always verify exploit PoC before submission", "bug-bounty", "user", false); store.addLesson("Use conventional commits for all projects", "general", "user", false); @@ -111,42 +111,42 @@ describe("buildContextBlock", () => { // With selective mode and a bug bounty prompt, should get bug-bounty + general lessons // FTS matches "bounty" against the category field and "exploit" against rule text - const { text: bbText } = buildContextBlock(store, undefined, "found an exploit on the bug bounty target", { lessonInjection: "selective" }); + const { text: bbText } = await buildContextBlock(store, undefined, "found an exploit on the bug bounty target", { lessonInjection: "selective" }); assert.ok(bbText.includes("verify exploit"), "should include bug-bounty lesson for bounty prompt"); assert.ok(bbText.includes("conventional commits"), "should include general lessons"); // With selective mode and a writing prompt, should get writing + general lessons - const { text: writeText } = buildContextBlock(store, undefined, "write a blog post about testing", { lessonInjection: "selective" }); + const { text: writeText } = await buildContextBlock(store, undefined, "write a blog post about testing", { lessonInjection: "selective" }); assert.ok(writeText.includes("fabricate"), "should include writing lesson for blog prompt"); assert.ok(writeText.includes("conventional commits"), "should include general lessons"); }); - it("selective: mode 'all' still includes all lessons", () => { - const { text } = buildContextBlock(store, undefined, "something totally unrelated xyz", { lessonInjection: "all" }); + it("selective: mode 'all' still includes all lessons", async () => { + const { text } = await buildContextBlock(store, undefined, "something totally unrelated xyz", { lessonInjection: "all" }); assert.ok(text.includes("Learned Corrections")); assert.ok(text.includes("DON'T:")); }); - it("selective: excludes other-project facts even when FTS text matches", () => { + it("selective: excludes other-project facts even when FTS text matches", async () => { // Simulate a scenario where a Prisma-related fact from project 'rise' could // match a prompt about prisma, but we're in a different project. store.setSemantic("project.rise.testing", "use fabricca (from @repo/prisma/testing) for fixtures", 0.95, "user"); store.setSemantic("project.myapp.orm", "prisma with postgres", 0.95, "user"); // In myapp context: prompt mentions prisma — should get myapp fact, not rise - const { text } = buildContextBlock(store, "/home/user/projects/myapp", "how do I set up prisma migrations"); + const { text } = await buildContextBlock(store, "/home/user/projects/myapp", "how do I set up prisma migrations"); assert.ok(text.includes("myapp.orm"), "should include current project prisma fact"); assert.ok(!text.includes("rise.testing"), "should NOT include rise's prisma fact in myapp session"); }); - it("selective: includes project context when cwd matches", () => { - const { text } = buildContextBlock(store, "/workplace/samfp/Rosie", "how do I build"); + it("selective: includes project context when cwd matches", async () => { + const { text } = await buildContextBlock(store, "/workplace/samfp/Rosie", "how do I build"); // Should find rosie entries via project slug search assert.ok(text.includes("rosie")); }); - it("selective: returns only lessons when prompt matches nothing", () => { - const { text, stats } = buildContextBlock(store, undefined, "zzzzqqqq xyzzy nonsense"); + it("selective: returns only lessons when prompt matches nothing", async () => { + const { text, stats } = await buildContextBlock(store, undefined, "zzzzqqqq xyzzy nonsense"); // No semantic hits, but lessons should still be there assert.ok(text.includes("Learned Corrections")); assert.equal(stats.semantic, 0); diff --git a/src/injector.ts b/src/injector.ts index 6e677cf..9f35817 100644 --- a/src/injector.ts +++ b/src/injector.ts @@ -7,6 +7,7 @@ * - Fallback (no prompt): dump top entries by prefix (old behavior). */ import type { MemoryStore, SemanticEntry, LessonEntry } from "./store.js"; +import { embed, similarity, fromBlob } from "./embedder.js"; import os from "node:os"; const MAX_CONTEXT_CHARS = 8000; @@ -34,22 +35,32 @@ export interface InjectorConfig { * session_start (correct message ordering, stable prefix cache). * * When true, the session_start dump is skipped and each turn runs a - * semantic search against the user's current prompt; the result is - * appended to `event.systemPrompt` in `before_agent_start`. - * - * Tradeoffs: - * - Pro: per-query relevance — facts outside the 8KB fallback dump reach - * the model when they match the current prompt. - * - Con: the system prompt mutates every turn, invalidating the provider's - * prefix cache after the system block (Bedrock / Anthropic cache_control). - * Conversation suffix gets re-written at cacheWrite rates on every user - * turn boundary (~12.5x cacheRead on Claude). + * semantic search against the user's current prompt. The injection + * strategy is then controlled by `injectionMode`. * * Correctness is preserved either way: systemPrompt is a separate field * from the messages list, so the user's question remains the last * user-role message and the model responds to it. */ perTurnInjection?: boolean; + /** + * Controls how per-turn memory is spliced into the LLM context. + * Only relevant when perTurnInjection is not false. + * + * "context-hook" (default): memory is injected as an ephemeral custom + * message immediately before the latest user message, via the + * pi.on("context") hook. The system prompt is never modified — it + * caches unconditionally. A memory content change only causes a cache + * miss at the injection point and forward (not from the system prompt + * root). Injected messages are NOT persisted to session history or + * fed back into consolidation. + * + * "system-prompt" (legacy v1.4.0 behavior): memory is appended to + * event.systemPrompt in before_agent_start. Cache-stable when memory + * content is unchanged, but a topic shift causes a cache miss that + * cascades from the system prompt root through all downstream messages. + */ + injectionMode?: "system-prompt" | "context-hook"; /** * Model string passed to `pi --model` for session-end consolidation. * When omitted, the built-in default is used. Useful for users on @@ -67,7 +78,7 @@ export interface InjectorConfig { * Build context block. When `prompt` is provided, uses selective injection * (search-based). Otherwise falls back to prefix-based dump. */ -export function buildContextBlock(store: MemoryStore, cwd?: string, prompt?: string, config?: InjectorConfig): ContextBlock { +export async function buildContextBlock(store: MemoryStore, cwd?: string, prompt?: string, config?: InjectorConfig): Promise { if (prompt?.trim()) { return buildSelectiveBlock(store, prompt, cwd, config); } @@ -76,7 +87,7 @@ export function buildContextBlock(store: MemoryStore, cwd?: string, prompt?: str // ─── Selective injection ───────────────────────────────────────────── -function buildSelectiveBlock(store: MemoryStore, prompt: string, cwd?: string, config?: InjectorConfig): ContextBlock { +async function buildSelectiveBlock(store: MemoryStore, prompt: string, cwd?: string, config?: InjectorConfig): Promise { const sections: string[] = []; let semanticCount = 0; let lessonCount = 0; @@ -113,6 +124,92 @@ function buildSelectiveBlock(store: MemoryStore, prompt: string, cwd?: string, c }) : results; + // Shared dedup set — used by both semantic search and prefix expansion below. + const seen = new Set(filteredResults.map(r => r.key)); + + // ── Semantic similarity ────────────────────────────────────────────────── + // Embed the prompt and compare against stored embeddings to surface entries + // that are conceptually related but share no keywords with the query. + // Example: "I'm hungry" → finds user.health.diet via vector proximity. + // + // Gracefully degrades: if @xenova/transformers is unavailable or the model + // hasn't been downloaded yet, embed() returns null and we skip this step. + const SEMANTIC_THRESHOLD = 0.25; + const SEMANTIC_LIMIT = 8; + const allEmbs = store.getAllEmbeddings(); + const promptVec = await embed(prompt); + const semanticKeys = new Set(); // track entries surfaced by embedding search + + if (promptVec) { + const semanticHits = allEmbs + .flatMap(({ key, embedding }) => { + const vec = fromBlob(embedding); + if (!vec) return []; + const score = similarity(promptVec, vec); + return score >= SEMANTIC_THRESHOLD ? [{ key, score }] : []; + }) + .sort((a, b) => b.score - a.score) + .slice(0, SEMANTIC_LIMIT); + + for (const { key } of semanticHits) { + // Always mark as a semantic hit for priority sorting, even if FTS already + // added this key — that way the reorder step promotes it to the front. + semanticKeys.add(key); + if (!seen.has(key)) { + const entry = store.getSemantic(key); + if (entry) { + filteredResults.push(entry); + seen.add(key); + } + } + } + + // Background: compute and store embeddings for entries that lack them. + // Fire-and-forget — does not block injection. + backfillEmbeddings(store, allEmbs.filter(r => !r.embedding)).catch(() => {}); + } + + // ── Prefix co-expansion ────────────────────────────────────────────────── + // When any key in a sibling group appears in results (FTS or semantic), + // pull siblings under the same prefix. Semantic hits get full expansion (20); + // FTS hits are capped at 5 to prevent noisy matches from flooding context. + const expandedPrefixes = new Set(); + for (const r of [...filteredResults]) { // snapshot — we push into filteredResults below + const prefix = keyDomainPrefix(r.key); + if (!prefix || expandedPrefixes.has(prefix)) continue; + expandedPrefixes.add(prefix); + const limit = semanticKeys.has(r.key) ? 20 : 5; + for (const sibling of store.listSemantic(prefix, limit)) { + if (!seen.has(sibling.key)) { + filteredResults.push(sibling); + seen.add(sibling.key); + } + } + } + + // Reorder: semantic-related entries float to the front so they survive + // MAX_CONTEXT_CHARS truncation even when FTS-matched noise fills the list. + if (semanticKeys.size > 0) { + const semanticPrefixes = new Set(); + for (const k of semanticKeys) { + const p = keyDomainPrefix(k); + if (p) semanticPrefixes.add(p); + } + const isSemanticRelated = (key: string): boolean => { + if (semanticKeys.has(key)) return true; + const p = keyDomainPrefix(key); + return p ? semanticPrefixes.has(p) : false; + }; + const priority = filteredResults.filter(r => isSemanticRelated(r.key)); + const rest = filteredResults.filter(r => !isSemanticRelated(r.key)); + // Deterministic key order within each group: same entries → same text → + // provider prefix cache hits when the topic doesn't change between turns. + priority.sort((a, b) => a.key.localeCompare(b.key)); + rest.sort((a, b) => a.key.localeCompare(b.key)); + filteredResults.length = 0; + filteredResults.push(...priority, ...rest); + } + if (filteredResults.length > 0) { sections.push(formatSection("Relevant Memory", filteredResults.map(formatSemantic))); semanticCount = filteredResults.length; @@ -320,6 +417,36 @@ const MEMORY_DRIFT_CAVEAT = `## Before acting on memory - If a recalled memory conflicts with what you observe in the current code or project state, trust what you observe now. - Memories about project state (deadlines, decisions, architecture) decay fastest — check if still relevant.`; +/** + * Extract domain prefix for sibling expansion. + * Only keys with 3+ segments expand: `user.health.diet` → `user.health`. + * 2-segment keys like `pref.editor` or `user.fitness` are leaf-level. + */ +function keyDomainPrefix(key: string): string | null { + const parts = key.split("."); + return parts.length >= 3 ? parts.slice(0, 2).join(".") : null; +} + +/** + * Background: compute and store embeddings for entries that are missing them. + * Runs after a successful semantic search, populating the DB for future use. + * Capped at 10 entries per call to avoid blocking the event loop. + */ +async function backfillEmbeddings( + store: MemoryStore, + missing: Array<{ key: string }>, +): Promise { + if (missing.length === 0) return; + for (const { key } of missing.slice(0, 10)) { + const entry = store.getSemantic(key); + if (!entry) continue; + // Use the human-readable key suffix + value as embedding input + const displayKey = key.split(".").slice(1).join(" "); + const vec = await embed(`${displayKey} ${entry.value}`); + if (vec) store.setEmbedding(key, vec); + } +} + export function projectSlug(cwd: string): string { const parts = cwd.split("/").filter(Boolean); const skip = new Set(["workplace", "local", "home", "src", "scratch", os.userInfo().username]); diff --git a/src/store.ts b/src/store.ts index 1a2ce8c..3df013f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -102,6 +102,12 @@ export class MemoryStore { } catch { // Column already exists — ignore } + // Migration: add embedding column for semantic vector search + try { + this.db.exec(`ALTER TABLE semantic ADD COLUMN embedding BLOB`); + } catch { + // Column already exists — ignore + } // FTS5 virtual tables for semantic + lesson search (optional — node:sqlite may lack FTS5) try { @@ -200,6 +206,27 @@ export class MemoryStore { }); } + /** + * Store a pre-computed embedding for a key. + * Converts Float32Array → Buffer for SQLite BLOB storage. + */ + setEmbedding(key: string, embedding: Float32Array): void { + const normalized = key.toLowerCase(); + const blob = Buffer.from(new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength)); + this.db.prepare("UPDATE semantic SET embedding = ? WHERE key = ?").run(blob, normalized); + } + + /** + * Return all semantic keys with their raw embedding BLOBs. + * Used for in-memory cosine similarity at query time. + * Entries without an embedding have embedding = null. + */ + getAllEmbeddings(): Array<{ key: string; embedding: Buffer | null }> { + return this.db + .prepare("SELECT key, embedding FROM semantic ORDER BY updated_at DESC") + .all() as unknown as Array<{ key: string; embedding: Buffer | null }>; + } + listSemantic(prefix?: string, limit: number = 100): SemanticEntry[] { if (prefix) { return this.db.prepare("SELECT * FROM semantic WHERE key LIKE ? ORDER BY updated_at DESC LIMIT ?")