diff --git a/index.js b/index.js
index 7f78560..24295cd 100644
--- a/index.js
+++ b/index.js
@@ -388,6 +388,7 @@ import {
import {
buildAuthorityDiagnosticsBundle,
buildAuthorityDiagnosticsBundlePath,
+ buildAuthorityPerformanceBaseline,
writeAuthorityDiagnosticsBundle as writeAuthorityDiagnosticsBundleFile,
} from "./maintenance/authority-diagnostics-bundle.js";
@@ -1885,6 +1886,16 @@ function getGraphPersistenceLiveState() {
authorityCheckpointRestoreError: String(
graphPersistenceState.authorityCheckpointRestoreError || "",
),
+ authorityPerformanceBaseline: cloneRuntimeDebugValue(
+ graphPersistenceState.authorityPerformanceBaseline,
+ null,
+ ),
+ authorityPerformanceBaselineUpdatedAt: String(
+ graphPersistenceState.authorityPerformanceBaselineUpdatedAt || "",
+ ),
+ authorityPerformanceBaselineReason: String(
+ graphPersistenceState.authorityPerformanceBaselineReason || "",
+ ),
authorityBrowserCacheMode: String(
authorityRuntime.browserState.mode || "minimal",
),
@@ -2284,6 +2295,147 @@ function getAuthorityBlobAdapter(options = {}) {
});
}
+function buildAuthorityPerformanceBaselineSnapshot(options = {}) {
+ const liveGraphPersistence = getGraphPersistenceLiveState();
+ return buildAuthorityPerformanceBaseline({
+ chatId:
+ normalizeChatIdCandidate(options.chatId) ||
+ normalizeChatIdCandidate(getCurrentChatId()) ||
+ normalizeChatIdCandidate(graphPersistenceState.chatId),
+ graphPersistence: liveGraphPersistence,
+ graph: currentGraph,
+ consistencyAudit: liveGraphPersistence.authorityConsistencyAudit,
+ });
+}
+
+function captureAuthorityPerformanceBaseline(options = {}) {
+ const baseline = buildAuthorityPerformanceBaselineSnapshot(options);
+ const capturedAt = String(baseline?.capturedAt || new Date().toISOString());
+ const reason = String(options.reason || "manual-authority-performance-baseline");
+ updateGraphPersistenceState({
+ authorityPerformanceBaseline: cloneRuntimeDebugValue(baseline, null),
+ authorityPerformanceBaselineUpdatedAt: capturedAt,
+ authorityPerformanceBaselineReason: reason,
+ });
+ refreshPanelLiveState();
+ return {
+ ok: true,
+ baseline,
+ };
+}
+
+async function exportAuthorityDiagnosticsBundle(options = {}) {
+ const settings = getSettings();
+ if (!shouldUseAuthorityDiagnosticsBundle()) {
+ return {
+ ok: false,
+ reason: "authority-diagnostics-unavailable",
+ };
+ }
+ const chatId = normalizeChatIdCandidate(
+ options.chatId || getCurrentChatId() || graphPersistenceState.chatId,
+ );
+ if (!chatId) {
+ return {
+ ok: false,
+ reason: "missing-chat-id",
+ };
+ }
+ const reason = String(options.reason || "diagnostics-bundle").trim() || "diagnostics-bundle";
+ const liveGraphPersistence = getGraphPersistenceLiveState();
+ const baseline = buildAuthorityPerformanceBaseline({
+ chatId,
+ graphPersistence: liveGraphPersistence,
+ graph: currentGraph,
+ consistencyAudit: liveGraphPersistence.authorityConsistencyAudit,
+ });
+ const bundle = buildAuthorityDiagnosticsBundle({
+ chatId,
+ reason,
+ settings,
+ runtimeStatus,
+ runtimeDebug: readRuntimeDebugSnapshot(),
+ graphPersistence: {
+ ...liveGraphPersistence,
+ authorityPerformanceBaseline: cloneRuntimeDebugValue(baseline, null),
+ authorityPerformanceBaselineUpdatedAt: String(baseline?.capturedAt || ""),
+ authorityPerformanceBaselineReason: reason,
+ },
+ graph: currentGraph,
+ lastExtractionStatus,
+ lastVectorStatus,
+ lastRecallStatus,
+ lastBatchStatus: cloneRuntimeDebugValue(currentGraph?.historyState?.lastBatchStatus, null),
+ lastInjection: lastInjectionContent,
+ lastExtract: lastExtractedItems,
+ lastRecall: lastRecalledItems,
+ performanceBaseline: baseline,
+ });
+ const path = buildAuthorityDiagnosticsBundlePath(chatId, reason);
+ const adapter = getAuthorityBlobAdapter();
+ try {
+ const result = await writeAuthorityDiagnosticsBundleFile(adapter, bundle, {
+ chatId,
+ reason,
+ path,
+ signal: options.signal,
+ });
+ const updatedAt = new Date().toISOString();
+ const bundleSize = (() => {
+ if (Number.isFinite(Number(result?.result?.size))) {
+ return Number(result.result.size);
+ }
+ try {
+ return JSON.stringify(bundle).length;
+ } catch {
+ return 0;
+ }
+ })();
+ recordAuthorityBlobSnapshot({
+ action: "diagnostics-write",
+ ok: result?.ok !== false,
+ backend: "authority-blob",
+ path: result?.path || path,
+ reason,
+ });
+ updateGraphPersistenceState({
+ authorityPerformanceBaseline: cloneRuntimeDebugValue(baseline, null),
+ authorityPerformanceBaselineUpdatedAt: String(baseline?.capturedAt || updatedAt),
+ authorityPerformanceBaselineReason: reason,
+ authorityDiagnosticsBundlePath: String(result?.path || path),
+ authorityDiagnosticsBundleReason: reason,
+ authorityDiagnosticsBundleUpdatedAt: updatedAt,
+ authorityDiagnosticsBundleSize: bundleSize,
+ });
+ if (options.refreshHost !== false) {
+ refreshPanelLiveState();
+ }
+ return {
+ ok: result?.ok !== false,
+ path: String(result?.path || path),
+ size: bundleSize,
+ baseline,
+ bundle,
+ };
+ } catch (error) {
+ const message =
+ error?.message || String(error) || "Authority diagnostics bundle failed";
+ recordAuthorityBlobSnapshot({
+ action: "diagnostics-write",
+ ok: false,
+ backend: "authority-blob",
+ path,
+ reason,
+ error: message,
+ });
+ return {
+ ok: false,
+ reason: "authority-diagnostics-bundle-error",
+ error,
+ };
+ }
+}
+
async function writeAuthorityLukerCheckpointBlob(
checkpoint = null,
{ chatId = "", reason = "luker-checkpoint", signal = undefined } = {},
@@ -21258,6 +21410,12 @@ async function onRestoreAuthorityCheckpoint() {
});
}
+async function onCaptureAuthorityPerformanceBaseline() {
+ return captureAuthorityPerformanceBaseline({
+ reason: "panel-authority-performance-baseline",
+ });
+}
+
async function onReembedDirect() {
return await onReembedDirectController({
getEmbeddingConfig,
@@ -21839,6 +21997,7 @@ async function onCompactLukerSidecar() {
refreshAuthorityJobs: onRefreshAuthorityJobs,
runAuthorityConsistencyAudit: onRunAuthorityConsistencyAudit,
restoreAuthorityCheckpoint: onRestoreAuthorityCheckpoint,
+ captureAuthorityPerformanceBaseline: onCaptureAuthorityPerformanceBaseline,
reembedDirect: onReembedDirect,
reroll: onReroll,
clearGraph: onClearGraph,
diff --git a/maintenance/authority-diagnostics-bundle.js b/maintenance/authority-diagnostics-bundle.js
index f03d226..f29c1e1 100644
--- a/maintenance/authority-diagnostics-bundle.js
+++ b/maintenance/authority-diagnostics-bundle.js
@@ -169,6 +169,126 @@ function buildStatusSnapshot(status = null) {
);
}
+export function buildAuthorityPerformanceBaseline({
+ chatId = "",
+ graphPersistence = null,
+ graph = null,
+ consistencyAudit = null,
+} = {}) {
+ const persistence =
+ graphPersistence && typeof graphPersistence === "object" && !Array.isArray(graphPersistence)
+ ? graphPersistence
+ : {};
+ const runtimeGraph = graph && typeof graph === "object" && !Array.isArray(graph) ? graph : {};
+ const audit =
+ consistencyAudit && typeof consistencyAudit === "object" && !Array.isArray(consistencyAudit)
+ ? consistencyAudit
+ : persistence.authorityConsistencyAudit &&
+ typeof persistence.authorityConsistencyAudit === "object" &&
+ !Array.isArray(persistence.authorityConsistencyAudit)
+ ? persistence.authorityConsistencyAudit
+ : null;
+ const loadDiagnostics =
+ persistence.loadDiagnostics && typeof persistence.loadDiagnostics === "object"
+ ? persistence.loadDiagnostics
+ : {};
+ const persistDelta =
+ persistence.persistDelta && typeof persistence.persistDelta === "object"
+ ? persistence.persistDelta
+ : {};
+ const recentJobs = Array.isArray(persistence.authorityRecentJobs)
+ ? persistence.authorityRecentJobs
+ : [];
+ const failedJobs = recentJobs.filter((job) => {
+ const queueState = String(job?.queueState || "").trim();
+ return queueState === "failed" || queueState === "error";
+ });
+ const runningJobs = recentJobs.filter(
+ (job) => String(job?.queueState || "").trim() === "running",
+ );
+ const graphRevision = Number(
+ runtimeGraph?.meta?.revision || persistence.revision || 0,
+ );
+ return {
+ kind: "authority-performance-baseline",
+ capturedAt: new Date().toISOString(),
+ chatId: normalizeRecordId(chatId || persistence.chatId || runtimeGraph?.chatId),
+ graphRevision: Number.isFinite(graphRevision) ? graphRevision : 0,
+ graphNodeCount: Array.isArray(runtimeGraph?.nodes) ? runtimeGraph.nodes.length : 0,
+ graphEdgeCount: Array.isArray(runtimeGraph?.edges) ? runtimeGraph.edges.length : 0,
+ extractionCount: Number(runtimeGraph?.historyState?.extractionCount || 0),
+ load: safeClone(
+ {
+ state: String(persistence.loadState || ""),
+ source: String(loadDiagnostics.source || ""),
+ totalMs: Number(loadDiagnostics.totalMs || 0),
+ preApplyMs: Number(loadDiagnostics.preApplyMs || 0),
+ exportSnapshotMs: Number(loadDiagnostics.exportSnapshotMs || 0),
+ hydrateMs: Number(loadDiagnostics.hydrateMs || 0),
+ hydrateNativeRecordsMs: Number(loadDiagnostics.hydrateNativeRecordsMs || 0),
+ applyRuntimeMs: Number(loadDiagnostics.applyRuntimeMs || 0),
+ updatedAt: String(loadDiagnostics.updatedAt || ""),
+ },
+ null,
+ ),
+ persist: safeClone(
+ {
+ totalMs: Number(persistDelta.totalMs || persistDelta.buildMs || 0),
+ buildMs: Number(persistDelta.buildMs || 0),
+ baseSnapshotReadMs: Number(persistDelta.baseSnapshotReadMs || 0),
+ snapshotBuildMs: Number(persistDelta.snapshotBuildMs || 0),
+ prepareMs: Number(persistDelta.prepareMs || 0),
+ nativeAttemptMs: Number(persistDelta.nativeAttemptMs || 0),
+ lookupMs: Number(persistDelta.lookupMs || 0),
+ jsDiffMs: Number(persistDelta.jsDiffMs || 0),
+ hydrateMs: Number(persistDelta.hydrateMs || 0),
+ commitQueueWaitMs: Number(persistDelta.commitQueueWaitMs || 0),
+ commitMs: Number(persistDelta.commitMs || 0),
+ commitPayloadBytes: Number(persistDelta.commitPayloadBytes || 0),
+ commitWalBytes: Number(persistDelta.commitWalBytes || 0),
+ updatedAt: String(persistDelta.updatedAt || ""),
+ },
+ null,
+ ),
+ soak: {
+ recentJobCount: recentJobs.length,
+ runningJobCount: runningJobs.length,
+ failedJobCount: failedJobs.length,
+ lastJobId: String(persistence.authorityLastJobId || ""),
+ lastJobStatus: String(persistence.authorityLastJobStatus || ""),
+ lastJobUpdatedAt: String(persistence.authorityLastJobUpdatedAt || ""),
+ },
+ audit: safeClone(
+ {
+ state: String(persistence.authorityConsistencyState || audit?.summary?.level || "idle"),
+ issueCount: Array.isArray(audit?.issues) ? audit.issues.length : 0,
+ sqlRevision: Number(audit?.sql?.revision || 0),
+ triviumRevision: Number(audit?.trivium?.revision || 0),
+ blobRevision: Number(
+ audit?.blob?.revision || persistence.authorityBlobCheckpointRevision || 0,
+ ),
+ },
+ null,
+ ),
+ artifacts: safeClone(
+ {
+ diagnosticsBundlePath: String(persistence.authorityDiagnosticsBundlePath || ""),
+ diagnosticsBundleReason: String(persistence.authorityDiagnosticsBundleReason || ""),
+ diagnosticsBundleUpdatedAt: String(
+ persistence.authorityDiagnosticsBundleUpdatedAt || "",
+ ),
+ diagnosticsBundleSize: Number(persistence.authorityDiagnosticsBundleSize || 0),
+ blobCheckpointPath: String(persistence.authorityBlobCheckpointPath || ""),
+ blobCheckpointRevision: Number(persistence.authorityBlobCheckpointRevision || 0),
+ blobCheckpointUpdatedAt: String(
+ persistence.authorityBlobCheckpointUpdatedAt || "",
+ ),
+ },
+ null,
+ ),
+ };
+}
+
export function sanitizeDiagnosticsSettings(settings = {}) {
return sanitizeValue(settings, "settings", 0);
}
@@ -197,6 +317,7 @@ export function buildAuthorityDiagnosticsBundle({
lastInjection = "",
lastExtract = [],
lastRecall = [],
+ performanceBaseline = null,
} = {}) {
const createdAt = new Date().toISOString();
return {
@@ -210,6 +331,7 @@ export function buildAuthorityDiagnosticsBundle({
runtimeDebug: safeClone(runtimeDebug, null),
graphPersistence: safeClone(graphPersistence, null),
graphSummary: buildGraphSummary(graph),
+ performanceBaseline: safeClone(performanceBaseline, null),
lastStatuses: {
extraction: buildStatusSnapshot(lastExtractionStatus),
vector: buildStatusSnapshot(lastVectorStatus),
diff --git a/tests/authority-diagnostics-bundle.mjs b/tests/authority-diagnostics-bundle.mjs
index d6532af..ec5cd6f 100644
--- a/tests/authority-diagnostics-bundle.mjs
+++ b/tests/authority-diagnostics-bundle.mjs
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
buildAuthorityDiagnosticsBundle,
buildAuthorityDiagnosticsBundlePath,
+ buildAuthorityPerformanceBaseline,
sanitizeDiagnosticsSettings,
writeAuthorityDiagnosticsBundle,
} from "../maintenance/authority-diagnostics-bundle.js";
@@ -42,6 +43,57 @@ function createMockAdapter() {
assert.equal(sanitized.models[0].token, "[REDACTED]");
}
+{
+ const baseline = buildAuthorityPerformanceBaseline({
+ chatId: "chat/main",
+ graphPersistence: {
+ chatId: "chat/main",
+ revision: 12,
+ loadState: "loaded",
+ loadDiagnostics: {
+ source: "authority-sql",
+ totalMs: 45,
+ hydrateMs: 18,
+ },
+ persistDelta: {
+ totalMs: 22,
+ commitMs: 8,
+ commitPayloadBytes: 2048,
+ },
+ authorityRecentJobs: [
+ { id: "job-1", queueState: "success" },
+ { id: "job-2", queueState: "failed" },
+ ],
+ authorityLastJobId: "job-2",
+ authorityLastJobStatus: "failed",
+ authorityConsistencyState: "warning",
+ authorityBlobCheckpointRevision: 11,
+ authorityDiagnosticsBundlePath: "user/files/diag.json",
+ authorityDiagnosticsBundleSize: 512,
+ },
+ graph: {
+ chatId: "chat/main",
+ meta: { revision: 12 },
+ nodes: [{ id: "n1" }, { id: "n2" }],
+ edges: [{ id: "e1" }],
+ historyState: { extractionCount: 5 },
+ },
+ consistencyAudit: {
+ issues: [{ code: "revision-drift" }],
+ sql: { revision: 12 },
+ trivium: { revision: 10 },
+ blob: { revision: 11 },
+ },
+ });
+ assert.equal(baseline.kind, "authority-performance-baseline");
+ assert.equal(baseline.graphRevision, 12);
+ assert.equal(baseline.graphNodeCount, 2);
+ assert.equal(baseline.soak.recentJobCount, 2);
+ assert.equal(baseline.soak.failedJobCount, 1);
+ assert.equal(baseline.audit.issueCount, 1);
+ assert.equal(baseline.artifacts.diagnosticsBundlePath, "user/files/diag.json");
+}
+
{
const bundle = buildAuthorityDiagnosticsBundle({
chatId: "chat/main",
@@ -123,6 +175,11 @@ function createMockAdapter() {
lastInjection: "A".repeat(4500),
lastExtract: [{ id: "n1" }],
lastRecall: [{ id: "n2" }],
+ performanceBaseline: {
+ kind: "authority-performance-baseline",
+ graphRevision: 9,
+ load: { totalMs: 12 },
+ },
});
assert.equal(bundle.kind, "st-bme-authority-diagnostics");
@@ -136,6 +193,7 @@ function createMockAdapter() {
assert.equal(bundle.lastInjection.textPreview.length, 4000);
assert.equal(bundle.recentExtractedItems.length, 1);
assert.equal(bundle.recentRecalledItems.length, 1);
+ assert.equal(bundle.performanceBaseline?.graphRevision, 9);
}
{
diff --git a/ui/panel.js b/ui/panel.js
index ab9fbd8..b688bb2 100644
--- a/ui/panel.js
+++ b/ui/panel.js
@@ -3104,6 +3104,34 @@ function _refreshTaskPersistence() {
const authorityRestoreUpdatedLabel = ps.authorityCheckpointRestoreUpdatedAt
? _formatTaskProfileTime(ps.authorityCheckpointRestoreUpdatedAt)
: "—";
+ const authorityBaseline =
+ ps.authorityPerformanceBaseline &&
+ typeof ps.authorityPerformanceBaseline === "object" &&
+ !Array.isArray(ps.authorityPerformanceBaseline)
+ ? ps.authorityPerformanceBaseline
+ : null;
+ const authorityBaselineUpdatedLabel = ps.authorityPerformanceBaselineUpdatedAt
+ ? _formatTaskProfileTime(ps.authorityPerformanceBaselineUpdatedAt)
+ : authorityBaseline?.capturedAt
+ ? _formatTaskProfileTime(authorityBaseline.capturedAt)
+ : "—";
+ const authorityBaselineLoadLabel = authorityBaseline?.load
+ ? `${_formatDurationMs(authorityBaseline.load.totalMs)} / hydrate ${_formatDurationMs(authorityBaseline.load.hydrateMs)}`
+ : "—";
+ const authorityBaselinePersistLabel = authorityBaseline?.persist
+ ? `${_formatDurationMs(authorityBaseline.persist.totalMs)} / commit ${_formatDurationMs(authorityBaseline.persist.commitMs)}`
+ : "—";
+ const authorityBaselineSoakLabel = authorityBaseline?.soak
+ ? `${Number(authorityBaseline.soak.recentJobCount || 0)} recent / ${Number(authorityBaseline.soak.failedJobCount || 0)} failed / ${Number(authorityBaseline.soak.runningJobCount || 0)} running`
+ : "—";
+ const authorityBaselineGraphLabel = authorityBaseline
+ ? `rev ${Number(authorityBaseline.graphRevision || 0)} · ${Number(authorityBaseline.graphNodeCount || 0)} 节点 / ${Number(authorityBaseline.graphEdgeCount || 0)} 边`
+ : "—";
+ const authorityBundlePathLabel = String(ps.authorityDiagnosticsBundlePath || "").trim() || "—";
+ const authorityBundleUpdatedLabel = ps.authorityDiagnosticsBundleUpdatedAt
+ ? _formatTaskProfileTime(ps.authorityDiagnosticsBundleUpdatedAt)
+ : "—";
+ const authorityBundleSizeLabel = _formatDataSizeBytes(ps.authorityDiagnosticsBundleSize);
const activeRegionLabel = String(
historyState?.activeRegion ||
historyState?.lastExtractedRegion ||
@@ -3210,6 +3238,15 @@ function _refreshTaskPersistence() {
["恢复状态", authorityRestoreLabel],
["恢复结果", authorityRestoreResult?.revision ? `rev ${Number(authorityRestoreResult.revision)}` : "—"],
["最近恢复", authorityRestoreUpdatedLabel],
+ ["Baseline 图谱", authorityBaselineGraphLabel],
+ ["Baseline Load", authorityBaselineLoadLabel],
+ ["Baseline Persist", authorityBaselinePersistLabel],
+ ["Baseline Soak", authorityBaselineSoakLabel],
+ ["最近 Baseline", authorityBaselineUpdatedLabel],
+ ["诊断包路径", authorityBundlePathLabel],
+ ["诊断包大小", authorityBundleSizeLabel],
+ ["诊断包时间", authorityBundleUpdatedLabel],
+ ["诊断包原因", ps.authorityDiagnosticsBundleReason || "—"],
];
const authorityActionButtons = [
typeof _actionHandlers.runAuthorityConsistencyAudit === "function"
@@ -3218,6 +3255,12 @@ function _refreshTaskPersistence() {
typeof _actionHandlers.restoreAuthorityCheckpoint === "function"
? ``
: "",
+ typeof _actionHandlers.captureAuthorityPerformanceBaseline === "function"
+ ? ``
+ : "",
+ typeof _actionHandlers.exportDiagnosticsBundle === "function"
+ ? ``
+ : "",
].filter(Boolean).join("");
el.innerHTML = `
@@ -3287,11 +3330,27 @@ function _refreshTaskPersistence() {
} else {
toastr.warning(`Authority Checkpoint 恢复失败:${result?.error || "unknown"}`, "ST-BME");
}
+ } else if (action === "baseline") {
+ if (typeof _actionHandlers.captureAuthorityPerformanceBaseline !== "function") return;
+ const result = await _actionHandlers.captureAuthorityPerformanceBaseline();
+ if (result?.ok) {
+ toastr.success("Authority Perf Baseline 已捕获", "ST-BME");
+ } else {
+ toastr.warning(`Authority Perf Baseline 捕获失败:${result?.error || "unknown"}`, "ST-BME");
+ }
+ } else if (action === "bundle") {
+ if (typeof _actionHandlers.exportDiagnosticsBundle !== "function") return;
+ const result = await _actionHandlers.exportDiagnosticsBundle();
+ if (result?.handledToast) {
+ return;
+ }
}
} catch (error) {
toastr.error(
action === "restore"
? `Authority Checkpoint 恢复失败: ${error?.message || error}`
+ : action === "baseline"
+ ? `Authority Perf Baseline 捕获失败: ${error?.message || error}`
: `Authority 审计失败: ${error?.message || error}`,
"ST-BME",
);
diff --git a/ui/ui-status.js b/ui/ui-status.js
index 318c412..f06d324 100644
--- a/ui/ui-status.js
+++ b/ui/ui-status.js
@@ -180,6 +180,9 @@ export function createGraphPersistenceState() {
authorityCheckpointRestoreResult: null,
authorityCheckpointRestoreUpdatedAt: "",
authorityCheckpointRestoreError: "",
+ authorityPerformanceBaseline: null,
+ authorityPerformanceBaselineUpdatedAt: "",
+ authorityPerformanceBaselineReason: "",
authorityDiagnosticsBundlePath: "",
authorityDiagnosticsBundleReason: "",
authorityDiagnosticsBundleUpdatedAt: "",