diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af1e89..f4ce1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Lumi changelog +## 0.2.4 + +- Changed the production diagnostics endpoint, cURL example, and local-client configuration example to use the actual URL from which the administrator opened Lumi, including trusted reverse-proxy HTTPS handling. + ## 0.2.3 - Fixed shared form action resolution so submit buttons without a `formaction` use their parent form's endpoint, restoring timed confirmations such as diagnostics-key creation. diff --git a/TODO.md b/TODO.md index ccded82..c798c61 100644 --- a/TODO.md +++ b/TODO.md @@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no ## Done +- 2026-07-18: Updated production diagnostics examples in core 0.2.4 to derive the full endpoint and client base URL from the administrator's current Lumi request instead of showing a placeholder hostname. - 2026-07-18: Fixed the shared submit-action resolver in core 0.2.3: ordinary buttons now inherit their parent form endpoint unless they explicitly declare `formaction`, restoring timed diagnostics-key creation and preventing async update actions from posting back to the Updates page. - 2026-07-18: Added production-stage plugin update diagnostics in core 0.2.2: selected plugin source is verified before snapshotting, failures record their exact stage and target in update state, and the affected plugin row displays the server error directly. - 2026-07-18: Added opt-in production diagnostics in core 0.2.2: administrators can issue or revoke a one-time access key for an HTTPS-only, rate-limited endpoint with five fixed read-only checks, recursive secret/path redaction, and audited access; no arbitrary command, SQL, file, URL, or write capability is exposed. diff --git a/knowledge/core/lumi-core.md b/knowledge/core/lumi-core.md index 42e0d20..84f9c7e 100644 --- a/knowledge/core/lumi-core.md +++ b/knowledge/core/lumi-core.md @@ -14,7 +14,7 @@ editable: false Lumi is the core web UI and bot runtime. ## Runtime Package: lumi-bot -Version: 0.2.3 +Version: 0.2.4 ## Routes - GET /api/events - POST /api/destructive-confirmations diff --git a/package-lock.json b/package-lock.json index 8d2115d..52a62cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "dependencies": { "adm-zip": "^0.5.12", "better-sqlite3": "^11.5.0", diff --git a/package.json b/package.json index cd3cd1a..1464940 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "private": true, "type": "commonjs", "scripts": { diff --git a/release-index.json b/release-index.json index b30e37a..b838ddd 100644 --- a/release-index.json +++ b/release-index.json @@ -2,6 +2,36 @@ "schema_version": 1, "channel": "stable", "releases": [ + { + "version": "0.2.4", + "ref": "refs/tags/v0.2.4", + "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": "Uses the administrator's current request origin for production diagnostics endpoint and local-client examples.", + "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.2", + "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.3", "ref": "refs/tags/v0.2.3", diff --git a/scripts/build-core-repair-patch.js b/scripts/build-core-repair-patch.js index 587e25b..1136fce 100644 --- a/scripts/build-core-repair-patch.js +++ b/scripts/build-core-repair-patch.js @@ -4,7 +4,7 @@ const path = require("path"); const AdmZip = require("adm-zip"); const root = path.join(__dirname, ".."); -const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip"); +const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.4-repair.zip"); const files = [ "CHANGELOG.md", "README.md", @@ -53,10 +53,10 @@ for (const relativePath of files) { const manifest = { schema_version: 1, - name: "Lumi core 0.2.3 repair", + name: "Lumi core 0.2.4 repair", target: "core", - from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"], - to_version: "0.2.3", + from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3", "0.2.4"], + to_version: "0.2.4", data_policy: "preserve", dependency_policy: "sync_on_restart", created_at: new Date().toISOString(), diff --git a/scripts/verify-core-repair-patch.js b/scripts/verify-core-repair-patch.js index e6c1182..b12f348 100644 --- a/scripts/verify-core-repair-patch.js +++ b/scripts/verify-core-repair-patch.js @@ -7,7 +7,7 @@ const AdmZip = require("adm-zip"); const { verifyPatchPackage } = require("../src/services/update-manager"); const root = path.join(__dirname, ".."); -const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip"); +const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.4-repair.zip"); assert.equal(fs.existsSync(archivePath), true, "build the repair patch first"); const zip = new AdmZip(archivePath); const entries = zip.getEntries().filter((entry) => !entry.isDirectory); @@ -15,9 +15,9 @@ const names = new Set(entries.map((entry) => entry.entryName.replace(/\\/g, "/") assert.equal(names.has("patch-manifest.json"), true); const manifest = JSON.parse(zip.readAsText("patch-manifest.json")); assert.equal(manifest.target, "core"); -assert.equal(manifest.to_version, "0.2.3"); +assert.equal(manifest.to_version, "0.2.4"); assert.equal(manifest.data_policy, "preserve"); -assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"]); +assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3", "0.2.4"]); const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/; for (const entry of entries) { @@ -29,7 +29,7 @@ for (const [relativePath, expected] of Object.entries(manifest.files)) { assert.equal(actual, expected, `${relativePath} checksum`); } assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed"); -assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.3"); +assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.4"); const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-")); try { @@ -47,11 +47,11 @@ try { fs.writeFileSync(target, `preserve:${sentinel}`); } zip.extractAllTo(simulation, true); - assert.equal(verifyPatchPackage(simulation).to_version, "0.2.3"); + assert.equal(verifyPatchPackage(simulation).to_version, "0.2.4"); for (const sentinel of sentinels) { assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`); } - assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.3"); + assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.4"); assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true); assert.equal(fs.existsSync(path.join(simulation, "src", "services", "production-diagnostics.js")), true); assert.equal(fs.existsSync(path.join(simulation, "src", "web", "views", "admin-diagnostics.ejs")), true); diff --git a/scripts/verify-production-diagnostics.js b/scripts/verify-production-diagnostics.js index 5da9b4a..7a03ba0 100644 --- a/scripts/verify-production-diagnostics.js +++ b/scripts/verify-production-diagnostics.js @@ -79,7 +79,10 @@ const viewSource = fs.readFileSync(path.join(root, "src", "web", "views", "admin assert.match(viewSource, /no remote control/i); assert.match(viewSource, /data-confirm-mode="modal"/); assert.match(viewSource, /\.secrets/); -assert.match(viewSource, /\/api\/diagnostics\/v1\/run/); +assert.match(viewSource, /diagnosticsEndpointUrl/); +assert.match(viewSource, /diagnosticsBaseUrl/); +assert.doesNotMatch(viewSource, /https:\/\/your-lumi-host/); +assert.match(serverSource, /const diagnosticsBaseUrl = `\$\{req\.protocol\}:\/\/\$\{req\.get\("host"\)\}`/); const clientSource = fs.readFileSync(path.join(root, "scripts", "production-diagnostics-client.js"), "utf8"); assert.match(clientSource, /\.secrets["'], "production-diagnostics\.json"/); diff --git a/scripts/verify-release-metadata.js b/scripts/verify-release-metadata.js index d901e18..f62cb72 100644 --- a/scripts/verify-release-metadata.js +++ b/scripts/verify-release-metadata.js @@ -4,8 +4,8 @@ const path = require("path"); const { findSafeTarget } = require("../src/services/versioning"); const root = path.join(__dirname, ".."); -const releaseVersion = "0.2.3"; -const previousCoreVersion = "0.2.2"; +const releaseVersion = "0.2.4"; +const previousCoreVersion = "0.2.3"; const earliestCompatibleCoreVersion = "0.1.9"; const changedPlugins = { "auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" }, @@ -87,4 +87,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0"); assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to); assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true); -console.log("Release metadata verification passed: core 0.2.3 and 11 changed plugin/tool packages."); +console.log("Release metadata verification passed: core 0.2.4 and 11 changed plugin/tool packages."); diff --git a/scripts/verify-update-system.js b/scripts/verify-update-system.js index 948f560..92cc601 100644 --- a/scripts/verify-update-system.js +++ b/scripts/verify-update-system.js @@ -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.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]); +assert.deepEqual(releaseVersions, ["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); @@ -35,8 +35,9 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) { } const baseTarget = { - current_version: "0.2.2", + current_version: "0.2.3", available_versions: [ + { version: "0.2.4", ref: "refs/tags/v0.2.4", rollback_safe: true }, { version: "0.2.3", ref: "refs/tags/v0.2.3", rollback_safe: true }, { version: "0.2.2", ref: "refs/tags/v0.2.2", rollback_safe: true }, { version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true }, @@ -63,7 +64,7 @@ const corrected = buildStatus({ channel: "stable" }); assert.equal(corrected.version_correction, true); -assert.equal(corrected.safe_target_version, "0.2.3"); +assert.equal(corrected.safe_target_version, "0.2.4"); assert.equal(corrected.update_available, true); assert.equal(corrected.blocked, false); diff --git a/src/web/server.js b/src/web/server.js index ec2a4c0..7ade243 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -5769,7 +5769,8 @@ function createWebServer({ loadPlugins, discordClient }) { res.redirect("/admin/theming"); }); - const renderDiagnosticsAdmin = (res, values = {}) => { + const renderDiagnosticsAdmin = (req, res, values = {}) => { + const diagnosticsBaseUrl = `${req.protocol}://${req.get("host")}`; res.set("Cache-Control", "no-store"); res.render("admin-diagnostics", { title: "Production diagnostics", @@ -5779,12 +5780,14 @@ function createWebServer({ loadPlugins, discordClient }) { diagnosticResult: null, diagnosticError: null, selectedCheck: "system_health", + diagnosticsBaseUrl, + diagnosticsEndpointUrl: `${diagnosticsBaseUrl}/api/diagnostics/v1/run`, ...values }); }; - app.get("/admin/diagnostics", requireRole("admin"), (_req, res) => { - renderDiagnosticsAdmin(res); + app.get("/admin/diagnostics", requireRole("admin"), (req, res) => { + renderDiagnosticsAdmin(req, res); }); app.post("/admin/diagnostics/run", requireRole("admin"), (req, res) => { @@ -5800,7 +5803,7 @@ function createWebServer({ loadPlugins, discordClient }) { fingerprint: `admin:${req.session.user.id}`, duration_ms: Date.now() - startedAt }); - renderDiagnosticsAdmin(res, { diagnosticResult, selectedCheck: check }); + renderDiagnosticsAdmin(req, res, { diagnosticResult, selectedCheck: check }); } catch (error) { auditDiagnosticRequest({ request_id: requestId, @@ -5810,13 +5813,13 @@ function createWebServer({ loadPlugins, discordClient }) { reason: error.message, duration_ms: Date.now() - startedAt }); - renderDiagnosticsAdmin(res, { diagnosticError: error.message, selectedCheck: check }); + renderDiagnosticsAdmin(req, res, { diagnosticError: error.message, selectedCheck: check }); } }); - app.post("/admin/diagnostics/access/renew", requireRole("admin"), (_req, res) => { + app.post("/admin/diagnostics/access/renew", requireRole("admin"), (req, res) => { const issuedKey = issueDiagnosticsAccessKey(); - renderDiagnosticsAdmin(res, { issuedKey }); + renderDiagnosticsAdmin(req, res, { issuedKey }); }); app.post("/admin/diagnostics/access/revoke", requireRole("admin"), (req, res) => { diff --git a/src/web/views/admin-diagnostics.ejs b/src/web/views/admin-diagnostics.ejs index 9241dce..c05658d 100644 --- a/src/web/views/admin-diagnostics.ejs +++ b/src/web/views/admin-diagnostics.ejs @@ -16,7 +16,7 @@
/api/diagnostics/v1/run<%= diagnosticsEndpointUrl %>