From e63a75ae8a84d3d2586a34d757041537a9a7726f Mon Sep 17 00:00:00 2001 From: Franz Rolfsvaag Date: Thu, 18 Jun 2026 22:42:25 +0200 Subject: [PATCH] Wire OKF into AI context --- TODO.md | 9 +++++++-- plugins/lumi_ai/backend/ai_provider.js | 8 +++++++- plugins/lumi_ai/index.js | 12 +++++++++--- plugins/lumi_ai/tests/verify.js | 10 +++++++++- plugins/okf/backend/okf_store.js | 4 ++-- plugins/okf/index.js | 21 +++++++++++++++++++++ plugins/okf/tests/verify.js | 22 ++++++++++++++++++++++ 7 files changed, 77 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index db34850..2bf8d31 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ This file tracks larger Lumi work that cannot safely be completed in one pass. K ## OKF Knowledge System -Current state on `experimental-okf` as of 2026-06-18: standalone OKF plugin work is implemented locally and not pushed. The plugin provides SQLite-backed entries, sanitized Markdown, logged-in browsing, role-gated details, admin/editor management, per-user OKF permission grants through the shared core user lookup, workflow actions, version history, version restore, and role-preview tooling. File-backed OKF directories, indexing, AI prompt integration, and feedback-to-correction flow remain for later passes. +Current state on `experimental-okf` as of 2026-06-18: standalone OKF plugin work is implemented locally and not pushed. The plugin provides SQLite-backed entries, sanitized Markdown, logged-in browsing, role-gated details, admin/editor management, per-user OKF permission grants through the shared core user lookup, workflow actions, version history, version restore, role-preview tooling, and role-aware Lumi AI context integration through the generic AI context provider hook. File-backed OKF directories, indexing, and feedback-to-correction flow remain for later passes. ### Implemented Locally @@ -30,11 +30,13 @@ Current state on `experimental-okf` as of 2026-06-18: standalone OKF plugin work - Migrated OKF permission grants, Lumi AI access grants, moderation target fields, Economy banking transfers, and Economy balance adjustment fields to the shared user lookup. - Fixed the moderation target internal username search by removing its page-local picker and using the shared live lookup. - Added a local `global.lumiFrameworks.okf.search(...)` integration point for later AI context provider wiring. +- Wired OKF into Lumi AI prompt context through the existing generic `registerContext` hook. +- Made Lumi AI context providers receive user/message/scope/origin context so per-user OKF permissions can be enforced. +- Ensured OKF AI context includes only published and approved entries and only fields visible to the requesting user. - Added `plugins/okf/tests/verify.js`. ### Remaining Work -- Wire OKF into Lumi AI context retrieval through a generic context provider path with role-aware filtering. - Add file-backed OKF Markdown/frontmatter directories for generated core/plugin/community/corrections knowledge. - Add Markdown/frontmatter parsing with stable IDs, scopes, status, priority, tags, generated/editable flags, and timestamps. - Add OKF indexing/chunking with path/id/heading/score/excerpt source metadata. @@ -165,6 +167,8 @@ Current state on `experimental-okf` as of 2026-06-18: standalone OKF plugin work - Review localization/translation keys if present so simplified wording remains consistent across languages. - IMPORTANT: on mobile, the navbar extends the viewport vertically, meaning users can only see as far down as "log in with discord". The navbar needs to be the full height of the viewport, and ensure all contained elements are visible. Should contents overflow the viewport, integrate a very slim and minimalistic scrollbar. - /plugins/moderation -> User notes should be deletable by admins +- Theme editor "Popout" button does not create the popout preview as intended. +- Users not logged in see the browser context menu instead of the lumi-custom context menu. ## Core Feedback System @@ -580,6 +584,7 @@ Current state on `experimental-feedback-system` as of 2026-06-18: the core feedb ## Done +- 2026-06-18: Continued `experimental-okf` locally: wired OKF into Lumi AI through the generic context provider hook, made AI context providers user/message-aware, and verified OKF AI context only includes published/approved entries and fields visible to the requester. No repo push yet. - 2026-06-18: Continued `experimental-okf` locally: added shared core live user lookup, migrated OKF/Lumi AI/Moderation/Economy user-selection fields to it, fixed the moderation target search field, added OKF restore-from-version, and added OKF role-preview cards. No repo push yet. - 2026-06-18: Started `experimental-okf` with a standalone OKF plugin: role-gated SQLite entries, sanitized Markdown browsing, admin/editor management, per-user OKF grants, workflow actions, version snapshots, and first verification coverage. No repo push yet. - 2026-06-18: Emergency patched `/admin/navigation` so the Unassigned items pool stays sticky while editing sections on desktop, and aligned Save navigation / Reset to default actions in a shared horizontal Lumi button group. diff --git a/plugins/lumi_ai/backend/ai_provider.js b/plugins/lumi_ai/backend/ai_provider.js index 042a457..7c14a59 100644 --- a/plugins/lumi_ai/backend/ai_provider.js +++ b/plugins/lumi_ai/backend/ai_provider.js @@ -163,7 +163,13 @@ class AiProvider { role, message: effectiveMessage, requestClass, - contextBlocks: this.getContext(role), + contextBlocks: this.getContext({ + role, + user, + message: effectiveMessage, + originContext, + scope + }), correctionContext, repoContext, originContext, diff --git a/plugins/lumi_ai/index.js b/plugins/lumi_ai/index.js index 66172f4..34e8d5e 100644 --- a/plugins/lumi_ai/index.js +++ b/plugins/lumi_ai/index.js @@ -99,9 +99,12 @@ module.exports = { }); const contextProviders = new Map(); const frontendVisibility = new Map(); - const getSafeContext = (role) => [...contextProviders.values()].flatMap((fn) => { - try { return normalizeContext(fn({ role })); } catch { return []; } - }); + const getSafeContext = (input = {}) => { + const context = typeof input === "string" ? { role: input } : input; + return [...contextProviders.values()].flatMap((fn) => { + try { return normalizeContext(fn(context)); } catch { return []; } + }); + }; const runtime = new RuntimeManager({ getConfig: () => config, getModel, @@ -245,6 +248,9 @@ module.exports = { global.lumiFrameworks = global.lumiFrameworks || {}; global.lumiFrameworks.ai = api; global.lumiFrameworks.lumi_ai = api; + if (typeof global.lumiFrameworks.okf?.context === "function") { + api.registerContext("okf", global.lumiFrameworks.okf.context); + } const router = web.createRouter(); router.use("/assets", express.static(path.join(__dirname, "public"))); diff --git a/plugins/lumi_ai/tests/verify.js b/plugins/lumi_ai/tests/verify.js index 0d28ee8..62bb9b8 100644 --- a/plugins/lumi_ai/tests/verify.js +++ b/plugins/lumi_ai/tests/verify.js @@ -1129,6 +1129,7 @@ async function run() { let assembledPrompt = ""; let assembledMessages = []; let generatedTokenBudget = 0; + let contextInput = null; const longContext = "context-marker ".repeat(900); const promptProvider = new AiProvider({ getConfig: () => ({ @@ -1149,7 +1150,10 @@ async function run() { queue, tools: registry, metrics: fakeMetrics, - getContext: () => [longContext] + getContext: (input) => { + contextInput = input; + return [longContext, `user-aware-context:${input.user.id}:${input.message}`]; + } }); await promptProvider.generate({ message: "Write a Lumi custom command in JavaScript that greets the user", @@ -1158,6 +1162,10 @@ async function run() { history: [{ role: "user", content: "Earlier question" }, { role: "assistant", content: "Earlier answer" }] }); assert(assembledPrompt.includes(longContext)); + assert(assembledPrompt.includes("user-aware-context:u1:Write a Lumi custom command")); + assert.equal(contextInput.role, "user"); + assert.equal(contextInput.user.id, "u1"); + assert.equal(contextInput.scope, "assistant"); assert.equal(assembledPrompt.includes("Maximum answer length: 100"), false); assert(assembledPrompt.includes("non-standard modules such as opencv, numpy, requests, discord.py")); assert(assembledPrompt.includes("function run(ctx)")); diff --git a/plugins/okf/backend/okf_store.js b/plugins/okf/backend/okf_store.js index 3c95c8f..e649f38 100644 --- a/plugins/okf/backend/okf_store.js +++ b/plugins/okf/backend/okf_store.js @@ -352,7 +352,7 @@ function searchForAi(db, query, user, limit = 5) { const access = accessForUser(db, user); if (!access.authenticated) return []; return listEntries(db, { q: query }, user) - .filter((entry) => entry.status === "published") + .filter((entry) => entry.status === "published" && entry.review_state === "approved") .slice(0, limit) .map((entry) => ({ id: entry.id, @@ -361,7 +361,7 @@ function searchForAi(db, query, user, limit = 5) { category: entry.category, visibility: entry.visibility, summary: entry.summary, - facts: [entry.user_markdown, access.isMod ? entry.moderator_markdown : "", access.isAdmin ? entry.admin_markdown : ""] + facts: [entry.user_markdown, entry.moderator_markdown, entry.admin_markdown, entry.ai_facts_markdown] .filter(Boolean) .join("\n\n") .slice(0, 4000), diff --git a/plugins/okf/index.js b/plugins/okf/index.js index 41f142d..99c65ba 100644 --- a/plugins/okf/index.js +++ b/plugins/okf/index.js @@ -27,13 +27,17 @@ module.exports = { id: PLUGIN_ID, init({ web, db }) { ensureTables(db); + const okfContextProvider = ({ message, user, limit } = {}) => + formatAiContext(searchForAi(db, message || "", user, limit || 5)); if (!global.lumiFrameworks) { global.lumiFrameworks = {}; } global.lumiFrameworks.okf = { search: ({ query, user, limit } = {}) => searchForAi(db, query || "", user, limit || 5), + context: okfContextProvider, accessForUser: (user) => accessForUser(db, user) }; + global.lumiFrameworks.ai?.registerContext?.(PLUGIN_ID, okfContextProvider); const router = web.createRouter(); @@ -164,9 +168,26 @@ module.exports = { section: "admin", canAccess: (user) => accessForUser(db, user).canEdit }); + return () => { + global.lumiFrameworks?.ai?.unregisterContext?.(PLUGIN_ID); + if (global.lumiFrameworks?.okf?.context === okfContextProvider) { + delete global.lumiFrameworks.okf; + } + }; } }; +function formatAiContext(entries) { + if (!entries.length) return []; + return entries.map((entry) => [ + `OKF entry: ${entry.title}`, + `Source: ${entry.source}`, + entry.category ? `Category: ${entry.category}` : "", + entry.summary ? `Summary: ${entry.summary}` : "", + entry.facts ? `Facts:\n${entry.facts}` : "" + ].filter(Boolean).join("\n")); +} + function renderAdmin(req, res, db) { const filters = { q: req.query.q || "", diff --git a/plugins/okf/tests/verify.js b/plugins/okf/tests/verify.js index 223fc8a..1b81e44 100644 --- a/plugins/okf/tests/verify.js +++ b/plugins/okf/tests/verify.js @@ -66,6 +66,27 @@ assert.equal(listEntries(db, {}, mod).length, 1); assert.equal(getEntryBySlug(db, "currency-basics", user), null); assert.equal(getEntryBySlug(db, "currency-basics", mod).moderator_markdown.includes("Moderators"), true); assert.equal(getEntryBySlug(db, "currency-basics", admin).admin_markdown.includes("Admin-only"), true); +assert(searchForAi(db, "currency", mod).some((item) => item.facts.includes("Moderators"))); +assert.equal(searchForAi(db, "currency", user).length, 0); + +const publicEntry = createEntry(db, { + title: "Public help", + slug: "public-help", + category: "Support", + summary: "Public support answer.", + user_markdown: "Everyone can read this.", + admin_markdown: "Admin-only implementation notes.", + ai_facts_markdown: "Admin AI fact.", + visibility: "user", + status: "published", + review_state: "approved" +}, admin); +const publicUserContext = searchForAi(db, "public", user); +const publicAdminContext = searchForAi(db, "public", admin); +assert(publicUserContext.some((item) => item.id === publicEntry.id)); +assert.equal(publicUserContext.some((item) => item.facts.includes("Admin-only")), false); +assert(publicAdminContext.some((item) => item.facts.includes("Admin-only implementation notes."))); +assert(publicAdminContext.some((item) => item.facts.includes("Admin AI fact."))); const proposed = createEntry(db, { title: "Draft from editor", @@ -91,6 +112,7 @@ const restored = restoreVersion(db, proposed.id, 1, admin, "Test restore."); assert.equal(restored.summary, "Editor draft"); assert.equal(restored.user_markdown, "Draft content."); assert.equal(listVersions(db, proposed.id).length, 3); +assert.equal(searchForAi(db, "draft", admin).length, 0); setEntryWorkflow(db, "draft-from-editor", "review", admin); setEntryWorkflow(db, "draft-from-editor", "publish", admin);