Fix Lumi AI feedback actions
This commit is contained in:
parent
e710469492
commit
a6e785a11b
@ -1,5 +1,10 @@
|
||||
# Lumi changelog
|
||||
|
||||
## 0.2.8
|
||||
|
||||
- Fixed Lumi AI Improvement Center actions that could return an unhelpful generic failure for no-change, dismiss, edit, and restore operations.
|
||||
- Added an explicit feedback-action response contract, actionable permission/session/not-found errors, and safe production diagnostics without changing existing feedback data.
|
||||
|
||||
## 0.2.7
|
||||
|
||||
- Reworked the Lumi AI Improvement Center into a live review queue with one-step apply, dismiss, and no-change outcomes.
|
||||
|
||||
1
TODO.md
1
TODO.md
@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no
|
||||
|
||||
## Done
|
||||
|
||||
- 2026-07-18: Fixed Lumi AI 0.8.4 Improvement Center state changes for no-change, dismiss, edit, and restore actions with an explicit authenticated JSON contract, actionable errors, safe diagnostics, and focused regression coverage.
|
||||
- 2026-07-18: Fixed private reverse-proxy HTTPS recognition for production diagnostics in core 0.2.5 without globally trusting client forwarding headers, and taught the local client to consume the repository's env-style `.secrets` file and short diagnostic variable names directly.
|
||||
- 2026-07-18: Fixed Windows/network-share plugin updates in core 0.2.6 by leaving live preserved plugin data in place and transactionally replacing only code, including automatic code rollback coverage.
|
||||
- 2026-07-18: Retired the one-off core repair ZIP build scripts after repository updates proved reliable; core 0.2.6 is distributed through the normal immutable Git release flow.
|
||||
|
||||
@ -27,6 +27,11 @@ review actions from another administrator or trusted reviewer update the queue
|
||||
without a full-page refresh and reconnect automatically after a temporary
|
||||
connection loss.
|
||||
|
||||
Review actions use an authenticated JSON response even when Lumi is behind a
|
||||
reverse proxy. If a save cannot complete, the page distinguishes an expired
|
||||
session, insufficient access, a feedback record changed elsewhere, and a
|
||||
server-side storage problem instead of showing a generic failure.
|
||||
|
||||
## Advanced tools
|
||||
|
||||
The collapsed **Advanced tools and access** section contains moderator access,
|
||||
|
||||
@ -14,7 +14,7 @@ editable: false
|
||||
Lumi is the core web UI and bot runtime.
|
||||
## Runtime
|
||||
Package: lumi-bot
|
||||
Version: 0.2.7
|
||||
Version: 0.2.8
|
||||
## Routes
|
||||
- GET /api/events
|
||||
- POST /api/destructive-confirmations
|
||||
|
||||
@ -14,7 +14,7 @@ editable: false
|
||||
Managed local AI provider and scoped WebUI assistant for Lumi.
|
||||
## Metadata
|
||||
Plugin ID: lumi_ai
|
||||
Version: 0.8.3
|
||||
Version: 0.8.4
|
||||
Default state: enabled
|
||||
## Web Routes
|
||||
- /plugins/lumi_ai
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.12",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
# Lumi AI changelog
|
||||
|
||||
## 0.8.4
|
||||
|
||||
- Fixed no-change, dismiss, edit, and restore actions in the Improvement Center by using an explicit authenticated JSON response contract.
|
||||
- Replaced the generic update failure with actionable session, permission, missing-record, HTTP, and server-write messages.
|
||||
- Added safe server diagnostics and regression coverage for the affected feedback state transitions.
|
||||
|
||||
## 0.8.3
|
||||
|
||||
- Replaced the multi-step approve, promote, and save workflow with a guided one-step “Apply improvement” action that activates reviewed corrections immediately.
|
||||
|
||||
38
plugins/lumi_ai/backend/improvement_http.js
Normal file
38
plugins/lumi_ai/backend/improvement_http.js
Normal file
@ -0,0 +1,38 @@
|
||||
function wantsImprovementJson(req) {
|
||||
const explicitFormat = String(req.get?.("x-lumi-response-format") || "").trim().toLowerCase();
|
||||
if (explicitFormat === "json") return true;
|
||||
if (req.xhr === true) return true;
|
||||
return req.accepts?.(["json", "html"]) === "json";
|
||||
}
|
||||
|
||||
function sendImprovementResult(req, res, type, message, result = {}) {
|
||||
if (wantsImprovementJson(req)) {
|
||||
return res.status(type === "error" ? 400 : 200).json({
|
||||
success: type !== "error",
|
||||
message,
|
||||
result
|
||||
});
|
||||
}
|
||||
req.session.flash = { type, message };
|
||||
return res.redirect("/plugins/lumi_ai/improvement_center");
|
||||
}
|
||||
|
||||
function sendImprovementError(req, res, status, message) {
|
||||
if (wantsImprovementJson(req)) {
|
||||
return res.status(status).json({
|
||||
success: false,
|
||||
error: message,
|
||||
message
|
||||
});
|
||||
}
|
||||
return res.status(status).render("error", {
|
||||
title: status === 404 ? "Feedback not found" : status === 400 ? "Invalid feedback action" : "Access denied",
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wantsImprovementJson,
|
||||
sendImprovementResult,
|
||||
sendImprovementError
|
||||
};
|
||||
@ -27,6 +27,7 @@ const { buildOriginContext, formatPlatformReply, formatPlatformReplyDetails } =
|
||||
const { AssistantPanelDiagnostics } = require("./backend/assistant_panel_diagnostics");
|
||||
const { formatAssistantResponse } = require("./backend/response_formatter");
|
||||
const { FeedbackStore, FEEDBACK_KINDS, FEEDBACK_TAGS, FINAL_FEEDBACK_STATUSES, improvementAccess, isFinalized } = require("./backend/feedback");
|
||||
const { sendImprovementResult, sendImprovementError } = require("./backend/improvement_http");
|
||||
const { CorrectionStore, PROMOTION_TARGETS } = require("./backend/corrections");
|
||||
const { EvalStore } = require("./backend/evals");
|
||||
const { TrainingExporter } = require("./backend/training_export");
|
||||
@ -1317,7 +1318,7 @@ module.exports = {
|
||||
|
||||
router.get("/improvement_center", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.allowed) return deniedImprovement(res);
|
||||
if (!access.allowed) return deniedImprovement(req, res);
|
||||
const historyStatus = FINAL_FEEDBACK_STATUSES.includes(cleanText(req.query.history_status, 30))
|
||||
? cleanText(req.query.history_status, 30)
|
||||
: "";
|
||||
@ -1354,7 +1355,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/settings", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_approve) return deniedImprovement(res);
|
||||
if (!access.can_approve) return deniedImprovement(req, res);
|
||||
config = saveConfig({
|
||||
...config,
|
||||
improvement: {
|
||||
@ -1371,30 +1372,36 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/reviews/:id", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.allowed) return deniedImprovement(res);
|
||||
if (!access.allowed) return deniedImprovement(req, res);
|
||||
try {
|
||||
const review = feedbackStore.get(req.params.id);
|
||||
if (!canReviewAiFeedback(review, access)) return deniedImprovement(res);
|
||||
const action = cleanText(req.body.action, 30);
|
||||
if (!review) {
|
||||
return sendImprovementError(req, res, 404, "This feedback record no longer exists. Refresh the queue and try again.");
|
||||
}
|
||||
if (!canReviewAiFeedback(review, access)) return deniedImprovement(req, res);
|
||||
const body = req.body || {};
|
||||
const action = cleanText(body.action, 30);
|
||||
let updated = null;
|
||||
if (action === "flag" && access.can_flag) {
|
||||
updated = feedbackStore.setStatus(req.params.id, "flagged", req.session.user, req.body.review_notes);
|
||||
updated = feedbackStore.setStatus(req.params.id, "flagged", req.session.user, body.review_notes);
|
||||
} else if (action === "verify" && access.can_verify) {
|
||||
updated = feedbackStore.verify(req.params.id, req.session.user, req.body.review_notes);
|
||||
updated = feedbackStore.verify(req.params.id, req.session.user, body.review_notes);
|
||||
} else if (action === "approve" && access.can_approve) {
|
||||
updated = feedbackStore.setStatus(req.params.id, "approved", req.session.user, req.body.review_notes);
|
||||
updated = feedbackStore.setStatus(req.params.id, "approved", req.session.user, body.review_notes);
|
||||
} else if (action === "reject" && access.can_approve) {
|
||||
updated = feedbackStore.setStatus(req.params.id, "rejected", req.session.user, req.body.review_notes);
|
||||
updated = feedbackStore.setStatus(req.params.id, "rejected", req.session.user, body.review_notes);
|
||||
} else if (action === "reviewed" && access.can_approve) {
|
||||
updated = feedbackStore.setStatus(req.params.id, "reviewed", req.session.user, req.body.review_notes);
|
||||
updated = feedbackStore.setStatus(req.params.id, "reviewed", req.session.user, body.review_notes);
|
||||
} else if (action === "restore" && access.can_approve) {
|
||||
updated = feedbackStore.restoreToQueue(req.params.id, req.session.user);
|
||||
} else if (action === "edit" && access.can_edit) {
|
||||
updated = feedbackStore.edit(req.params.id, req.body, req.session.user);
|
||||
updated = feedbackStore.edit(req.params.id, body, req.session.user);
|
||||
} else if (action === "export" && access.can_export) {
|
||||
updated = feedbackStore.markExportApproved(req.params.id, req.session.user);
|
||||
} else if (!action) {
|
||||
return sendImprovementError(req, res, 400, "The feedback action was missing. Refresh the page and try again.");
|
||||
} else {
|
||||
return deniedImprovement(res);
|
||||
return sendImprovementError(req, res, 403, "This account is not allowed to perform that feedback action.");
|
||||
}
|
||||
const messages = {
|
||||
flag: "Feedback marked for closer review.",
|
||||
@ -1409,13 +1416,14 @@ module.exports = {
|
||||
announceImprovementChange(updated, messages[action]);
|
||||
return improvementFlash(req, res, "success", messages[action] || "Feedback updated.", { review: updated });
|
||||
} catch (error) {
|
||||
logImprovementActionError(req, error);
|
||||
return improvementFlash(req, res, "error", error.message);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/improvement_center/reviews/:id/delete", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_delete) return deniedImprovement(res);
|
||||
if (!access.can_delete) return deniedImprovement(req, res);
|
||||
try {
|
||||
const deleted = feedbackStore.get(req.params.id);
|
||||
feedbackStore.delete(req.params.id);
|
||||
@ -1428,7 +1436,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/reviews/:id/archive", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_delete) return deniedImprovement(res);
|
||||
if (!access.can_delete) return deniedImprovement(req, res);
|
||||
try {
|
||||
const updated = feedbackStore.setStatus(req.params.id, "archived", req.session.user, req.body.review_notes);
|
||||
announceImprovementChange(updated, "Feedback archived and moved to history.");
|
||||
@ -1440,7 +1448,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/reviews/:id/implement", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_implement) return deniedImprovement(res);
|
||||
if (!access.can_implement) return deniedImprovement(req, res);
|
||||
try {
|
||||
let review = feedbackStore.get(req.params.id);
|
||||
if (!review || isFinalized(review)) throw new Error("This feedback is already finalized. Restore it from history before applying it again.");
|
||||
@ -1502,7 +1510,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/corrections/save", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_implement) return deniedImprovement(res);
|
||||
if (!access.can_implement) return deniedImprovement(req, res);
|
||||
const result = correctionStore.saveCorrections(req.session.user);
|
||||
announceImprovementChange(null, "AI corrections were activated.", "correction");
|
||||
return improvementFlash(req, res, "success", `Corrections saved. ${result.active} of ${result.total} are active.`);
|
||||
@ -1510,7 +1518,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/corrections/:id", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.allowed) return deniedImprovement(res);
|
||||
if (!access.allowed) return deniedImprovement(req, res);
|
||||
try {
|
||||
const action = cleanText(req.body.action, 30);
|
||||
if (action === "verify" && access.can_verify) {
|
||||
@ -1527,7 +1535,7 @@ module.exports = {
|
||||
} else if (action === "toggle" && access.can_edit) {
|
||||
correctionStore.setEnabled(req.params.id, req.body.enabled === "on");
|
||||
} else {
|
||||
return deniedImprovement(res);
|
||||
return deniedImprovement(req, res);
|
||||
}
|
||||
announceImprovementChange(null, `Correction ${action} completed.`, "correction");
|
||||
return improvementFlash(req, res, "success", `Correction ${action} completed. Save Corrections to activate changes.`);
|
||||
@ -1538,7 +1546,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/corrections/:id/delete", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_delete) return deniedImprovement(res);
|
||||
if (!access.can_delete) return deniedImprovement(req, res);
|
||||
try {
|
||||
correctionStore.delete(req.params.id);
|
||||
announceImprovementChange(null, "Correction deleted.", "correction");
|
||||
@ -1550,7 +1558,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/evals", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_run_evals) return deniedImprovement(res);
|
||||
if (!access.can_run_evals) return deniedImprovement(req, res);
|
||||
try {
|
||||
if (req.body.expected_link && !isVerifiedImprovementLink(req.body.expected_link)) {
|
||||
throw new Error("Expected links must match a verified Lumi route.");
|
||||
@ -1565,7 +1573,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/evals/:id/delete", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_run_evals) return deniedImprovement(res);
|
||||
if (!access.can_run_evals) return deniedImprovement(req, res);
|
||||
evalStore.delete(req.params.id);
|
||||
announceImprovementChange(null, "Evaluation case deleted.", "eval");
|
||||
return improvementFlash(req, res, "success", "Eval case deleted.");
|
||||
@ -1573,7 +1581,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/evals/run", async (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_run_evals) return deniedImprovement(res);
|
||||
if (!access.can_run_evals) return deniedImprovement(req, res);
|
||||
try {
|
||||
const results = await evalStore.runAll({ provider, actor: req.session.user });
|
||||
announceImprovementChange(null, "Evaluation run completed.", "eval");
|
||||
@ -1585,7 +1593,7 @@ module.exports = {
|
||||
|
||||
router.post("/improvement_center/exports/:format", (req, res) => {
|
||||
const access = improvementAccess(req.session.user, config);
|
||||
if (!access.can_export) return deniedImprovement(res);
|
||||
if (!access.can_export) return deniedImprovement(req, res);
|
||||
try {
|
||||
const output = trainingExporter.export(req.params.format);
|
||||
return res.download(output.file, output.filename);
|
||||
@ -1770,23 +1778,21 @@ function toolDenied(req, res) {
|
||||
return denied(res);
|
||||
}
|
||||
function improvementFlash(req, res, type, message, result = {}) {
|
||||
if (req.accepts(["json", "html"]) === "json") {
|
||||
return res.status(type === "error" ? 400 : 200).json({
|
||||
success: type !== "error",
|
||||
message,
|
||||
result
|
||||
});
|
||||
}
|
||||
req.session.flash = { type, message };
|
||||
return res.redirect(`/plugins/${PLUGIN_ID}/improvement_center`);
|
||||
return sendImprovementResult(req, res, type, message, result);
|
||||
}
|
||||
function denied(res) {
|
||||
return res.status(403).render("error", { title: "Access denied", message: "Administrator access is required." });
|
||||
}
|
||||
function deniedImprovement(res) {
|
||||
return res.status(403).render("error", {
|
||||
title: "Access denied",
|
||||
message: "Improvement Center access is not enabled for this account."
|
||||
function deniedImprovement(req, res) {
|
||||
return sendImprovementError(req, res, 403, "Improvement Center access is not enabled for this account.");
|
||||
}
|
||||
function logImprovementActionError(req, error) {
|
||||
console.error("Lumi AI feedback action failed", {
|
||||
review_id: cleanText(req.params?.id, 100),
|
||||
action: cleanText(req.body?.action, 30),
|
||||
user_id: cleanText(req.session?.user?.id, 100),
|
||||
code: cleanText(error?.code, 40),
|
||||
message: cleanText(error?.message || error, 500)
|
||||
});
|
||||
}
|
||||
function parseIdList(value) {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"id": "lumi_ai",
|
||||
"name": "Lumi AI",
|
||||
"version": "0.8.3",
|
||||
"version": "0.8.4",
|
||||
"description": "Managed local AI provider and scoped WebUI assistant for Lumi.",
|
||||
"main": "index.js",
|
||||
"channel": "stable",
|
||||
"compatible_from": "0.8.2",
|
||||
"migration_notes": "Existing models, runtimes, settings, feedback, corrections, metrics, and tool data are retained. Existing finalized feedback appears in searchable history; no manual migration is required.",
|
||||
"compatible_from": "0.8.3",
|
||||
"migration_notes": "Existing models, runtimes, settings, feedback, corrections, metrics, and tool data are retained. Feedback actions now use a reliable JSON response contract and report actionable failures; no manual migration is required.",
|
||||
"rollback_safe": true
|
||||
}
|
||||
|
||||
@ -129,12 +129,30 @@
|
||||
method: form.method || "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
"X-Lumi-Response-Format": "json"
|
||||
},
|
||||
body
|
||||
body,
|
||||
credentials: "same-origin",
|
||||
cache: "no-store"
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok || result.success === false) throw new Error(result.message || result.error || "The feedback could not be updated.");
|
||||
const responseText = await response.text();
|
||||
let result = {};
|
||||
try { result = responseText ? JSON.parse(responseText) : {}; } catch {}
|
||||
if (response.redirected) {
|
||||
throw new Error("Your session changed while saving. Refresh the page, sign in again if needed, and retry.");
|
||||
}
|
||||
if (!response.ok || result.success === false) {
|
||||
const fallback = response.status === 403
|
||||
? "You no longer have permission to perform this feedback action."
|
||||
: response.status === 404
|
||||
? "This feedback record is no longer available. Refresh the queue and try again."
|
||||
: `The feedback could not be updated (HTTP ${response.status}).`;
|
||||
throw new Error(result.message || result.error || fallback);
|
||||
}
|
||||
if (!result.message && !responseText) {
|
||||
throw new Error("Lumi returned an empty response while saving feedback. Check the server diagnostics and try again.");
|
||||
}
|
||||
form.closest("dialog")?.close();
|
||||
showStatus(result.message || "Feedback updated.", "success");
|
||||
await refreshWorkspace(null, { openHistory: Boolean(historyDialog()?.open) });
|
||||
|
||||
@ -50,6 +50,7 @@ const { FeedbackStore, FEEDBACK_TAGS, FINAL_FEEDBACK_STATUSES, improvementAccess
|
||||
const { CorrectionStore } = require("../backend/corrections");
|
||||
const { EvalStore, evaluateCase } = require("../backend/evals");
|
||||
const { TrainingExporter, approvedExamples } = require("../backend/training_export");
|
||||
const { wantsImprovementJson, sendImprovementResult, sendImprovementError } = require("../backend/improvement_http");
|
||||
const { registerAssistantCommands, authorizeAiRequest, searchKnownUsers, finalizeAssistantResult } = require("../index");
|
||||
const { EventEmitter } = require("events");
|
||||
const os = require("os");
|
||||
@ -272,6 +273,26 @@ async function run() {
|
||||
improvement: { allow_moderators_to_review_responses: false, trusted_moderator_reviewers: [] }
|
||||
}).allowed, false);
|
||||
|
||||
const jsonRequest = {
|
||||
get: (name) => name.toLowerCase() === "x-lumi-response-format" ? "json" : "",
|
||||
accepts: () => "html",
|
||||
session: {}
|
||||
};
|
||||
const jsonResponse = responseRecorder();
|
||||
sendImprovementResult(jsonRequest, jsonResponse, "success", "Feedback updated.", { review: { id: "one" } });
|
||||
assert.equal(wantsImprovementJson(jsonRequest), true);
|
||||
assert.equal(jsonResponse.statusCode, 200);
|
||||
assert.deepEqual(jsonResponse.jsonBody, {
|
||||
success: true,
|
||||
message: "Feedback updated.",
|
||||
result: { review: { id: "one" } }
|
||||
});
|
||||
const deniedResponse = responseRecorder();
|
||||
sendImprovementError(jsonRequest, deniedResponse, 403, "Access denied.");
|
||||
assert.equal(deniedResponse.statusCode, 403);
|
||||
assert.equal(deniedResponse.jsonBody.success, false);
|
||||
assert.equal(deniedResponse.jsonBody.message, "Access denied.");
|
||||
|
||||
const improvementTemp = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-ai-improvement-"));
|
||||
const feedbackStore = new FeedbackStore({ file: path.join(improvementTemp, "feedback.json") });
|
||||
const review = feedbackStore.capture({
|
||||
@ -297,6 +318,21 @@ async function run() {
|
||||
assert.deepEqual(review.context_snapshot.tools, ["settings_lookup"]);
|
||||
assert.equal(feedbackStore.list({ viewerRole: "mod" }).entries.length, 0);
|
||||
assert.equal(feedbackStore.list({ viewerRole: "admin" }).entries.length, 1);
|
||||
feedbackStore.edit(review.id, {
|
||||
feedback_tag: "bad_response",
|
||||
feedback_kind: "instruction_based",
|
||||
optional_correction: "Use the verified settings page.",
|
||||
review_notes: "Edited locally."
|
||||
}, { id: "admin" });
|
||||
assert.equal(feedbackStore.get(review.id).review_notes, "Edited locally.");
|
||||
feedbackStore.setStatus(review.id, "reviewed", { id: "admin" });
|
||||
assert.equal(feedbackStore.get(review.id).status, "reviewed");
|
||||
feedbackStore.restoreToQueue(review.id, { id: "admin" });
|
||||
assert.equal(feedbackStore.get(review.id).status, "pending");
|
||||
feedbackStore.setStatus(review.id, "rejected", { id: "admin" });
|
||||
assert.equal(feedbackStore.get(review.id).status, "rejected");
|
||||
feedbackStore.restoreToQueue(review.id, { id: "admin" });
|
||||
assert.equal(feedbackStore.get(review.id).status, "pending");
|
||||
const safeSnapshot = buildFeedbackContextSnapshot({
|
||||
contextBlocks: [`Open ${process.cwd()}\\private.md; password=secret`],
|
||||
contextDiagnostics: [{ provider: "okf", query: "settings", candidate_count: 3 }],
|
||||
@ -1980,6 +2016,29 @@ function fakeChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
function responseRecorder() {
|
||||
return {
|
||||
statusCode: 200,
|
||||
jsonBody: null,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json(value) {
|
||||
this.jsonBody = value;
|
||||
return this;
|
||||
},
|
||||
redirect(value) {
|
||||
this.redirectedTo = value;
|
||||
return this;
|
||||
},
|
||||
render(view, values) {
|
||||
this.rendered = { view, values };
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<%- include("../../../src/web/views/partials/layout-top", { title }) %>
|
||||
<link rel="stylesheet" href="/plugins/lumi_ai/assets/settings.css?v=0.8.3" />
|
||||
<link rel="stylesheet" href="/plugins/lumi_ai/assets/improvement-center.css?v=0.8.3" />
|
||||
<link rel="stylesheet" href="/plugins/lumi_ai/assets/settings.css?v=0.8.4" />
|
||||
<link rel="stylesheet" href="/plugins/lumi_ai/assets/improvement-center.css?v=0.8.4" />
|
||||
|
||||
<% const statusLabel = (status) => ({ pending: "New", flagged: "Needs attention", verified: "Reviewed by moderator", approved: "Ready to apply", implemented: "Applied", rejected: "Dismissed", reviewed: "No change needed", archived: "Archived" }[status] || status); %>
|
||||
<section class="ai-titlebar improvement-titlebar">
|
||||
@ -188,5 +188,5 @@
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<script src="/plugins/lumi_ai/assets/improvement-center.js?v=0.8.3" defer></script>
|
||||
<script src="/plugins/lumi_ai/assets/improvement-center.js?v=0.8.4" defer></script>
|
||||
<%- include("../../../src/web/views/partials/layout-bottom") %>
|
||||
|
||||
@ -2,6 +2,36 @@
|
||||
"schema_version": 1,
|
||||
"channel": "stable",
|
||||
"releases": [
|
||||
{
|
||||
"version": "0.2.8",
|
||||
"ref": "refs/tags/v0.2.8",
|
||||
"released_at": "2026-07-18",
|
||||
"installable": true,
|
||||
"rollback_safe": true,
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"data_policy": "preserve",
|
||||
"dependency_policy": "sync_on_restart",
|
||||
"migration_notes": "Fixes Lumi AI feedback review actions and adds actionable failure reporting while preserving all existing feedback, corrections, models, settings, and plugin data.",
|
||||
"plugins": {
|
||||
"auto-vc": "0.1.6",
|
||||
"birthday": "0.1.3",
|
||||
"economy-framework": "0.2.10",
|
||||
"economy-games": "0.1.7",
|
||||
"expression-interaction": "0.2.1",
|
||||
"lumi_ai": "0.8.4",
|
||||
"moderation": "0.1.5",
|
||||
"okf": "0.1.1",
|
||||
"quotes": "0.1.2",
|
||||
"sample-plugin": "0.1.0",
|
||||
"throne_wishlist": "0.1.2",
|
||||
"welcome_messages": "0.1.1"
|
||||
},
|
||||
"tools": {
|
||||
"lumi_ai_web_search": "0.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.2.7",
|
||||
"ref": "refs/tags/v0.2.7",
|
||||
|
||||
@ -4,15 +4,15 @@ const path = require("path");
|
||||
const { findSafeTarget } = require("../src/services/versioning");
|
||||
|
||||
const root = path.join(__dirname, "..");
|
||||
const releaseVersion = "0.2.7";
|
||||
const previousCoreVersion = "0.2.6";
|
||||
const releaseVersion = "0.2.8";
|
||||
const previousCoreVersion = "0.2.7";
|
||||
const earliestCompatibleCoreVersion = "0.1.9";
|
||||
const changedPlugins = {
|
||||
"auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" },
|
||||
birthday: { from: "0.1.2", to: "0.1.3", knowledge: "birthday" },
|
||||
"economy-framework": { from: "0.2.9", to: "0.2.10", knowledge: "economy-framework" },
|
||||
"expression-interaction": { from: "0.2.0", to: "0.2.1", knowledge: "expression-interaction" },
|
||||
lumi_ai: { from: "0.8.2", to: "0.8.3", knowledge: "lumi-ai" },
|
||||
lumi_ai: { from: "0.8.3", to: "0.8.4", knowledge: "lumi-ai" },
|
||||
moderation: { from: "0.1.4", to: "0.1.5", knowledge: "moderation" },
|
||||
okf: { from: "0.1.0", to: "0.1.1", knowledge: "okf" },
|
||||
quotes: { from: "0.1.1", to: "0.1.2", knowledge: "quotes" },
|
||||
@ -87,4 +87,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0");
|
||||
assert.equal(webSearch.minimum_lumi_ai_version, "0.8.2");
|
||||
assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true);
|
||||
|
||||
console.log("Release metadata verification passed: core 0.2.7, Lumi AI 0.8.3, and synchronized package metadata.");
|
||||
console.log("Release metadata verification passed: core 0.2.8, Lumi AI 0.8.4, and synchronized package metadata.");
|
||||
|
||||
@ -16,7 +16,7 @@ function readJson(relativePath) {
|
||||
|
||||
const releaseIndex = readJson("release-index.json");
|
||||
const releaseVersions = releaseIndex.releases.map((release) => release.version);
|
||||
assert.deepEqual(releaseVersions, ["0.2.7", "0.2.6", "0.2.5", "0.2.4", "0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]);
|
||||
assert.deepEqual(releaseVersions, ["0.2.8", "0.2.7", "0.2.6", "0.2.5", "0.2.4", "0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]);
|
||||
assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique");
|
||||
for (const release of releaseIndex.releases) {
|
||||
assert.equal(normalizeRepositoryRef(release.ref), release.ref);
|
||||
@ -37,6 +37,7 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) {
|
||||
const baseTarget = {
|
||||
current_version: "0.2.4",
|
||||
available_versions: [
|
||||
{ version: "0.2.8", ref: "refs/tags/v0.2.8", rollback_safe: true },
|
||||
{ version: "0.2.7", ref: "refs/tags/v0.2.7", rollback_safe: true },
|
||||
{ version: "0.2.6", ref: "refs/tags/v0.2.6", rollback_safe: true },
|
||||
{ version: "0.2.5", ref: "refs/tags/v0.2.5", rollback_safe: true },
|
||||
@ -67,7 +68,7 @@ const corrected = buildStatus({
|
||||
channel: "stable"
|
||||
});
|
||||
assert.equal(corrected.version_correction, true);
|
||||
assert.equal(corrected.safe_target_version, "0.2.7");
|
||||
assert.equal(corrected.safe_target_version, "0.2.8");
|
||||
assert.equal(corrected.update_available, true);
|
||||
assert.equal(corrected.blocked, false);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Lumi Core",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"channel": "stable",
|
||||
"released_at": "2026-07-18",
|
||||
"compatible_from": "0.1.9",
|
||||
@ -8,7 +8,7 @@
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, Windows/network-share-safe plugin code replacement, and the live streamlined Lumi AI feedback workflow. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, feedback, and secrets.",
|
||||
"migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, Windows/network-share-safe plugin code replacement, and reliable Lumi AI feedback review actions. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, feedback, and secrets.",
|
||||
"rollback_safe": true,
|
||||
"requirements": [
|
||||
"Node.js 18 or newer"
|
||||
@ -109,6 +109,18 @@
|
||||
],
|
||||
"rollback_safe": true,
|
||||
"migration_notes": "Adds the live streamlined Lumi AI feedback queue and searchable finalized-feedback history; existing feedback and plugin data are preserved."
|
||||
},
|
||||
{
|
||||
"version": "0.2.8",
|
||||
"channel": "stable",
|
||||
"released_at": "2026-07-18",
|
||||
"compatible_from": "0.1.9",
|
||||
"migration_kind": "patch",
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"rollback_safe": true,
|
||||
"migration_notes": "Fixes Lumi AI feedback review actions and failure reporting; existing feedback, corrections, models, settings, and plugin data are preserved."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user