mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Preserve hidden context for extraction
This commit is contained in:
@@ -6,8 +6,24 @@ import { clampInt } from "./ui-status.js";
|
|||||||
import { sanitizePlannerMessageText } from "./planner-tag-utils.js";
|
import { sanitizePlannerMessageText } from "./planner-tag-utils.js";
|
||||||
import { rollbackBatch } from "./runtime-state.js";
|
import { rollbackBatch } from "./runtime-state.js";
|
||||||
|
|
||||||
|
export function isBmeManagedHiddenMessage(message) {
|
||||||
|
return Boolean(
|
||||||
|
message?.extra &&
|
||||||
|
typeof message.extra === "object" &&
|
||||||
|
message.extra.__st_bme_hide_managed === true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSystemMessageForExtraction(message) {
|
||||||
|
return Boolean(message?.is_system) && !isBmeManagedHiddenMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
export function isAssistantChatMessage(message) {
|
export function isAssistantChatMessage(message) {
|
||||||
return Boolean(message) && !message.is_user && !message.is_system;
|
return (
|
||||||
|
Boolean(message) &&
|
||||||
|
!message.is_user &&
|
||||||
|
!isSystemMessageForExtraction(message)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAssistantTurns(chat) {
|
export function getAssistantTurns(chat) {
|
||||||
@@ -37,7 +53,7 @@ export function buildExtractionMessages(chat, startIdx, endIdx, settings) {
|
|||||||
index++
|
index++
|
||||||
) {
|
) {
|
||||||
const msg = chat[index];
|
const msg = chat[index];
|
||||||
if (msg.is_system) continue;
|
if (isSystemMessageForExtraction(msg)) continue;
|
||||||
messages.push({
|
messages.push({
|
||||||
seq: index,
|
seq: index,
|
||||||
role: msg.is_user ? "user" : "assistant",
|
role: msg.is_user ? "user" : "assistant",
|
||||||
@@ -54,7 +70,7 @@ export function getChatIndexForPlayableSeq(chat, playableSeq) {
|
|||||||
let currentSeq = -1;
|
let currentSeq = -1;
|
||||||
for (let index = 0; index < chat.length; index++) {
|
for (let index = 0; index < chat.length; index++) {
|
||||||
const message = chat[index];
|
const message = chat[index];
|
||||||
if (message?.is_system) continue;
|
if (isSystemMessageForExtraction(message)) continue;
|
||||||
currentSeq++;
|
currentSeq++;
|
||||||
if (currentSeq >= playableSeq) {
|
if (currentSeq >= playableSeq) {
|
||||||
return index;
|
return index;
|
||||||
|
|||||||
39
index.js
39
index.js
@@ -33,6 +33,7 @@ import {
|
|||||||
clampRecoveryStartFloor,
|
clampRecoveryStartFloor,
|
||||||
getAssistantTurns,
|
getAssistantTurns,
|
||||||
isAssistantChatMessage,
|
isAssistantChatMessage,
|
||||||
|
isSystemMessageForExtraction,
|
||||||
pruneProcessedMessageHashesFromFloor,
|
pruneProcessedMessageHashesFromFloor,
|
||||||
resolveDirtyFloorFromMutationMeta,
|
resolveDirtyFloorFromMutationMeta,
|
||||||
rollbackAffectedJournals,
|
rollbackAffectedJournals,
|
||||||
@@ -4359,6 +4360,22 @@ function notifyExtractionIssue(message, title = "ST-BME 提取提示") {
|
|||||||
toastr.warning(message, title, { timeOut: 4500 });
|
toastr.warning(message, title, { timeOut: 4500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
text = "提取完成",
|
||||||
|
meta = "",
|
||||||
|
level = "success",
|
||||||
|
) {
|
||||||
|
const currentText = String(lastExtractionStatus?.text || "");
|
||||||
|
const currentLevel = String(lastExtractionStatus?.level || "");
|
||||||
|
if (currentText !== "AI 生成中" && currentLevel !== "running") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLastExtractionStatus(text, meta, level, {
|
||||||
|
syncRuntime: true,
|
||||||
|
toastKind: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchLocalWithTimeout(
|
async function fetchLocalWithTimeout(
|
||||||
url,
|
url,
|
||||||
options = {},
|
options = {},
|
||||||
@@ -5594,7 +5611,7 @@ const DEFAULT_TRIGGER_KEYWORDS = [
|
|||||||
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
|
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
|
||||||
const pendingMessages = chat
|
const pendingMessages = chat
|
||||||
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
|
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
|
||||||
.filter((msg) => !msg.is_system)
|
.filter((msg) => !isSystemMessageForExtraction(msg))
|
||||||
.map((msg) => ({
|
.map((msg) => ({
|
||||||
role: msg.is_user ? "user" : "assistant",
|
role: msg.is_user ? "user" : "assistant",
|
||||||
content: msg.mes || "",
|
content: msg.mes || "",
|
||||||
@@ -7695,6 +7712,11 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
|
|||||||
}
|
}
|
||||||
saveGraphToChat({ reason: "history-recovery-complete" });
|
saveGraphToChat({ reason: "history-recovery-complete" });
|
||||||
refreshPanelLiveState();
|
refreshPanelLiveState();
|
||||||
|
settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
"提取完成",
|
||||||
|
`历史恢复回放 ${replayedBatches} 批`,
|
||||||
|
"success",
|
||||||
|
);
|
||||||
updateStageNotice(
|
updateStageNotice(
|
||||||
"history",
|
"history",
|
||||||
usedFullRebuild ? "历史恢复完成(全量重建)" : "历史恢复完成",
|
usedFullRebuild ? "历史恢复完成(全量重建)" : "历史恢复完成",
|
||||||
@@ -7736,6 +7758,11 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
|
|||||||
currentGraph.vectorIndexState.replayRequiredNodeIds = [];
|
currentGraph.vectorIndexState.replayRequiredNodeIds = [];
|
||||||
currentGraph.vectorIndexState.dirty = false;
|
currentGraph.vectorIndexState.dirty = false;
|
||||||
currentGraph.vectorIndexState.dirtyReason = "";
|
currentGraph.vectorIndexState.dirtyReason = "";
|
||||||
|
settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
"提取已终止",
|
||||||
|
error?.message || "历史恢复已终止",
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
updateStageNotice(
|
updateStageNotice(
|
||||||
"history",
|
"history",
|
||||||
"历史恢复已终止",
|
"历史恢复已终止",
|
||||||
@@ -7782,6 +7809,11 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
|
|||||||
currentGraph.vectorIndexState.lastIntegrityIssue = null;
|
currentGraph.vectorIndexState.lastIntegrityIssue = null;
|
||||||
saveGraphToChat({ reason: "history-recovery-fallback-rebuild" });
|
saveGraphToChat({ reason: "history-recovery-fallback-rebuild" });
|
||||||
refreshPanelLiveState();
|
refreshPanelLiveState();
|
||||||
|
settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
"提取完成",
|
||||||
|
`历史恢复已退化为全量重建,回放 ${replayedBatches} 批`,
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
updateStageNotice(
|
updateStageNotice(
|
||||||
"history",
|
"history",
|
||||||
"历史恢复已退化为全量重建",
|
"历史恢复已退化为全量重建",
|
||||||
@@ -7814,6 +7846,11 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
|
|||||||
currentGraph.vectorIndexState.lastIntegrityIssue = null;
|
currentGraph.vectorIndexState.lastIntegrityIssue = null;
|
||||||
saveGraphToChat({ reason: "history-recovery-failed" });
|
saveGraphToChat({ reason: "history-recovery-failed" });
|
||||||
refreshPanelLiveState();
|
refreshPanelLiveState();
|
||||||
|
settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
"提取失败",
|
||||||
|
fallbackError?.message || String(fallbackError),
|
||||||
|
"error",
|
||||||
|
);
|
||||||
updateStageNotice(
|
updateStageNotice(
|
||||||
"history",
|
"history",
|
||||||
"历史恢复失败",
|
"历史恢复失败",
|
||||||
|
|||||||
68
tests/chat-history.mjs
Normal file
68
tests/chat-history.mjs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import {
|
||||||
|
buildExtractionMessages,
|
||||||
|
getAssistantTurns,
|
||||||
|
isAssistantChatMessage,
|
||||||
|
isBmeManagedHiddenMessage,
|
||||||
|
isSystemMessageForExtraction,
|
||||||
|
} from "../chat-history.js";
|
||||||
|
|
||||||
|
const visibleAssistant = {
|
||||||
|
is_user: false,
|
||||||
|
is_system: false,
|
||||||
|
mes: "visible assistant",
|
||||||
|
};
|
||||||
|
assert.equal(isAssistantChatMessage(visibleAssistant), true);
|
||||||
|
|
||||||
|
const managedHiddenAssistant = {
|
||||||
|
is_user: false,
|
||||||
|
is_system: true,
|
||||||
|
mes: "managed hidden assistant",
|
||||||
|
extra: { __st_bme_hide_managed: true },
|
||||||
|
};
|
||||||
|
assert.equal(isBmeManagedHiddenMessage(managedHiddenAssistant), true);
|
||||||
|
assert.equal(isSystemMessageForExtraction(managedHiddenAssistant), false);
|
||||||
|
assert.equal(isAssistantChatMessage(managedHiddenAssistant), true);
|
||||||
|
|
||||||
|
const realSystemMessage = {
|
||||||
|
is_user: false,
|
||||||
|
is_system: true,
|
||||||
|
mes: "real system",
|
||||||
|
};
|
||||||
|
assert.equal(isSystemMessageForExtraction(realSystemMessage), true);
|
||||||
|
assert.equal(isAssistantChatMessage(realSystemMessage), false);
|
||||||
|
|
||||||
|
const chat = [
|
||||||
|
{ is_user: false, is_system: true, mes: "greeting/system" },
|
||||||
|
{ is_user: true, is_system: false, mes: "user-1" },
|
||||||
|
managedHiddenAssistant,
|
||||||
|
{ is_user: true, is_system: false, mes: "user-2" },
|
||||||
|
visibleAssistant,
|
||||||
|
realSystemMessage,
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
getAssistantTurns(chat),
|
||||||
|
[2, 4],
|
||||||
|
"managed hidden assistant floors should still be extractable assistant turns",
|
||||||
|
);
|
||||||
|
|
||||||
|
const extractionMessages = buildExtractionMessages(chat, 4, 4, {
|
||||||
|
extractContextTurns: 2,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
extractionMessages.map((message) => ({
|
||||||
|
seq: message.seq,
|
||||||
|
role: message.role,
|
||||||
|
content: message.content,
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{ seq: 1, role: "user", content: "user-1" },
|
||||||
|
{ seq: 2, role: "assistant", content: "managed hidden assistant" },
|
||||||
|
{ seq: 3, role: "user", content: "user-2" },
|
||||||
|
{ seq: 4, role: "assistant", content: "visible assistant" },
|
||||||
|
],
|
||||||
|
"extraction should keep BME-managed hidden context but still skip real system messages",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("chat-history tests passed");
|
||||||
Reference in New Issue
Block a user