Wire OKF into AI context
This commit is contained in:
parent
8ce36ea540
commit
e63a75ae8a
9
TODO.md
9
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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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")));
|
||||
|
||||
@ -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)"));
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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 || "",
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user