mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
refactor: controllerize onReroll and keep reroll harness green
This commit is contained in:
@@ -365,3 +365,167 @@ export async function onManualExtractController(runtime) {
|
||||
runtime.refreshPanelLiveState();
|
||||
}
|
||||
}
|
||||
|
||||
export async function onRerollController(runtime, { fromFloor } = {}) {
|
||||
if (runtime.getIsExtracting?.()) {
|
||||
runtime.toastr?.info?.("记忆提取正在进行中,请稍候");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "busy",
|
||||
affectedBatchCount: 0,
|
||||
error: "记忆提取正在进行中",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
typeof runtime.ensureGraphMutationReady === "function" &&
|
||||
!runtime.ensureGraphMutationReady("重新提取")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: Number.isFinite(fromFloor) ? fromFloor : null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: runtime.getGraphPersistenceState?.()?.loadState || "graph-not-ready",
|
||||
affectedBatchCount: 0,
|
||||
error:
|
||||
typeof runtime.getGraphMutationBlockReason === "function"
|
||||
? runtime.getGraphMutationBlockReason("重新提取")
|
||||
: "重新提取已暂停:图谱尚未就绪。",
|
||||
};
|
||||
}
|
||||
|
||||
if (!runtime.getCurrentGraph?.()) {
|
||||
runtime.toastr?.info?.("图谱为空,无需重 Roll");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "empty-graph",
|
||||
affectedBatchCount: 0,
|
||||
error: "图谱为空",
|
||||
};
|
||||
}
|
||||
|
||||
const context = runtime.getContext();
|
||||
const chat = context?.chat;
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
runtime.toastr?.info?.("当前聊天为空");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "empty-chat",
|
||||
affectedBatchCount: 0,
|
||||
error: "当前聊天为空",
|
||||
};
|
||||
}
|
||||
|
||||
let targetFloor = Number.isFinite(fromFloor) ? fromFloor : null;
|
||||
if (targetFloor === null) {
|
||||
const assistantTurns = runtime.getAssistantTurns(chat);
|
||||
if (assistantTurns.length === 0) {
|
||||
runtime.toastr?.info?.("聊天中没有 AI 回复");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "no-assistant-turn",
|
||||
affectedBatchCount: 0,
|
||||
error: "聊天中没有 AI 回复",
|
||||
};
|
||||
}
|
||||
targetFloor = assistantTurns[assistantTurns.length - 1];
|
||||
}
|
||||
|
||||
runtime.setRuntimeStatus(
|
||||
"重新提取中",
|
||||
Number.isFinite(targetFloor)
|
||||
? `准备从楼层 ${targetFloor} 开始回滚并重新提取`
|
||||
: "准备回滚最新 AI 楼并重新提取",
|
||||
"running",
|
||||
);
|
||||
|
||||
const lastProcessed = runtime.getLastProcessedAssistantFloor();
|
||||
const alreadyExtracted = targetFloor <= lastProcessed;
|
||||
|
||||
if (!alreadyExtracted) {
|
||||
runtime.toastr?.info?.("该楼层尚未提取,直接执行提取…", "ST-BME 重 Roll", {
|
||||
timeOut: 2000,
|
||||
});
|
||||
await runtime.onManualExtract();
|
||||
return {
|
||||
success: true,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: true,
|
||||
requestedFloor: targetFloor,
|
||||
effectiveFromFloor: lastProcessed + 1,
|
||||
recoveryPath: "direct-extract",
|
||||
affectedBatchCount: 0,
|
||||
extractionStatus: runtime.getLastExtractionStatusLevel?.() || "idle",
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
||||
runtime.console.log(`[ST-BME] 重 Roll 开始,目标楼层: ${targetFloor}`);
|
||||
let rollbackResult;
|
||||
try {
|
||||
rollbackResult = await runtime.rollbackGraphForReroll(targetFloor, context);
|
||||
} catch (e) {
|
||||
if (runtime.isAbortError(e)) {
|
||||
runtime.setRuntimeStatus(
|
||||
"重新提取已取消",
|
||||
e.message || "聊天已切换",
|
||||
"warning",
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: targetFloor,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "aborted",
|
||||
affectedBatchCount: 0,
|
||||
error: e.message || "聊天已切换,重新提取已取消",
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (!rollbackResult?.success) {
|
||||
runtime.setRuntimeStatus(
|
||||
"重新提取失败",
|
||||
rollbackResult.error || "回滚失败",
|
||||
"error",
|
||||
);
|
||||
runtime.toastr?.error?.(rollbackResult.error, "ST-BME 重 Roll");
|
||||
return rollbackResult;
|
||||
}
|
||||
|
||||
const rerollDesc =
|
||||
rollbackResult.effectiveFromFloor !== targetFloor
|
||||
? `已按批次边界回滚到楼层 ${rollbackResult.effectiveFromFloor} 开始重新提取…`
|
||||
: `已回滚到楼层 ${targetFloor} 开始重新提取…`;
|
||||
runtime.toastr?.info?.(rerollDesc, "ST-BME 重 Roll", {
|
||||
timeOut: 2500,
|
||||
});
|
||||
|
||||
await runtime.onManualExtract();
|
||||
runtime.refreshPanelLiveState();
|
||||
return {
|
||||
...rollbackResult,
|
||||
extractionTriggered: true,
|
||||
extractionStatus: runtime.getLastExtractionStatusLevel?.() || "idle",
|
||||
};
|
||||
}
|
||||
|
||||
180
index.js
180
index.js
@@ -21,6 +21,7 @@ import { consolidateMemories } from "./consolidator.js";
|
||||
import {
|
||||
executeExtractionBatchController,
|
||||
onManualExtractController,
|
||||
onRerollController,
|
||||
runExtractionController,
|
||||
} from "./extraction-controller.js";
|
||||
import {
|
||||
@@ -4642,166 +4643,27 @@ async function onManualExtract() {
|
||||
}
|
||||
|
||||
async function onReroll({ fromFloor } = {}) {
|
||||
if (isExtracting) {
|
||||
toastr.info("记忆提取正在进行中,请稍候");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "busy",
|
||||
affectedBatchCount: 0,
|
||||
error: "记忆提取正在进行中",
|
||||
};
|
||||
}
|
||||
if (
|
||||
typeof ensureGraphMutationReady === "function" &&
|
||||
!ensureGraphMutationReady("重新提取")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: Number.isFinite(fromFloor) ? fromFloor : null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath:
|
||||
typeof graphPersistenceState === "object"
|
||||
? graphPersistenceState.loadState
|
||||
: "graph-not-ready",
|
||||
affectedBatchCount: 0,
|
||||
error:
|
||||
typeof getGraphMutationBlockReason === "function"
|
||||
? getGraphMutationBlockReason("重新提取")
|
||||
: "重新提取已暂停:图谱尚未就绪。",
|
||||
};
|
||||
}
|
||||
if (!currentGraph) {
|
||||
toastr.info("图谱为空,无需重 Roll");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "empty-graph",
|
||||
affectedBatchCount: 0,
|
||||
error: "图谱为空",
|
||||
};
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
const chat = context.chat;
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
toastr.info("当前聊天为空");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "empty-chat",
|
||||
affectedBatchCount: 0,
|
||||
error: "当前聊天为空",
|
||||
};
|
||||
}
|
||||
|
||||
// 确定回滚起点
|
||||
let targetFloor = Number.isFinite(fromFloor) ? fromFloor : null;
|
||||
if (targetFloor === null) {
|
||||
// 默认:重做最新 AI 楼
|
||||
const assistantTurns = getAssistantTurns(chat);
|
||||
if (assistantTurns.length === 0) {
|
||||
toastr.info("聊天中没有 AI 回复");
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: null,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: "no-assistant-turn",
|
||||
affectedBatchCount: 0,
|
||||
error: "聊天中没有 AI 回复",
|
||||
};
|
||||
}
|
||||
targetFloor = assistantTurns[assistantTurns.length - 1];
|
||||
}
|
||||
|
||||
setRuntimeStatus(
|
||||
"重新提取中",
|
||||
Number.isFinite(targetFloor)
|
||||
? `准备从楼层 ${targetFloor} 开始回滚并重新提取`
|
||||
: "准备回滚最新 AI 楼并重新提取",
|
||||
"running",
|
||||
return await onRerollController(
|
||||
{
|
||||
console,
|
||||
ensureGraphMutationReady,
|
||||
getAssistantTurns,
|
||||
getContext,
|
||||
getCurrentGraph: () => currentGraph,
|
||||
getGraphMutationBlockReason,
|
||||
getGraphPersistenceState: () => graphPersistenceState,
|
||||
getIsExtracting: () => isExtracting,
|
||||
getLastExtractionStatusLevel: () => lastExtractionStatus?.level || "idle",
|
||||
getLastProcessedAssistantFloor,
|
||||
isAbortError,
|
||||
onManualExtract,
|
||||
refreshPanelLiveState,
|
||||
rollbackGraphForReroll,
|
||||
setRuntimeStatus,
|
||||
toastr,
|
||||
},
|
||||
{ fromFloor },
|
||||
);
|
||||
|
||||
const lastProcessed = getLastProcessedAssistantFloor();
|
||||
const alreadyExtracted = targetFloor <= lastProcessed;
|
||||
|
||||
if (!alreadyExtracted) {
|
||||
// 目标楼层未提取过 → 直接走手动提取即可,不需要回滚
|
||||
toastr.info("该楼层尚未提取,直接执行提取…", "ST-BME 重 Roll", {
|
||||
timeOut: 2000,
|
||||
});
|
||||
await onManualExtract();
|
||||
return {
|
||||
success: true,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: true,
|
||||
requestedFloor: targetFloor,
|
||||
effectiveFromFloor: lastProcessed + 1,
|
||||
recoveryPath: "direct-extract",
|
||||
affectedBatchCount: 0,
|
||||
extractionStatus: lastExtractionStatus?.level || "idle",
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[ST-BME] 重 Roll 开始,目标楼层: ${targetFloor}`);
|
||||
let rollbackResult;
|
||||
try {
|
||||
rollbackResult = await rollbackGraphForReroll(targetFloor, context);
|
||||
} catch (e) {
|
||||
if (isAbortError(e)) {
|
||||
setRuntimeStatus('重新提取已取消', e.message || '聊天已切换', 'warning');
|
||||
return {
|
||||
success: false,
|
||||
rollbackPerformed: false,
|
||||
extractionTriggered: false,
|
||||
requestedFloor: targetFloor,
|
||||
effectiveFromFloor: null,
|
||||
recoveryPath: 'aborted',
|
||||
affectedBatchCount: 0,
|
||||
error: e.message || '聊天已切换,重新提取已取消',
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (!rollbackResult?.success) {
|
||||
setRuntimeStatus(
|
||||
"重新提取失败",
|
||||
rollbackResult.error || "回滚失败",
|
||||
"error",
|
||||
);
|
||||
toastr.error(rollbackResult.error, "ST-BME 重 Roll");
|
||||
return rollbackResult;
|
||||
}
|
||||
|
||||
const rerollDesc =
|
||||
rollbackResult.effectiveFromFloor !== targetFloor
|
||||
? `已按批次边界回滚到楼层 ${rollbackResult.effectiveFromFloor} 开始重新提取…`
|
||||
: `已回滚到楼层 ${targetFloor} 开始重新提取…`;
|
||||
toastr.info(rerollDesc, "ST-BME 重 Roll", {
|
||||
timeOut: 2500,
|
||||
});
|
||||
|
||||
await onManualExtract();
|
||||
refreshPanelLiveState();
|
||||
return {
|
||||
...rollbackResult,
|
||||
extractionTriggered: true,
|
||||
extractionStatus: lastExtractionStatus?.level || "idle",
|
||||
};
|
||||
}
|
||||
|
||||
async function onManualSleep() {
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
onBeforeCombinePromptsController,
|
||||
onGenerationAfterCommandsController,
|
||||
} from "../event-binding.js";
|
||||
import { onRerollController } from "../extraction-controller.js";
|
||||
|
||||
const extensionsShimSource = [
|
||||
"export const extension_settings = globalThis.__p0ExtensionSettings || {};",
|
||||
@@ -461,7 +462,17 @@ function createRerollHarness() {
|
||||
context.onManualExtractCalls += 1;
|
||||
context.lastExtractionStatus = { level: context.manualExtractLevel };
|
||||
},
|
||||
ensureGraphMutationReady() {
|
||||
return true;
|
||||
},
|
||||
getGraphMutationBlockReason() {
|
||||
return "graph-not-ready";
|
||||
},
|
||||
graphPersistenceState: {
|
||||
loadState: "loaded",
|
||||
},
|
||||
createUiStatus,
|
||||
onRerollController,
|
||||
isAbortError: (e) => e?.name === "AbortError",
|
||||
assertRecoveryChatStillActive() {
|
||||
// no-op in test
|
||||
|
||||
Reference in New Issue
Block a user