mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix mobile action status cleanup
This commit is contained in:
260
tests/mobile-status-regressions.mjs
Normal file
260
tests/mobile-status-regressions.mjs
Normal file
@@ -0,0 +1,260 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import vm from "node:vm";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const indexPath = path.resolve(__dirname, "../index.js");
|
||||
const indexSource = await fs.readFile(indexPath, "utf8");
|
||||
|
||||
function extractSnippet(startMarker, endMarker) {
|
||||
const start = indexSource.indexOf(startMarker);
|
||||
const end = indexSource.indexOf(endMarker);
|
||||
if (start < 0 || end < 0 || end <= start) {
|
||||
throw new Error(`无法提取 index.js 片段: ${startMarker} -> ${endMarker}`);
|
||||
}
|
||||
return indexSource.slice(start, end).replace(/^export\s+/gm, "");
|
||||
}
|
||||
|
||||
const statusSnippet = extractSnippet(
|
||||
"function setRuntimeStatus(",
|
||||
"function notifyExtractionIssue(",
|
||||
);
|
||||
const vectorSnippet = extractSnippet(
|
||||
"async function syncVectorState({",
|
||||
"async function ensureVectorReadyIfNeeded(",
|
||||
);
|
||||
const manualExtractSnippet = extractSnippet(
|
||||
"async function onManualExtract() {",
|
||||
"async function onReroll(",
|
||||
);
|
||||
const rebuildSnippet = extractSnippet(
|
||||
"async function onRebuild() {",
|
||||
"async function onManualCompress() {",
|
||||
);
|
||||
|
||||
function createBaseStatusContext() {
|
||||
return {
|
||||
console,
|
||||
Date,
|
||||
createUiStatus(text = "待命", meta = "", level = "idle") {
|
||||
return {
|
||||
text: String(text || "待命"),
|
||||
meta: String(meta || ""),
|
||||
level,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
},
|
||||
runtimeStatus: { text: "待命", meta: "", level: "idle" },
|
||||
lastExtractionStatus: { text: "待命", meta: "", level: "idle" },
|
||||
lastVectorStatus: { text: "待命", meta: "", level: "idle" },
|
||||
lastRecallStatus: { text: "待命", meta: "", level: "idle" },
|
||||
lastStatusToastAt: {},
|
||||
STATUS_TOAST_THROTTLE_MS: 1500,
|
||||
_panelModule: {
|
||||
updateFloatingBallStatus() {},
|
||||
},
|
||||
refreshPanelLiveState() {},
|
||||
updateStageNotice() {},
|
||||
notifyStatusToast() {},
|
||||
toastr: {
|
||||
info() {},
|
||||
success() {},
|
||||
warning() {},
|
||||
error() {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function testVectorSyncTerminalStateUpdatesRuntime() {
|
||||
const context = {
|
||||
...createBaseStatusContext(),
|
||||
currentGraph: {
|
||||
vectorIndexState: {
|
||||
dirty: true,
|
||||
lastWarning: "",
|
||||
},
|
||||
},
|
||||
ensureCurrentGraphRuntimeState() {
|
||||
return context.currentGraph;
|
||||
},
|
||||
getEmbeddingConfig() {
|
||||
return { mode: "direct" };
|
||||
},
|
||||
validateVectorConfig() {
|
||||
return { valid: true };
|
||||
},
|
||||
async syncGraphVectorIndex() {
|
||||
return {
|
||||
insertedHashes: [],
|
||||
stats: {
|
||||
indexed: 12,
|
||||
pending: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
getCurrentChatId() {
|
||||
return "chat-mobile";
|
||||
},
|
||||
getVectorIndexStats() {
|
||||
return { indexed: 12, pending: 0 };
|
||||
},
|
||||
isAbortError() {
|
||||
return false;
|
||||
},
|
||||
markVectorStateDirty() {},
|
||||
result: null,
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
`${statusSnippet}\n${vectorSnippet}\nresult = { syncVectorState };`,
|
||||
context,
|
||||
{ filename: indexPath },
|
||||
);
|
||||
|
||||
const result = await context.result.syncVectorState({ force: true });
|
||||
assert.equal(result.stats.indexed, 12);
|
||||
assert.equal(context.lastVectorStatus.text, "向量完成");
|
||||
assert.equal(context.runtimeStatus.text, "向量完成");
|
||||
assert.equal(context.runtimeStatus.level, "success");
|
||||
}
|
||||
|
||||
async function testManualExtractNoBatchesDoesNotStayRunning() {
|
||||
let assistantTurnCallCount = 0;
|
||||
const chat = [{ is_user: true, mes: "u" }, { is_user: false, mes: "a" }];
|
||||
const context = {
|
||||
...createBaseStatusContext(),
|
||||
isExtracting: false,
|
||||
currentGraph: {},
|
||||
getCurrentChatId() {
|
||||
return "chat-mobile";
|
||||
},
|
||||
ensureGraphMutationReady() {
|
||||
return true;
|
||||
},
|
||||
async recoverHistoryIfNeeded() {
|
||||
return true;
|
||||
},
|
||||
normalizeGraphRuntimeState(graph) {
|
||||
return graph;
|
||||
},
|
||||
createEmptyGraph() {
|
||||
return {};
|
||||
},
|
||||
getContext() {
|
||||
return { chat };
|
||||
},
|
||||
getAssistantTurns() {
|
||||
assistantTurnCallCount += 1;
|
||||
return assistantTurnCallCount === 1 ? [1] : [];
|
||||
},
|
||||
getLastProcessedAssistantFloor() {
|
||||
return 0;
|
||||
},
|
||||
clampInt(value, fallback) {
|
||||
return Number.isFinite(Number(value)) ? Number(value) : fallback;
|
||||
},
|
||||
getSettings() {
|
||||
return { extractEvery: 1 };
|
||||
},
|
||||
beginStageAbortController() {
|
||||
return { signal: {} };
|
||||
},
|
||||
async executeExtractionBatch() {
|
||||
throw new Error("不应进入批次执行");
|
||||
},
|
||||
isAbortError() {
|
||||
return false;
|
||||
},
|
||||
finishStageAbortController() {},
|
||||
result: null,
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
`${statusSnippet}\n${manualExtractSnippet}\nresult = { onManualExtract };`,
|
||||
context,
|
||||
{ filename: indexPath },
|
||||
);
|
||||
|
||||
await context.result.onManualExtract();
|
||||
assert.equal(context.isExtracting, false);
|
||||
assert.equal(context.lastExtractionStatus.text, "无待提取内容");
|
||||
assert.equal(context.runtimeStatus.text, "无待提取内容");
|
||||
assert.notEqual(context.runtimeStatus.level, "running");
|
||||
}
|
||||
|
||||
async function testManualRebuildSetsTerminalRuntimeStatus() {
|
||||
const chat = [{ is_user: true, mes: "u" }, { is_user: false, mes: "a" }];
|
||||
const context = {
|
||||
...createBaseStatusContext(),
|
||||
currentGraph: {
|
||||
vectorIndexState: {
|
||||
lastWarning: "",
|
||||
},
|
||||
batchJournal: [],
|
||||
},
|
||||
confirm() {
|
||||
return true;
|
||||
},
|
||||
ensureGraphMutationReady() {
|
||||
return true;
|
||||
},
|
||||
getContext() {
|
||||
return { chat };
|
||||
},
|
||||
cloneGraphSnapshot(graph) {
|
||||
return graph;
|
||||
},
|
||||
snapshotRuntimeUiState() {
|
||||
return {};
|
||||
},
|
||||
getSettings() {
|
||||
return {};
|
||||
},
|
||||
normalizeGraphRuntimeState(graph) {
|
||||
return graph;
|
||||
},
|
||||
createEmptyGraph() {
|
||||
return {
|
||||
vectorIndexState: {
|
||||
lastWarning: "",
|
||||
},
|
||||
batchJournal: [],
|
||||
};
|
||||
},
|
||||
getCurrentChatId() {
|
||||
return "chat-mobile";
|
||||
},
|
||||
clearInjectionState() {},
|
||||
async prepareVectorStateForReplay() {},
|
||||
async replayExtractionFromHistory() {
|
||||
context.currentGraph.vectorIndexState.lastWarning = "";
|
||||
return 2;
|
||||
},
|
||||
clearHistoryDirty() {},
|
||||
buildRecoveryResult(status, extra = {}) {
|
||||
return { status, ...extra };
|
||||
},
|
||||
saveGraphToChat() {},
|
||||
restoreRuntimeUiState() {},
|
||||
result: null,
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
`${statusSnippet}\n${rebuildSnippet}\nresult = { onRebuild };`,
|
||||
context,
|
||||
{ filename: indexPath },
|
||||
);
|
||||
|
||||
await context.result.onRebuild();
|
||||
assert.equal(context.lastExtractionStatus.text, "图谱重建完成");
|
||||
assert.equal(context.runtimeStatus.text, "图谱重建完成");
|
||||
assert.equal(context.runtimeStatus.level, "success");
|
||||
}
|
||||
|
||||
await testVectorSyncTerminalStateUpdatesRuntime();
|
||||
await testManualExtractNoBatchesDoesNotStayRunning();
|
||||
await testManualRebuildSetsTerminalRuntimeStatus();
|
||||
|
||||
console.log("mobile-status-regressions tests passed");
|
||||
@@ -372,6 +372,9 @@ function createRerollHarness() {
|
||||
refreshPanelLiveState() {
|
||||
context.refreshPanelCalls += 1;
|
||||
},
|
||||
setRuntimeStatus(text, meta = "", level = "info") {
|
||||
context.runtimeStatus = { text, meta, level };
|
||||
},
|
||||
clearInjectionState() {
|
||||
context.clearInjectionCalls += 1;
|
||||
},
|
||||
@@ -1384,7 +1387,7 @@ async function testRerollUsesBatchBoundaryRollbackAndPersistsState() {
|
||||
assert.equal(harness.prepareVectorStateCalls.length, 1);
|
||||
assert.equal(harness.prepareVectorStateCalls[0][2].skipBackendPurge, true);
|
||||
assert.equal(harness.saveGraphToChatCalls, 1);
|
||||
assert.equal(harness.refreshPanelCalls, 1);
|
||||
assert.equal(harness.refreshPanelCalls, 2);
|
||||
assert.equal(harness.clearInjectionCalls, 1);
|
||||
assert.equal(harness.onManualExtractCalls, 1);
|
||||
assert.equal(harness.currentGraph.historyState.processedMessageHashes[3], undefined);
|
||||
|
||||
Reference in New Issue
Block a user