// DSH host RSI — the "界面 / 记忆系统"-like status view, two surfaces (bili-style): // // 1) LOOPBACK dashboard (startDashboardServer): a tiny self-hosted http server on an // ephemeral port; the "打开 Web UI" target the user can open in a browser. // 2) DSH-WEB SERVER routes (rsiWebserverHandler): `/rsi`, `/rsi/status.json`, // `/rsi/records.json` registered on DSH's OWN webserver (same-origin as the GUI), // so the Settings-tab can live-fetch status with no CORS + the page can be embedded. // // The status PAGE (DASHBOARD_HTML) is self-contained: vanilla HTML/CSS/JS, dark theme to // match DSH, no external assets. It polls `/status.json` every 5s and renders: // 插件状态徽章(启用中/已停用/仅内存) · 教训统计(可信/隔离/轨迹/总数) · 分层分布 · 最近记录 · 存储/最近活动. // Guarded everywhere so a bind/registration failure never disturbs the host. import http from 'node:http'; import { snapshot, records as fetchRecords } from './status.js'; /** * The self-contained status page. `basePath` is prepended to the status.json fetch so the * same page works both on the loopback dashboard ("" → /status.json) and DSH-web routes * ("/rsi/" → /rsi/status.json). * @param {string} [basePath] * @returns {string} */ export function dashboardHtml(basePath = "") { const base = JSON.stringify(basePath); return ` DSH HOST RSI记忆

DSH HOST RSI记忆

加载中…

插件状态
—
可信教训
0
隔离(待证实)
0
轨迹
0
教训总数
0
触发事件
0
注入
0
捕获
0
用户指令
0
失败信号
0
收敛注入
0
护栏触发
0
纠正重试
0
救回
0
最后通牒
0
未交付
0
分层分布
时间领域层/状态记住的内容 / 纠正
暂无记录
`; } /** * node:http handler for the DSH-webserver `/rsi` prefix route (bili-style same-origin routes). * Serves: `/rsi` → status page HTML; `/rsi/status.json` → live snapshot; `/rsi/records.json` → recent records. * @param {object} store openStore() result * @param {object} cfg * @returns {(req:object,res:object)=>void} */ export function rsiWebserverHandler(store, cfg = {}, getSnapshot = null) { const d = cfg.dashboard || {}; const html = dashboardHtml("/rsi/"); let lastSnap = null; let lastAt = 0; const snap = () => { const now = Date.now(); if (!lastSnap || now - lastAt > 3000) { lastSnap = getSnapshot ? getSnapshot() : snapshot(store, { enabled: cfg.enabled, recent: d.recent ?? 15, version: cfg.version ?? null }); lastAt = now; } return lastSnap; }; return (req, res) => { try { const url = req?.url || "/rsi"; const path = url.split("?")[0]; if (path === "/rsi/status.json") { res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify(snap())); return; } if (path === "/rsi/records.json") { const qs = new URL(url.startsWith("http") ? url : `http://x${url}`, "http://x"); const q = qs.searchParams; const out = fetchRecords(store, { limit: Number(q.get("limit")) || 15, trustedOnly: q.get("trustedOnly") === "true", domain: q.get("domain") || undefined, }); res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); res.end(JSON.stringify(out)); return; } res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(html); } catch (e) { try { res.writeHead(500, { "Content-Type": "text/plain" }); res.end(String(e && e.message)); } catch { /* ignore */ } } }; } /** * Start the self-hosted LOOPBACK dashboard (fallback + "打开 Web UI" target). * @param {object} store * @param {object} cfg * @returns {Promise<{url:string, port:number, host:string, close:()=>void}>} */ export async function startDashboardServer(store, cfg = {}, getSnapshot = null) { const d = cfg.dashboard || {}; const host = d.host ?? "127.0.0.1"; const port = d.port ?? 0; const handler = rsiWebserverHandler(store, { ...cfg, _rsiRewriteRoot: true }); // Rewrite the request URL mapping: loopback serves "/" (not "/rsi"). let lastSnap = null; let lastAt = 0; const server = http.createServer((req, res) => { try { const path = (req.url || "/").split("?")[0]; if (path === "/status.json" || path === "/") { if (path === "/status.json") { const now = Date.now(); if (!lastSnap || now - lastAt > 3000) { lastSnap = getSnapshot ? getSnapshot() : snapshot(store, { enabled: cfg.enabled, recent: d.recent ?? 15, version: cfg.version ?? null }); lastAt = now; } res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify(lastSnap)); } else { res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(dashboardHtml("")); } return; } if (path === "/records.json") { const q = new URL(req.url, "http://x").searchParams; const out = fetchRecords(store, { limit: Number(q.get("limit")) || 15, trustedOnly: q.get("trustedOnly") === "true", domain: q.get("domain") || undefined }); res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); res.end(JSON.stringify(out)); return; } res.writeHead(404, { "Content-Type": "text/plain" }); res.end("not found"); } catch (e) { try { res.writeHead(500); res.end(String(e && e.message)); } catch { /* ignore */ } } }); await new Promise((resolve, reject) => { server.once("error", reject); server.listen(port, host, () => resolve()); }); const actualPort = server.address().port; if (typeof server.unref === "function") server.unref(); return { url: `http://${host}:${actualPort}`, host, port: actualPort, close() { try { server.close(); } catch { /* ignore */ } }, }; } export default { dashboardHtml, rsiWebserverHandler, startDashboardServer };