Lumi/plugins/okf/index.js
2026-06-25 14:10:04 +02:00

504 lines
18 KiB
JavaScript

const path = require("path");
const { getSetting, setSetting } = require("../../src/services/settings");
const {
PERMISSION_LEVELS,
REVIEW_STATES,
STATUS_VALUES,
VISIBILITY_VALUES,
accessForUser,
createEntry,
ensureTables,
getEditableEntry,
getEntryBySlug,
grantPermission,
listEntries,
listPermissions,
listVersions,
revokePermission,
restoreVersion,
searchForAi,
setEntryWorkflow,
updateEntry
} = require("./backend/okf_store");
const {
ensureKnowledgeDirs,
getCommunityKnowledgeFile,
listCommunityKnowledgeFiles,
loadKnowledgeEntries,
migrateSingleBracePlaceholders,
registerKnowledgePlaceholderDefinitions,
saveCommunityKnowledgeFile,
searchFileKnowledge
} = require("./backend/file_knowledge");
const { generateKnowledgeFiles } = require("./backend/generate_knowledge");
const { renderMarkdown } = require("./backend/markdown");
const placeholderService = require("../../src/services/placeholders");
const PLUGIN_ID = "okf";
module.exports = {
id: PLUGIN_ID,
init({ web, db, placeholders = placeholderService }) {
ensureTables(db);
ensureKnowledgeDirs(process.cwd());
if (!getSetting("okf_single_brace_placeholder_migration_v1", false)) {
migrateSingleBracePlaceholders(process.cwd());
setSetting("okf_single_brace_placeholder_migration_v1", true);
}
generateKnowledgeFiles(process.cwd());
registerOkfPlaceholderFields(placeholders);
registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
const searchOkfForAi = ({ query = "", user, limit = 5, okf_retrieval = "light" } = {}) => {
if (okf_retrieval === "none" || Number(limit) === 0) return [];
const normalizedLimit = Math.max(1, Math.min(Number(limit) || 5, 25));
const fileResults = searchFileKnowledge({ query, user, limit: normalizedLimit, rootDir: process.cwd() });
const dbResults = searchForAi(db, query || "", user, normalizedLimit);
const seen = new Set(fileResults.map((entry) => entry.source || entry.id));
return [
...fileResults,
...dbResults.filter((entry) => {
const key = entry.source || entry.id;
if (seen.has(key)) return false;
seen.add(key);
return true;
})
].slice(0, normalizedLimit);
};
const okfContextProvider = ({ message, user, limit, okf_retrieval } = {}) => {
const normalizedLimit = limit ?? 5;
const query = message || "";
const results = searchOkfForAi({ query, user, limit: normalizedLimit, okf_retrieval });
return {
blocks: formatAiContext(results),
diagnostics: {
provider: "okf",
kind: "okf",
query,
okf_retrieval: okf_retrieval || "light",
limit: normalizedLimit,
candidate_count: results.length,
returned_count: results.length,
reason: okf_retrieval === "none" || Number(normalizedLimit) === 0 ? "disabled_by_controller" : "search_complete"
}
};
};
if (!global.lumiFrameworks) {
global.lumiFrameworks = {};
}
global.lumiFrameworks.okf = {
search: searchOkfForAi,
context: okfContextProvider,
accessForUser: (user) => accessForUser(db, user)
};
global.lumiFrameworks.ai?.registerContext?.(PLUGIN_ID, okfContextProvider);
const router = web.createRouter();
router.use((req, res, next) => {
res.locals.okfAccess = accessForUser(db, req.session.user);
next();
});
router.get("/", requireLogin, (req, res) => {
const filters = {
q: req.query.q || "",
category: req.query.category || "",
tag: req.query.tag || ""
};
const entries = listEntries(db, filters, req.session.user);
res.render(view("index"), {
title: "Knowledge",
entries,
filters,
categories: categoriesFor(entries),
tags: tagsFor(entries)
});
});
router.get("/admin", requireOkfManagement(db), (req, res) => {
renderAdmin(req, res, db);
});
router.post("/admin/community", requireOkfEdit(db), (req, res) => {
try {
validateOkfPlaceholderFields({
body: req.body.body
}, req.session.user, placeholders);
const entry = saveCommunityKnowledgeFile(process.cwd(), req.body);
registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
req.session.flash = { type: "success", message: "Community OKF file saved." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(entry.slug)}#okf-community-files`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community#okf-community-files`);
}
});
router.post("/admin/community/:slug", requireOkfEdit(db), (req, res) => {
try {
validateOkfPlaceholderFields({
body: req.body.body
}, req.session.user, placeholders);
const entry = saveCommunityKnowledgeFile(process.cwd(), {
...req.body,
existing_slug: req.params.slug
});
registerKnowledgePlaceholderDefinitions(placeholders, { rootDir: process.cwd() });
req.session.flash = { type: "success", message: "Community OKF file updated." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(entry.slug)}#okf-community-files`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=community&community=${encodeURIComponent(req.params.slug)}#okf-community-files`);
}
});
router.post("/admin/entries", requireOkfEdit(db), (req, res) => {
try {
validateOkfPlaceholderFields(req.body, req.session.user, placeholders);
const entry = createEntry(db, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF entry created." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general`);
}
});
router.post("/admin/entries/:slug", requireOkfEdit(db), (req, res) => {
try {
validateOkfPlaceholderFields(req.body, req.session.user, placeholders);
const entry = updateEntry(db, req.params.slug, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF entry updated." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/entries/:slug/:action", requireOkfManagement(db), (req, res) => {
try {
const entry = setEntryWorkflow(db, req.params.slug, req.params.action, req.session.user, req.body.note || "");
req.session.flash = { type: "success", message: "OKF workflow updated." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/entries/:slug/versions/:version/restore", requireOkfManagement(db), (req, res) => {
try {
const selected = getEditableEntry(db, req.params.slug);
if (!selected) throw new Error("OKF entry was not found.");
const entry = restoreVersion(
db,
selected.id,
req.params.version,
req.session.user,
req.body.note || ""
);
req.session.flash = { type: "success", message: "OKF version restored." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general&edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/permissions", requireAdmin, (req, res) => {
try {
grantPermission(db, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF permission granted." };
} catch (error) {
req.session.flash = { type: "error", message: error.message };
}
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general#okf-permissions`);
});
router.post("/admin/permissions/:id/revoke", requireAdmin, (req, res) => {
try {
revokePermission(db, req.params.id, req.session.user);
req.session.flash = { type: "success", message: "OKF permission revoked." };
} catch (error) {
req.session.flash = { type: "error", message: error.message };
}
res.redirect(`/plugins/${PLUGIN_ID}/admin?tab=general#okf-permissions`);
});
router.get("/:slug", requireLogin, async (req, res) => {
const entry = await resolveOkfEntryPlaceholders(
getEntryBySlug(db, req.params.slug, req.session.user),
req.session.user,
placeholders
);
if (!entry) {
return res.status(404).render("error", {
title: "Knowledge entry unavailable",
message: "That OKF entry was not found or is not visible to your account."
});
}
res.render(view("entry"), {
title: entry.title,
entry,
renderMarkdown
});
});
web.mount(`/plugins/${PLUGIN_ID}`, router, {
label: "Knowledge",
role: "public",
authRequired: true,
section: "community",
canAccess: (user) => Boolean(user)
});
web.addNavItem({
label: "OKF Management",
path: `/plugins/${PLUGIN_ID}/admin`,
role: "public",
authRequired: true,
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 [];
const prepared = preferSpecificKnowledgeChunks(entries).slice(0, 3);
const blocks = [];
let remaining = 4200;
for (const entry of prepared) {
const facts = trimForContext(entry.facts, Math.min(1400, remaining));
const block = [
`OKF entry: ${entry.title}`,
`Source: ${entry.source}`,
entry.source_metadata ? `Source metadata: ${formatSourceMetadata(entry.source_metadata)}` : "",
entry.category ? `Category: ${entry.category}` : "",
entry.summary ? `Summary: ${trimForContext(entry.summary, 360)}` : "",
facts ? `Facts:\n${facts}` : ""
].filter(Boolean).join("\n");
if (!block || remaining <= 0) continue;
const trimmedBlock = trimForContext(block, remaining);
blocks.push(trimmedBlock);
remaining -= trimmedBlock.length + 2;
}
return blocks;
}
function preferSpecificKnowledgeChunks(entries) {
const hasSpecificRoute = entries.some((entry) => {
const heading = String(entry.source_metadata?.heading || "");
return /^(GET|POST|PUT|PATCH|DELETE|MOUNT)\s+\//i.test(heading);
});
if (!hasSpecificRoute) return entries;
return entries.filter((entry) => {
const heading = String(entry.source_metadata?.heading || "");
return !["Routes", "Web Routes", "Route Reference"].includes(heading);
});
}
function trimForContext(value, maximum) {
const text = String(value || "").trim();
const limit = Math.max(0, Number(maximum) || 0);
if (!text || !limit) return "";
if (text.length <= limit) return text;
return `${text.slice(0, Math.max(0, limit - 24)).trimEnd()}\n[truncated]`;
}
function formatSourceMetadata(metadata = {}) {
return [
metadata.path ? `path=${metadata.path}` : "",
metadata.id ? `id=${metadata.id}` : "",
metadata.heading ? `heading=${metadata.heading}` : "",
Number.isFinite(Number(metadata.score)) ? `score=${Math.round(Number(metadata.score))}` : "",
metadata.excerpt ? `excerpt=${String(metadata.excerpt).slice(0, 240)}` : ""
].filter(Boolean).join("; ");
}
function registerOkfPlaceholderFields(placeholders) {
if (!placeholders?.registerFieldPolicy) return;
placeholders.registerFieldPolicy({
field_id: "okf.markdown",
label: "OKF Markdown",
field_type: "okf_markdown",
output_audience: "user",
min_editor_role: "user",
allowed_namespaces: [],
max_sensitivity: "admin",
description: "Role-gated OKF Markdown content."
});
}
function validateOkfPlaceholderFields(values, user, placeholders) {
if (!placeholders?.validateTemplate) return;
const checks = [
["user_markdown", "user"],
["moderator_markdown", "mod"],
["admin_markdown", "admin"],
["ai_facts_markdown", "admin"],
["body", "user"]
];
for (const [field, outputAudience] of checks) {
if (values[field] === undefined) continue;
const result = placeholders.validateTemplate({
fieldId: "okf.markdown",
template: values[field],
outputAudience,
user
});
if (!result.ok) {
const tokens = result.errors
.map((error) => error.token || error.id || error.reason)
.filter(Boolean)
.slice(0, 5)
.join(", ");
throw new Error(`Unavailable or unauthorized OKF placeholder${tokens ? `: ${tokens}` : "."}`);
}
}
}
async function resolveOkfEntryPlaceholders(entry, user, placeholders) {
if (!entry || !placeholders?.renderTemplate) return entry;
const resolved = { ...entry };
const fields = [
["user_markdown", "user"],
["moderator_markdown", "mod"],
["admin_markdown", "admin"],
["ai_facts_markdown", "admin"]
];
for (const [field, outputAudience] of fields) {
if (!resolved[field]) continue;
const result = await placeholders.renderTemplate({
fieldId: "okf.markdown",
template: resolved[field],
outputAudience,
user,
runtimeContext: {
runtime: true,
okf: { entry: resolved }
},
fallback: "[unavailable]"
});
resolved[field] = result.rendered;
}
return resolved;
}
function renderAdmin(req, res, db) {
const activeTab = resolveAdminTab(req.query);
const filters = {
q: req.query.q || "",
status: req.query.status || "",
category: req.query.category || "",
tag: req.query.tag || ""
};
const entries = listEntries(db, filters, req.session.user, { management: true });
const allEntries = listEntries(db, {}, req.session.user, { management: true });
const selected = req.query.edit ? getEditableEntry(db, req.query.edit) : null;
const communityFiles = listCommunityKnowledgeFiles(process.cwd());
const selectedCommunity = req.query.community ? getCommunityKnowledgeFile(process.cwd(), req.query.community) : null;
const systemFiles = listSystemKnowledgeFiles(process.cwd());
const selectedSystemFile = req.query.system ? findSystemKnowledgeFile(systemFiles, req.query.system) : null;
const suggestionEntries = [...allEntries, ...communityFiles];
const versions = selected ? listVersions(db, selected.id) : [];
res.render(view("admin"), {
title: "OKF Management",
activeTab,
entries,
filters,
selected,
communityFiles,
selectedCommunity,
systemFiles,
selectedSystemFile,
versions,
permissions: listPermissions(db),
levels: PERMISSION_LEVELS,
statuses: STATUS_VALUES,
visibilityValues: VISIBILITY_VALUES,
reviewStates: REVIEW_STATES,
categories: categoriesFor(suggestionEntries),
tags: tagsFor(suggestionEntries),
renderMarkdown
});
}
function resolveAdminTab(query = {}) {
if (query.edit) return "general";
if (query.community) return "community";
if (query.system) return "system";
return ["general", "community", "system"].includes(query.tab) ? query.tab : "general";
}
function listSystemKnowledgeFiles(rootDir = process.cwd()) {
return loadKnowledgeEntries(rootDir, { includeHidden: true })
.filter((entry) => ["core", "plugins"].includes(entry.scope) && (entry.generated || !entry.editable))
.map((entry) => ({
...entry,
slug: entry.file_slug,
entry_slug: entry.slug,
path: `knowledge/${entry.path}`
}))
.sort((a, b) => a.scope.localeCompare(b.scope) || a.title.localeCompare(b.title));
}
function findSystemKnowledgeFile(files, key) {
const value = String(key || "");
return files.find((file) =>
file.slug === value ||
file.entry_slug === value ||
file.id === value ||
file.path === value
) || null;
}
function view(name) {
return path.join(__dirname, "views", `${name}.ejs`);
}
function requireLogin(req, res, next) {
if (req.session.user) return next();
return res.redirect("/login");
}
function requireAdmin(req, res, next) {
if (req.session.user?.isAdmin) return next();
return denied(res);
}
function requireOkfManagement(db) {
return (req, res, next) => {
if (accessForUser(db, req.session.user).canEdit) return next();
return denied(res);
};
}
function requireOkfEdit(db) {
return (req, res, next) => {
if (accessForUser(db, req.session.user).canEdit) return next();
return denied(res);
};
}
function denied(res) {
return res.status(403).render("error", {
title: "Access denied",
message: "You do not have access to that page."
});
}
function categoriesFor(entries) {
return Array.from(new Set(entries.map((entry) => entry.category).filter(Boolean))).sort();
}
function tagsFor(entries) {
return Array.from(new Set(entries.flatMap((entry) => entry.tags || []).filter(Boolean))).sort();
}