mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
fix(recall): reuse reroll recall from host generation type
This commit is contained in:
18
index.js
18
index.js
@@ -156,10 +156,6 @@ import {
|
||||
resolvePersistenceChatIdCore,
|
||||
resolveRuntimeGraphFallbackIdentityCore,
|
||||
} from "./runtime/identity-resolver.js";
|
||||
import {
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
} from "./runtime/reroll-transaction-boundary.js";
|
||||
import { createRecallInputState } from "./runtime/recall-input-state.js";
|
||||
import { createRerollRecallInput } from "./runtime/reroll-recall-input.js";
|
||||
import { createGenerationContextTracker } from "./runtime/generation-context.js";
|
||||
@@ -1666,8 +1662,6 @@ const rerollRecallInput = createRerollRecallInput({
|
||||
clearPendingHostGenerationInputSnapshot(...args),
|
||||
clearPendingRecallSendIntent: (...args) => clearPendingRecallSendIntent(...args),
|
||||
console,
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
createTrivialRecallSkipSentinel: (...args) =>
|
||||
createTrivialRecallSkipSentinel(...args),
|
||||
findLatestUserChatMessageWithIndex: (...args) =>
|
||||
@@ -14581,22 +14575,10 @@ function getLastNonSystemChatMessage(chat) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPendingRerollRecallReuse() {
|
||||
return rerollRecallInput.getPendingRerollRecallReuse();
|
||||
}
|
||||
|
||||
function clearPendingRerollRecallReuse(reason = "") {
|
||||
return rerollRecallInput.clearPendingRerollRecallReuse(reason);
|
||||
}
|
||||
|
||||
function prepareRerollRecallReuse({ fromFloor = null, meta = null } = {}) {
|
||||
return rerollRecallInput.prepareRerollRecallReuse({ fromFloor, meta });
|
||||
}
|
||||
|
||||
function consumePendingRerollRecallReuse(chat = getContext()?.chat) {
|
||||
return rerollRecallInput.consumePendingRerollRecallReuse(chat);
|
||||
}
|
||||
|
||||
function buildRecallRecentMessages(chat, limit, syntheticUserMessage = "") {
|
||||
return buildRecallRecentMessagesController(
|
||||
chat,
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"test:graph-snapshot-upgrade": "node tests/graph-snapshot-upgrade.mjs",
|
||||
"test:snapshot-forward-compat": "node tests/snapshot-forward-compat.mjs",
|
||||
"test:luker-snapshot-forward-compat": "node tests/luker-snapshot-forward-compat.mjs",
|
||||
"test:reroll-transaction-boundary": "node tests/reroll-transaction-boundary.mjs",
|
||||
"test:vector-gate": "node tests/vector-gate.mjs",
|
||||
"test:hide-engine": "node tests/hide-engine.mjs",
|
||||
"test:maintenance-journal": "node tests/maintenance-journal.mjs",
|
||||
|
||||
@@ -118,7 +118,14 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) {
|
||||
"planner-handoff",
|
||||
]);
|
||||
const isActiveInputSource = activeInputSources.has(recallSource);
|
||||
if (isActiveInputSource) {
|
||||
const noNewUserGenerationTypes = new Set([
|
||||
"swipe",
|
||||
"regenerate",
|
||||
"continue",
|
||||
"history",
|
||||
]);
|
||||
const isNoNewUserGeneration = noNewUserGenerationTypes.has(generationType);
|
||||
if (isActiveInputSource && !isNoNewUserGeneration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -203,7 +210,7 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) {
|
||||
"persisted-user-floor",
|
||||
]);
|
||||
const canTrustUserFloorRecord = Boolean(
|
||||
!isActiveInputSource &&
|
||||
(!isActiveInputSource || isNoNewUserGeneration) &&
|
||||
!boundUserFloorText &&
|
||||
(generationType !== "normal" || userFloorSources.has(recallSource)),
|
||||
);
|
||||
|
||||
@@ -134,10 +134,15 @@ export function bumpPersistedRecallGenerationCount(chat, userMessageIndex) {
|
||||
|
||||
export function resolveGenerationTargetUserMessageIndex(
|
||||
chat,
|
||||
{ generationType = "normal" } = {},
|
||||
{ generationType = "normal", generationContext = null } = {},
|
||||
) {
|
||||
if (!Array.isArray(chat) || chat.length === 0) return null;
|
||||
return resolveGenerationParentUserFloor(chat, { type: generationType });
|
||||
return resolveGenerationParentUserFloor(
|
||||
chat,
|
||||
generationContext && typeof generationContext === "object"
|
||||
? { ...generationContext, type: generationContext.type || generationType }
|
||||
: { type: generationType },
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveFinalRecallInjectionSource({
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export function createRerollRecallInput(deps = {}) {
|
||||
let pendingRerollRecallReuse = null;
|
||||
const plannerRecallHandoffs = new Map();
|
||||
|
||||
const getContext = (...args) => deps.getContext?.(...args);
|
||||
const getCurrentChatId = (...args) => deps.getCurrentChatId?.(...args);
|
||||
const normalizeChatIdCandidate = (value = "") =>
|
||||
deps.normalizeChatIdCandidate?.(value) ?? String(value ?? "").trim();
|
||||
@@ -13,94 +11,13 @@ export function createRerollRecallInput(deps = {}) {
|
||||
deps.getLastRecallSentUserMessage?.() || {};
|
||||
const getPendingRecallSendIntent = () =>
|
||||
deps.getPendingRecallSendIntent?.() || {};
|
||||
const getGenerationRecallTransactionTtlMs = () =>
|
||||
Number.isFinite(Number(deps.GENERATION_RECALL_TRANSACTION_TTL_MS))
|
||||
? Number(deps.GENERATION_RECALL_TRANSACTION_TTL_MS)
|
||||
: 60000;
|
||||
const getPlannerRecallHandoffTtlMs = () =>
|
||||
Number.isFinite(Number(deps.PLANNER_RECALL_HANDOFF_TTL_MS))
|
||||
? Number(deps.PLANNER_RECALL_HANDOFF_TTL_MS)
|
||||
: 60000;
|
||||
|
||||
function getPendingRerollRecallReuse() {
|
||||
return pendingRerollRecallReuse;
|
||||
}
|
||||
|
||||
function clearPendingRerollRecallReuse(reason = "") {
|
||||
const previous = pendingRerollRecallReuse;
|
||||
pendingRerollRecallReuse = null;
|
||||
return previous;
|
||||
}
|
||||
|
||||
function prepareRerollRecallReuse({ fromFloor = null, meta = null } = {}) {
|
||||
const context = getContext();
|
||||
const chat = context?.chat;
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
pendingRerollRecallReuse = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const latestUser = deps.findLatestUserChatMessageWithIndex(chat);
|
||||
const targetUserMessageIndex = Number.isFinite(latestUser?.index)
|
||||
? latestUser.index
|
||||
: null;
|
||||
if (!Number.isFinite(targetUserMessageIndex)) {
|
||||
pendingRerollRecallReuse = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const userMessage = chat[targetUserMessageIndex];
|
||||
const userText = normalizeRecallInputText(userMessage?.mes || "");
|
||||
if (!userText) {
|
||||
pendingRerollRecallReuse = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const persistedRecord = deps.readPersistedRecallFromUserMessage(
|
||||
chat,
|
||||
targetUserMessageIndex,
|
||||
);
|
||||
const chatId = normalizeChatIdCandidate(getCurrentChatId(context));
|
||||
const prepared = deps.createRerollRecallReuseMarker({
|
||||
chatId,
|
||||
fromFloor,
|
||||
targetUserMessageIndex,
|
||||
userText,
|
||||
persistedRecord,
|
||||
hashRecallInput,
|
||||
now: Date.now(),
|
||||
meta,
|
||||
});
|
||||
if (!prepared.marker) {
|
||||
pendingRerollRecallReuse = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
pendingRerollRecallReuse = prepared.marker;
|
||||
return pendingRerollRecallReuse;
|
||||
}
|
||||
|
||||
function consumePendingRerollRecallReuse(chat = getContext()?.chat) {
|
||||
const reuse = pendingRerollRecallReuse;
|
||||
if (!reuse) return null;
|
||||
|
||||
const activeChatId = normalizeChatIdCandidate(getCurrentChatId());
|
||||
const latestUser = deps.findLatestUserChatMessageWithIndex(chat);
|
||||
const targetUserMessageIndex = Number.isFinite(latestUser?.index)
|
||||
? latestUser.index
|
||||
: reuse.targetUserMessageIndex;
|
||||
const userText = normalizeRecallInputText(chat?.[targetUserMessageIndex]?.mes || "");
|
||||
const consumed = deps.consumeRerollRecallReuseMarker({
|
||||
marker: reuse,
|
||||
activeChatId,
|
||||
latestUserMessageIndex: targetUserMessageIndex,
|
||||
currentUserText: userText,
|
||||
hashRecallInput,
|
||||
now: Date.now(),
|
||||
ttlMs: getGenerationRecallTransactionTtlMs(),
|
||||
});
|
||||
pendingRerollRecallReuse = consumed.marker;
|
||||
return consumed.consumed ? consumed.override : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildGenerationAfterCommandsRecallInput(type, params = {}, chat) {
|
||||
@@ -115,6 +32,7 @@ export function createRerollRecallInput(deps = {}) {
|
||||
|
||||
const targetUserMessageIndex = deps.resolveGenerationTargetUserMessageIndex(chat, {
|
||||
generationType,
|
||||
generationContext: params?.generationContext,
|
||||
});
|
||||
|
||||
// 对于 history 类型(continue/regenerate/swipe),必须依赖 chat 中的用户消息
|
||||
@@ -125,7 +43,10 @@ export function createRerollRecallInput(deps = {}) {
|
||||
targetUserMessageIndex: null,
|
||||
};
|
||||
}
|
||||
const historyInput = buildHistoryGenerationRecallInput(chat);
|
||||
const historyInput = buildHistoryGenerationRecallInput(chat, {
|
||||
generationType,
|
||||
generationContext: params?.generationContext,
|
||||
});
|
||||
if (!historyInput) {
|
||||
return {
|
||||
generationType,
|
||||
@@ -157,11 +78,6 @@ export function createRerollRecallInput(deps = {}) {
|
||||
}
|
||||
|
||||
function buildNormalGenerationRecallInput(chat, options = {}) {
|
||||
const rerollReuse = consumePendingRerollRecallReuse(chat);
|
||||
if (rerollReuse) {
|
||||
return rerollReuse;
|
||||
}
|
||||
|
||||
const lastNonSystemMessage = deps.getLastNonSystemChatMessage(chat);
|
||||
const tailUserText = lastNonSystemMessage?.is_user
|
||||
? normalizeRecallInputText(lastNonSystemMessage?.mes || "")
|
||||
@@ -294,19 +210,24 @@ export function createRerollRecallInput(deps = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildHistoryGenerationRecallInput(chat) {
|
||||
function buildHistoryGenerationRecallInput(chat, options = {}) {
|
||||
const generationType = String(options?.generationType || "history").trim() || "history";
|
||||
const lastRecallSentUserMessage = getLastRecallSentUserMessage();
|
||||
const targetUserMessageIndex = deps.resolveGenerationTargetUserMessageIndex(chat, {
|
||||
generationType,
|
||||
generationContext: options?.generationContext,
|
||||
});
|
||||
const targetUserText = Number.isFinite(targetUserMessageIndex)
|
||||
? normalizeRecallInputText(chat?.[targetUserMessageIndex]?.mes || "")
|
||||
: "";
|
||||
const latestUserText = normalizeRecallInputText(
|
||||
deps.getLatestUserChatMessage(chat)?.mes || lastRecallSentUserMessage.text,
|
||||
targetUserText || deps.getLatestUserChatMessage(chat)?.mes || lastRecallSentUserMessage.text,
|
||||
);
|
||||
if (!latestUserText) return null;
|
||||
const targetUserMessageIndex = deps.resolveGenerationTargetUserMessageIndex(chat, {
|
||||
generationType: "history",
|
||||
});
|
||||
|
||||
return {
|
||||
overrideUserMessage: latestUserText,
|
||||
generationType: "history",
|
||||
generationType,
|
||||
targetUserMessageIndex,
|
||||
overrideSource: Number.isFinite(targetUserMessageIndex)
|
||||
? "chat-last-user"
|
||||
@@ -429,10 +350,7 @@ export function createRerollRecallInput(deps = {}) {
|
||||
}
|
||||
|
||||
return {
|
||||
prepareRerollRecallReuse,
|
||||
getPendingRerollRecallReuse,
|
||||
clearPendingRerollRecallReuse,
|
||||
consumePendingRerollRecallReuse,
|
||||
buildNormalGenerationRecallInput,
|
||||
buildHistoryGenerationRecallInput,
|
||||
buildGenerationAfterCommandsRecallInput,
|
||||
|
||||
@@ -201,10 +201,6 @@ import {
|
||||
syncGraphLoadFromLiveContextImpl,
|
||||
writeAuthorityCheckpointFromCurrentGraphImpl,
|
||||
} from "../sync/graph-load-persist.js";
|
||||
import {
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
} from "../runtime/reroll-transaction-boundary.js";
|
||||
|
||||
function isAuthorityVectorConfig(config = null) {
|
||||
return config?.mode === "authority" || config?.source === "authority-trivium";
|
||||
@@ -892,8 +888,6 @@ async function createGraphPersistenceHarness({
|
||||
shouldUseAuthorityJobsImpl,
|
||||
syncGraphLoadFromLiveContextImpl,
|
||||
writeAuthorityCheckpointFromCurrentGraphImpl,
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
createRecallMessageUiController() {
|
||||
return {
|
||||
refreshPersistedRecallMessageUi: () => ({
|
||||
|
||||
@@ -34,10 +34,6 @@ import {
|
||||
shouldRunRecallForTransaction,
|
||||
} from "../../ui/ui-status.js";
|
||||
import { defaultSettings, mergePersistedSettings } from "../../runtime/settings-defaults.js";
|
||||
import {
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
} from "../../runtime/reroll-transaction-boundary.js";
|
||||
import { createRecallInputState } from "../../runtime/recall-input-state.js";
|
||||
import { createRerollRecallInput } from "../../runtime/reroll-recall-input.js";
|
||||
import { createGenerationRecallTransactions } from "../../runtime/generation-recall-transactions.js";
|
||||
@@ -487,8 +483,6 @@ export async function createGenerationRecallHarness(options = {}) {
|
||||
clearPendingRecallSendIntent: (...args) =>
|
||||
recallInputState.clearPendingRecallSendIntent(...args),
|
||||
console,
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
createTrivialRecallSkipSentinel,
|
||||
findLatestUserChatMessageWithIndex,
|
||||
formatInjection,
|
||||
@@ -840,10 +834,6 @@ export async function createGenerationRecallHarness(options = {}) {
|
||||
recallInputState.getPendingHostGenerationInputSnapshot(...args),
|
||||
clearPendingHostGenerationInputSnapshot: (...args) =>
|
||||
recallInputState.clearPendingHostGenerationInputSnapshot(...args),
|
||||
prepareRerollRecallReuse: (...args) =>
|
||||
rerollRecallInput.prepareRerollRecallReuse(...args),
|
||||
getPendingRerollRecallReuse: (...args) =>
|
||||
rerollRecallInput.getPendingRerollRecallReuse(...args),
|
||||
clearPendingRerollRecallReuse,
|
||||
recordRecallSendIntent: (...args) =>
|
||||
recallInputState.recordRecallSendIntent(...args),
|
||||
|
||||
@@ -534,11 +534,6 @@ writePersistedRecallToUserMessage(
|
||||
}),
|
||||
);
|
||||
|
||||
const preparedRerollReuse = rerollInputHarness.result.prepareRerollRecallReuse({
|
||||
fromFloor: 1,
|
||||
});
|
||||
assert.ok(preparedRerollReuse, "assistant-only reroll should prepare recall reuse marker");
|
||||
|
||||
rerollInputHarness.result.recordRecallSendIntent(
|
||||
"错误的主动输入不应覆盖 reroll 用户楼",
|
||||
"send-intent",
|
||||
@@ -548,11 +543,16 @@ rerollInputHarness.result.freezeHostGenerationInputSnapshot(
|
||||
"host-generation-lifecycle",
|
||||
);
|
||||
|
||||
const rerollReplacementInput = rerollInputHarness.result.buildNormalGenerationRecallInput(
|
||||
rerollInputHarness.chat,
|
||||
const rerollReplacementInput = rerollInputHarness.result.buildGenerationAfterCommandsRecallInput(
|
||||
"swipe",
|
||||
{
|
||||
frozenInputSnapshot: rerollInputHarness.result.getPendingHostGenerationInputSnapshot(),
|
||||
generationContext: {
|
||||
type: "swipe",
|
||||
kind: "no-new-user",
|
||||
swipedAssistantFloor: 1,
|
||||
},
|
||||
},
|
||||
rerollInputHarness.chat,
|
||||
);
|
||||
assert.equal(
|
||||
rerollReplacementInput.overrideUserMessage,
|
||||
@@ -560,14 +560,9 @@ assert.equal(
|
||||
"reroll replacement should ignore stale live input sources and bind to stable user floor",
|
||||
);
|
||||
assert.equal(rerollReplacementInput.overrideSource, "chat-last-user");
|
||||
assert.equal(rerollReplacementInput.overrideReason, "reroll-user-floor-reuse");
|
||||
assert.equal(
|
||||
rerollInputHarness.result.getPendingRerollRecallReuse(),
|
||||
null,
|
||||
"reroll reuse marker should be one-shot after binding recall input",
|
||||
);
|
||||
assert.equal(rerollReplacementInput.generationType, "swipe");
|
||||
|
||||
console.log(" ✓ reroll replacement normal input is forced to stable user-floor recall source");
|
||||
console.log(" ✓ reroll replacement input is forced by host type to stable user-floor recall source");
|
||||
|
||||
const legacyUnboundReuseChat = [
|
||||
{ is_user: true, mes: "旧记录没有绑定楼层" },
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// ST-BME restrained rebirth — Phase 4 reroll transaction boundary tests.
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
consumeRerollRecallReuseMarker,
|
||||
createRerollRecallReuseMarker,
|
||||
} from "../runtime/reroll-transaction-boundary.js";
|
||||
|
||||
const hashRecallInput = (text) => `h:${String(text || "").trim()}`;
|
||||
|
||||
const prepared = createRerollRecallReuseMarker({
|
||||
chatId: "chat-a",
|
||||
fromFloor: 4,
|
||||
targetUserMessageIndex: 2,
|
||||
userText: " hello\n",
|
||||
persistedRecord: {
|
||||
injectionText: "memory",
|
||||
boundUserFloorText: "hello",
|
||||
},
|
||||
hashRecallInput,
|
||||
now: 1000,
|
||||
});
|
||||
assert.equal(prepared.reason, "prepared");
|
||||
assert.equal(prepared.marker.chatId, "chat-a");
|
||||
assert.equal(prepared.marker.fromFloor, 4);
|
||||
assert.equal(prepared.marker.targetUserMessageIndex, 2);
|
||||
assert.equal(prepared.marker.userHash, "h:hello");
|
||||
|
||||
const consumed = consumeRerollRecallReuseMarker({
|
||||
marker: prepared.marker,
|
||||
activeChatId: "chat-a",
|
||||
latestUserMessageIndex: 2,
|
||||
currentUserText: "hello",
|
||||
hashRecallInput,
|
||||
now: 1500,
|
||||
ttlMs: 5000,
|
||||
});
|
||||
assert.equal(consumed.consumed, true);
|
||||
assert.equal(consumed.override.rerollRecallReuse, true);
|
||||
assert.equal(consumed.override.targetUserMessageIndex, 2);
|
||||
|
||||
console.log(" ✓ reroll recall reuse marker is one-shot and floor-bound");
|
||||
|
||||
assert.equal(
|
||||
createRerollRecallReuseMarker({
|
||||
userText: "changed",
|
||||
persistedRecord: { injectionText: "memory", boundUserFloorText: "original" },
|
||||
}).reason,
|
||||
"bound-user-floor-mismatch",
|
||||
);
|
||||
assert.equal(
|
||||
createRerollRecallReuseMarker({
|
||||
userText: "hello",
|
||||
persistedRecord: { injectionText: "" },
|
||||
}).reason,
|
||||
"missing-persisted-recall",
|
||||
);
|
||||
|
||||
for (const [caseName, options, reason] of [
|
||||
["chat", { activeChatId: "other-chat" }, "chat-mismatch"],
|
||||
["ttl", { now: 7001, ttlMs: 5000 }, "expired"],
|
||||
["floor", { latestUserMessageIndex: 3 }, "target-user-floor-changed"],
|
||||
["text", { currentUserText: "changed" }, "user-text-changed"],
|
||||
]) {
|
||||
const result = consumeRerollRecallReuseMarker({
|
||||
marker: prepared.marker,
|
||||
activeChatId: "chat-a",
|
||||
latestUserMessageIndex: 2,
|
||||
currentUserText: "hello",
|
||||
hashRecallInput,
|
||||
now: 1500,
|
||||
ttlMs: 5000,
|
||||
...options,
|
||||
});
|
||||
assert.equal(result.consumed, false, `${caseName} mismatch must reject reuse`);
|
||||
assert.equal(result.reason, reason);
|
||||
assert.equal(result.marker, null, `${caseName} mismatch must clear marker`);
|
||||
}
|
||||
|
||||
console.log(" ✓ reroll marker rejects stale, cross-chat, changed-floor, changed-text reuse");
|
||||
console.log("reroll-transaction-boundary tests passed");
|
||||
Reference in New Issue
Block a user