/** * Per-Space Knowledge Index — ranked retrieval for MI context injection. * * Instead of dumping all 35 module summaries into every prompt, * this index builds KnowledgeEntry objects from each module and * returns only the top-N most relevant entries for a given query * using trigram similarity + recency boost + category weighting. */ import { trigrams, jaccardSimilarity } from "./mi-trigrams"; import { getUpcomingEventsForMI } from "../modules/rcal/mod"; import { getRecentVaultNotesForMI } from "../modules/rnotes/mod"; import { getRecentTasksForMI } from "../modules/rtasks/mod"; import { getRecentCampaignsForMI } from "../modules/rsocials/mod"; import { getRecentContactsForMI } from "../modules/rnetwork/mod"; import { getRecentThreadsForMI } from "../modules/rinbox/mod"; import { getRecentCommitmentsForMI } from "../modules/rtime/mod"; import { getRecentFilesForMI } from "../modules/rfiles/mod"; import { getUpcomingRemindersForMI } from "../modules/rschedule/mod"; import { getMapPinsForMI } from "../modules/rmaps/mod"; import { getRecentMeetingsForMI } from "../modules/rmeets/mod"; import { getRecentVideosForMI } from "../modules/rtube/mod"; import { getRecentMessagesForMI } from "../modules/rchats/mod"; import { getRecentAgentPostsForMI } from "../modules/ragents/mod"; import { getRecentPublicationsForMI } from "../modules/rpubs/mod"; import { getRecentDesignsForMI } from "../modules/rswag/mod"; import { getRecentSheetsForMI } from "../modules/rsheets/mod"; import { getRecentDocsForMI } from "../modules/rdocs/mod"; import { getRecentSessionsForMI } from "../modules/rdesign/mod"; import { getSharedAlbumsForMI } from "../modules/rphotos/mod"; import { getRecentFlowsForMI } from "../modules/rflows/mod"; import { getRecentIntentsForMI } from "../modules/rexchange/mod"; import { getRecentOrdersForMI } from "../modules/rcart/mod"; import { getActiveProposalsForMI } from "../modules/rvote/mod"; import { getRecentBooksForMI } from "../modules/rbooks/mod"; import { getRecentSplatsForMI } from "../modules/rsplat/mod"; import { getRecentTripsForMI } from "../modules/rtrips/mod"; import { getActiveListingsForMI } from "../modules/rbnb/mod"; import { getActiveVehiclesForMI } from "../modules/rvnb/mod"; import { getForumInstancesForMI } from "../modules/rforum/mod"; import { getRecentChoiceSessionsForMI } from "../modules/rchoices/mod"; import { getActivePromptsForMI } from "../modules/crowdsurf/mod"; import { getGovShapesForMI } from "../modules/rgov/mod"; import { getCrdtTokensForMI } from "../modules/rwallet/mod"; import { getCanvasSummaryForMI } from "../modules/rspace/mod"; import { getDataSummaryForMI } from "../modules/rdata/mod"; // ── Types ── type KnowledgeCategory = | "tasks" | "time" | "content" | "people" | "social" | "commerce" | "community" | "spatial" | "media" | "infra" | "canvas"; interface KnowledgeEntry { id: string; moduleId: string; category: KnowledgeCategory; title: string; detail: string; tags: string[]; timestamp: number; formatted: string; } // ── Category weights (higher = more likely to surface) ── const CATEGORY_WEIGHT: Record = { tasks: 1.5, time: 1.4, content: 1.3, people: 1.2, social: 1.1, commerce: 0.9, community: 0.8, spatial: 0.7, media: 0.7, infra: 0.6, canvas: 0.5, }; const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000; const CACHE_TTL = 5 * 60 * 1000; // 5 minutes const PER_MODULE_LIMIT = 12; // ── SpaceKnowledgeIndex ── class SpaceKnowledgeIndex { #cache = new Map(); /** Get ranked context string for injection into MI system prompt. */ getRankedContext(space: string, query: string, topN = 18): string { const { entries } = this.#getOrBuild(space); if (entries.length === 0) return ""; const ranked = this.#rank(entries, query, topN); if (ranked.length === 0) return ""; // Group by module for readability const groups = new Map(); for (const entry of ranked) { if (!groups.has(entry.moduleId)) groups.set(entry.moduleId, []); groups.get(entry.moduleId)!.push(entry.formatted); } const lines: string[] = []; for (const [moduleId, items] of groups) { lines.push(`- [${moduleId}]`); for (const item of items) { lines.push(` ${item}`); } } return `\n- Space data (ranked by relevance):\n${lines.join("\n")}`; } /** Invalidate cache for a space (called on doc changes). */ invalidate(space: string): void { this.#cache.delete(space); } #getOrBuild(space: string): { entries: KnowledgeEntry[]; builtAt: number } { const cached = this.#cache.get(space); if (cached && Date.now() - cached.builtAt < CACHE_TTL) return cached; const entries = this.#buildIndex(space); const result = { entries, builtAt: Date.now() }; this.#cache.set(space, result); return result; } #buildIndex(space: string): KnowledgeEntry[] { const entries: KnowledgeEntry[] = []; const now = Date.now(); // Each adapter is wrapped in try/catch so one module failure doesn't block others try { for (const e of getUpcomingEventsForMI(space, 14, PER_MODULE_LIMIT)) { const date = e.allDay ? e.start.split("T")[0] : e.start; let line = `${date}: ${e.title}`; if (e.location) line += ` (${e.location})`; else if (e.isVirtual) line += ` (virtual)`; if (e.tags?.length) line += ` [${e.tags.join(", ")}]`; entries.push({ id: `rcal:${e.title}:${e.start}`, moduleId: "rcal", category: "time", title: e.title, detail: `${e.location || ""} ${(e.tags || []).join(" ")}`, tags: e.tags || [], timestamp: Date.parse(e.start) || now, formatted: line, }); } } catch {} try { for (const n of getRecentVaultNotesForMI(space, PER_MODULE_LIMIT)) { const line = `"${n.title}" in ${n.vaultName} (${n.path})${n.tags.length ? ` [${n.tags.join(", ")}]` : ""}`; entries.push({ id: `rnotes:${n.path}`, moduleId: "rnotes", category: "content", title: n.title, detail: `${n.vaultName} ${n.path}`, tags: n.tags, timestamp: now, formatted: line, }); } } catch {} try { for (const t of getRecentTasksForMI(space, PER_MODULE_LIMIT)) { const line = `"${t.title}" [${t.status}]${t.priority ? ` (${t.priority})` : ""}${t.description ? `: ${t.description.slice(0, 80)}` : ""}`; entries.push({ id: `rtasks:${t.id}`, moduleId: "rtasks", category: "tasks", title: t.title, detail: t.description?.slice(0, 200) || "", tags: [t.status, t.priority].filter(Boolean) as string[], timestamp: t.createdAt || now, formatted: line, }); } } catch {} try { for (const c of getRecentCampaignsForMI(space, PER_MODULE_LIMIT)) { const line = `"${c.title}" (${c.platforms.join(", ")}, ${c.postCount} posts)`; entries.push({ id: `rsocials:${c.id}`, moduleId: "rsocials", category: "social", title: c.title, detail: c.platforms.join(" "), tags: c.platforms, timestamp: c.updatedAt || now, formatted: line, }); } } catch {} try { for (const c of getRecentContactsForMI(space, PER_MODULE_LIMIT)) { const line = `${c.name} (${c.role})${c.tags.length ? ` [${c.tags.join(", ")}]` : ""}`; entries.push({ id: `rnetwork:${c.did}`, moduleId: "rnetwork", category: "people", title: c.name, detail: c.role, tags: c.tags, timestamp: now, formatted: line, }); } } catch {} try { for (const t of getRecentThreadsForMI(space, PER_MODULE_LIMIT)) { const line = `"${t.subject}" from ${t.fromAddress || "unknown"} [${t.status}]${t.isRead ? "" : " (unread)"}`; entries.push({ id: `rinbox:${t.subject}`, moduleId: "rinbox", category: "people", title: t.subject, detail: t.fromAddress || "", tags: [t.status, t.isRead ? "" : "unread"].filter(Boolean), timestamp: t.receivedAt || now, formatted: line, }); } } catch {} try { for (const c of getRecentCommitmentsForMI(space, PER_MODULE_LIMIT)) { const line = `${c.memberName}: ${c.hours}h ${c.skill} — ${c.desc.slice(0, 80)}`; entries.push({ id: `rtime:${c.id}`, moduleId: "rtime", category: "people", title: `${c.memberName} ${c.skill}`, detail: c.desc.slice(0, 200), tags: [c.skill], timestamp: now, formatted: line, }); } } catch {} try { for (const f of getRecentFilesForMI(space, PER_MODULE_LIMIT)) { const line = `${f.title || f.originalFilename} (${f.mimeType || "unknown"})`; entries.push({ id: `rfiles:${f.id}`, moduleId: "rfiles", category: "content", title: f.title || f.originalFilename, detail: f.mimeType || "", tags: [], timestamp: f.updatedAt || now, formatted: line, }); } } catch {} try { for (const r of getUpcomingRemindersForMI(space, 14, PER_MODULE_LIMIT)) { const date = new Date(r.remindAt).toISOString().split("T")[0]; const line = `${date}: ${r.title}${r.sourceModule ? ` (from ${r.sourceModule})` : ""}`; entries.push({ id: `rschedule:${r.id}`, moduleId: "rschedule", category: "time", title: r.title, detail: r.sourceModule || "", tags: [r.sourceModule].filter(Boolean) as string[], timestamp: r.remindAt || now, formatted: line, }); } } catch {} try { for (const p of getMapPinsForMI(space, PER_MODULE_LIMIT)) { const line = `"${p.label}" (${p.type}) at ${p.lat.toFixed(4)}, ${p.lng.toFixed(4)}`; entries.push({ id: `rmaps:${p.id}`, moduleId: "rmaps", category: "spatial", title: p.label, detail: p.type, tags: [p.type], timestamp: p.createdAt || now, formatted: line, }); } } catch {} try { for (const m of getRecentMeetingsForMI(space, PER_MODULE_LIMIT)) { const line = `"${m.title}" (${m.participantCount} participants)`; entries.push({ id: `rmeets:${m.id}`, moduleId: "rmeets", category: "people", title: m.title, detail: "", tags: [], timestamp: m.createdAt || now, formatted: line, }); } } catch {} try { for (const v of getRecentVideosForMI(space, PER_MODULE_LIMIT)) { const line = `"${v.name}" (${v.entryCount} entries)`; entries.push({ id: `rtube:${v.id}`, moduleId: "rtube", category: "media", title: v.name, detail: "", tags: [], timestamp: v.createdAt || now, formatted: line, }); } } catch {} try { for (const m of getRecentMessagesForMI(space, PER_MODULE_LIMIT)) { const line = `[${m.channel}] ${m.author}: ${m.content.slice(0, 80)}`; entries.push({ id: `rchats:${m.id}`, moduleId: "rchats", category: "social", title: `${m.channel} ${m.author}`, detail: m.content.slice(0, 200), tags: [m.channel], timestamp: m.createdAt || now, formatted: line, }); } } catch {} try { for (const p of getRecentAgentPostsForMI(space, PER_MODULE_LIMIT)) { const line = `[${p.channel}] ${p.author}: ${p.content.slice(0, 80)}${p.hasPayload ? " [+data]" : ""}`; entries.push({ id: `ragents:${p.id}`, moduleId: "ragents", category: "social", title: `${p.channel} ${p.author}`, detail: p.content.slice(0, 200), tags: [p.channel], timestamp: p.createdAt || now, formatted: line, }); } } catch {} try { for (const p of getRecentPublicationsForMI(space, PER_MODULE_LIMIT)) { const line = `"${p.title}" by ${p.author} (${p.format})`; entries.push({ id: `rpubs:${p.id}`, moduleId: "rpubs", category: "content", title: p.title, detail: `${p.author} ${p.format}`, tags: [p.format], timestamp: p.updatedAt || now, formatted: line, }); } } catch {} try { for (const d of getRecentDesignsForMI(space, PER_MODULE_LIMIT)) { const line = `"${d.title}" (${d.productType}, ${d.status})`; entries.push({ id: `rswag:${d.id}`, moduleId: "rswag", category: "media", title: d.title, detail: `${d.productType} ${d.status}`, tags: [d.productType, d.status], timestamp: d.createdAt || now, formatted: line, }); } } catch {} try { for (const s of getRecentSheetsForMI(space, PER_MODULE_LIMIT)) { const line = `"${s.name}" (${s.cellCount} cells)`; entries.push({ id: `rsheets:${s.id}`, moduleId: "rsheets", category: "infra", title: s.name, detail: "", tags: [], timestamp: s.updatedAt || now, formatted: line, }); } } catch {} try { for (const n of getRecentDocsForMI(space, PER_MODULE_LIMIT)) { const line = `"${n.title}" (${n.type}${n.tags.length ? `, tags: ${n.tags.join(", ")}` : ""}): ${n.contentPlain.slice(0, 100)}`; entries.push({ id: `rdocs:${n.id}`, moduleId: "rdocs", category: "content", title: n.title, detail: n.contentPlain.slice(0, 200), tags: n.tags, timestamp: n.updatedAt || now, formatted: line, }); } } catch {} try { for (const s of getRecentSessionsForMI(space, PER_MODULE_LIMIT)) { const line = `"${s.title}" (${s.pageCount} pages, ${s.frameCount} frames)`; entries.push({ id: `rdesign:${s.title}`, moduleId: "rdesign", category: "media", title: s.title, detail: "", tags: [], timestamp: now, formatted: line, }); } } catch {} try { for (const a of getSharedAlbumsForMI(space, PER_MODULE_LIMIT)) { const line = `"${a.name}"`; entries.push({ id: `rphotos:${a.id}`, moduleId: "rphotos", category: "media", title: a.name, detail: "", tags: [], timestamp: a.sharedAt || now, formatted: line, }); } } catch {} try { for (const f of getRecentFlowsForMI(space, PER_MODULE_LIMIT)) { const line = `"${f.name}" (${f.nodeCount} nodes)`; entries.push({ id: `rflows:${f.id}`, moduleId: "rflows", category: "infra", title: f.name, detail: "", tags: [], timestamp: f.createdAt || now, formatted: line, }); } } catch {} try { for (const i of getRecentIntentsForMI(space, PER_MODULE_LIMIT)) { const line = `${i.side} ${i.tokenId} [${i.status}]`; entries.push({ id: `rexchange:${i.id}`, moduleId: "rexchange", category: "commerce", title: `${i.side} ${i.tokenId}`, detail: i.status, tags: [i.side, i.status], timestamp: i.createdAt || now, formatted: line, }); } } catch {} try { for (const o of getRecentOrdersForMI(space, PER_MODULE_LIMIT)) { const line = `"${o.title}" [${o.status}]`; entries.push({ id: `rcart:${o.id}`, moduleId: "rcart", category: "commerce", title: o.title, detail: o.status, tags: [o.status], timestamp: o.createdAt || now, formatted: line, }); } } catch {} try { for (const p of getActiveProposalsForMI(space, PER_MODULE_LIMIT)) { const line = `"${p.title}" [${p.status}] (${p.voteCount} votes)`; entries.push({ id: `rvote:${p.id}`, moduleId: "rvote", category: "community", title: p.title, detail: p.status, tags: [p.status], timestamp: p.createdAt || now, formatted: line, }); } } catch {} try { for (const b of getRecentBooksForMI(space, PER_MODULE_LIMIT)) { const line = `"${b.title}" by ${b.author}`; entries.push({ id: `rbooks:${b.id}`, moduleId: "rbooks", category: "content", title: b.title, detail: b.author, tags: [b.author], timestamp: b.createdAt || now, formatted: line, }); } } catch {} try { for (const s of getRecentSplatsForMI(space, PER_MODULE_LIMIT)) { const line = `"${s.title}" (${s.format})`; entries.push({ id: `rsplat:${s.id}`, moduleId: "rsplat", category: "media", title: s.title, detail: s.format, tags: [s.format], timestamp: s.createdAt || now, formatted: line, }); } } catch {} try { for (const t of getRecentTripsForMI(space, PER_MODULE_LIMIT)) { const line = `"${t.title}" [${t.status}] (${t.destinationCount} destinations)`; entries.push({ id: `rtrips:${t.id}`, moduleId: "rtrips", category: "spatial", title: t.title, detail: t.status, tags: [t.status], timestamp: t.createdAt || now, formatted: line, }); } } catch {} try { for (const l of getActiveListingsForMI(space, PER_MODULE_LIMIT)) { const line = `"${l.title}" (${l.type}, ${l.economy})`; entries.push({ id: `rbnb:${l.id}`, moduleId: "rbnb", category: "infra", title: l.title, detail: `${l.type} ${l.economy}`, tags: [l.type, l.economy], timestamp: l.createdAt || now, formatted: line, }); } } catch {} try { for (const v of getActiveVehiclesForMI(space, PER_MODULE_LIMIT)) { const line = `"${v.title}" (${v.type}, ${v.economy})`; entries.push({ id: `rvnb:${v.id}`, moduleId: "rvnb", category: "infra", title: v.title, detail: `${v.type} ${v.economy}`, tags: [v.type, v.economy], timestamp: v.createdAt || now, formatted: line, }); } } catch {} try { for (const i of getForumInstancesForMI(space, PER_MODULE_LIMIT)) { const line = `"${i.name}" (${i.domain || "pending"}) [${i.status}]`; entries.push({ id: `rforum:${i.id}`, moduleId: "rforum", category: "community", title: i.name, detail: i.domain || "", tags: [i.status], timestamp: i.createdAt || now, formatted: line, }); } } catch {} try { for (const s of getRecentChoiceSessionsForMI(space, PER_MODULE_LIMIT)) { const line = `"${s.title}" (${s.type}, ${s.optionCount} options)`; entries.push({ id: `rchoices:${s.id}`, moduleId: "rchoices", category: "community", title: s.title, detail: s.type, tags: [s.type], timestamp: s.createdAt || now, formatted: line, }); } } catch {} try { for (const p of getActivePromptsForMI(space, PER_MODULE_LIMIT)) { const line = `"${p.text.slice(0, 60)}" (${p.swipeCount}/${p.threshold})`; entries.push({ id: `crowdsurf:${p.id}`, moduleId: "crowdsurf", category: "community", title: p.text.slice(0, 80), detail: "", tags: [], timestamp: p.createdAt || now, formatted: line, }); } } catch {} try { for (const s of getGovShapesForMI(space)) { const line = `${s.type}: ${s.count}`; entries.push({ id: `rgov:${s.type}`, moduleId: "rgov", category: "community", title: s.type, detail: `${s.count} shapes`, tags: [s.type], timestamp: now, formatted: line, }); } } catch {} try { for (const t of getCrdtTokensForMI(space, PER_MODULE_LIMIT)) { const line = `${t.symbol} (${t.name}): supply ${t.totalSupply}`; entries.push({ id: `rwallet:${t.tokenId}`, moduleId: "rwallet", category: "commerce", title: `${t.symbol} ${t.name}`, detail: `supply ${t.totalSupply}`, tags: [t.symbol], timestamp: now, formatted: line, }); } } catch {} try { const summary = getCanvasSummaryForMI(space); if (summary.length > 0 && summary[0].totalShapes > 0) { const s = summary[0]; const top = s.typeBreakdown.slice(0, 5).map((t) => `${t.type}: ${t.count}`).join(", "); entries.push({ id: "rspace:canvas", moduleId: "rspace", category: "canvas", title: "Canvas summary", detail: top, tags: s.typeBreakdown.map((t) => t.type), timestamp: now, formatted: `${s.totalShapes} shapes (${top})`, }); } } catch {} try { const data = getDataSummaryForMI(space); if (data.length > 0) { entries.push({ id: "rdata:summary", moduleId: "rdata", category: "infra", title: "Analytics", detail: data.map((d) => `${d.label}: ${d.value}`).join(", "), tags: ["analytics"], timestamp: now, formatted: `Analytics: ${data.map((d) => `${d.label}=${d.value}`).join(", ")}`, }); } } catch {} return entries; } #rank(entries: KnowledgeEntry[], query: string, topN: number): KnowledgeEntry[] { const now = Date.now(); if (!query.trim()) { // No query — sort by recency * category weight return entries .map((e) => ({ entry: e, score: (0.5 + 0.2 * Math.exp(-(now - e.timestamp) / SEVEN_DAYS)) * CATEGORY_WEIGHT[e.category], })) .sort((a, b) => b.score - a.score) .slice(0, topN) .map((r) => r.entry); } const queryTrigrams = trigrams(query); return entries .map((e) => { const text = `${e.title} ${e.detail} ${e.tags.join(" ")}`; const entryTrigrams = trigrams(text); const sim = jaccardSimilarity(queryTrigrams, entryTrigrams); const recencyBoost = 0.2 * Math.exp(-(now - e.timestamp) / SEVEN_DAYS); const score = (sim + recencyBoost) * CATEGORY_WEIGHT[e.category]; return { entry: e, score }; }) .sort((a, b) => b.score - a.score) .slice(0, topN) .map((r) => r.entry); } } export const spaceKnowledgeIndex = new SpaceKnowledgeIndex();