feat: support multi-owner scene recall anchors

This commit is contained in:
Youzini-afk
2026-04-08 21:29:36 +08:00
parent 835303d4fb
commit d7989303d9
16 changed files with 1729 additions and 91 deletions

View File

@@ -847,7 +847,11 @@ function listToSet(values = []) {
return new Set(uniqueIds(values));
}
export function computeKnowledgeGateForNode(
function normalizeOwnerKeyList(ownerKeys = []) {
return uniqueIds(Array.isArray(ownerKeys) ? ownerKeys : [ownerKeys]);
}
function computeKnowledgeGateForSingleOwner(
graph,
node,
ownerKey = "",
@@ -971,6 +975,115 @@ export function computeKnowledgeGateForNode(
};
}
export function computeKnowledgeGateForNode(
graph,
node,
ownerKey = "",
{
vectorScore = 0,
graphScore = 0,
lexicalScore = 0,
scopeBucket = "",
injectLowConfidenceObjectiveMemory = false,
} = {},
) {
const normalizedOwnerKeys = normalizeOwnerKeyList(ownerKey);
if (normalizedOwnerKeys.length <= 1) {
const singleGate = computeKnowledgeGateForSingleOwner(
graph,
node,
normalizedOwnerKeys[0] || "",
{
vectorScore,
graphScore,
lexicalScore,
scopeBucket,
injectLowConfidenceObjectiveMemory,
},
);
return {
...singleGate,
ownerCoverage: singleGate.visible ? 1 : 0,
visibleOwnerKeys: singleGate.visible && normalizedOwnerKeys[0]
? [normalizedOwnerKeys[0]]
: [],
suppressedOwnerKeys:
singleGate.visible || !normalizedOwnerKeys[0]
? []
: [normalizedOwnerKeys[0]],
ownerResults: normalizedOwnerKeys[0]
? { [normalizedOwnerKeys[0]]: singleGate }
: {},
};
}
const ownerResults = {};
const visibleOwnerKeys = [];
const suppressedOwnerKeys = [];
let bestVisibilityScore = 0;
let bestThreshold = 0;
let anchored = false;
let rescued = false;
let bestMode = "suppressed";
let bestSuppressedReason = "";
for (const candidateOwnerKey of normalizedOwnerKeys) {
const result = computeKnowledgeGateForSingleOwner(
graph,
node,
candidateOwnerKey,
{
vectorScore,
graphScore,
lexicalScore,
scopeBucket,
injectLowConfidenceObjectiveMemory,
},
);
ownerResults[candidateOwnerKey] = result;
bestVisibilityScore = Math.max(
bestVisibilityScore,
Number(result.visibilityScore || 0),
);
bestThreshold = Math.max(bestThreshold, Number(result.threshold || 0));
if (result.visible) {
visibleOwnerKeys.push(candidateOwnerKey);
anchored ||= Boolean(result.anchored);
rescued ||= Boolean(result.rescued);
if (
bestMode === "suppressed" ||
(result.anchored && bestMode !== "manual-known") ||
(result.mode === "manual-known")
) {
bestMode = String(result.mode || bestMode);
}
} else {
suppressedOwnerKeys.push(candidateOwnerKey);
if (!bestSuppressedReason && result.suppressedReason) {
bestSuppressedReason = String(result.suppressedReason || "");
}
}
}
const visible = visibleOwnerKeys.length > 0;
return {
visible,
anchored,
rescued,
suppressed: !visible,
suppressedReason: visible ? "" : bestSuppressedReason || "low-visibility",
visibilityScore: bestVisibilityScore,
mode: visible ? bestMode : "suppressed",
threshold: bestThreshold,
ownerCoverage: normalizedOwnerKeys.length
? visibleOwnerKeys.length / normalizedOwnerKeys.length
: 0,
visibleOwnerKeys,
suppressedOwnerKeys,
ownerResults,
};
}
export function applyManualKnowledgeOverride(
graph,
{ ownerKey = "", ownerType = "", ownerName = "", nodeId = "", mode = "known" } = {},

View File

@@ -58,6 +58,12 @@ function normalizeStringArray(values = []) {
return result;
}
function normalizeOwnerValueSet(values = []) {
return new Set(
normalizeStringArray(values).map((value) => normalizeKey(value)),
);
}
function normalizeOwnerType(layer, ownerType) {
if (layer !== MEMORY_SCOPE_LAYER.POV) {
return MEMORY_SCOPE_OWNER_TYPE.NONE;
@@ -224,11 +230,14 @@ export function classifyNodeScopeBucket(
node,
{
activeCharacterPovOwner = "",
activeCharacterPovOwners = [],
activeUserPovOwner = "",
activeUserPovOwners = [],
activeRegion = "",
adjacentRegions = [],
enablePovMemory = true,
enableRegionScopedObjective = true,
allowImplicitCharacterPovFallback = true,
} = {},
) {
const scope = normalizeMemoryScope(node?.scope);
@@ -236,6 +245,18 @@ export function classifyNodeScopeBucket(
const normalizedAdjacentRegions = new Set(
normalizeStringArray(adjacentRegions).map((value) => normalizeKey(value)),
);
const normalizedActiveCharacterOwners = normalizeOwnerValueSet([
...normalizeStringArray(activeCharacterPovOwners),
activeCharacterPovOwner,
]);
const normalizedActiveUserOwners = normalizeOwnerValueSet([
...normalizeStringArray(activeUserPovOwners),
activeUserPovOwner,
]);
const scopeOwnerValues = normalizeOwnerValueSet([
scope.ownerId,
scope.ownerName,
]);
if (scope.layer === MEMORY_SCOPE_LAYER.POV) {
if (!enablePovMemory) {
@@ -243,24 +264,29 @@ export function classifyNodeScopeBucket(
}
if (
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.CHARACTER &&
matchesScopeOwner(scope, MEMORY_SCOPE_OWNER_TYPE.CHARACTER, activeCharacterPovOwner)
scopeOwnerValues.size > 0 &&
[...scopeOwnerValues].some((value) =>
normalizedActiveCharacterOwners.has(value),
)
) {
return MEMORY_SCOPE_BUCKETS.CHARACTER_POV;
}
if (
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.USER &&
matchesScopeOwner(scope, MEMORY_SCOPE_OWNER_TYPE.USER, activeUserPovOwner)
scopeOwnerValues.size > 0 &&
[...scopeOwnerValues].some((value) => normalizedActiveUserOwners.has(value))
) {
return MEMORY_SCOPE_BUCKETS.USER_POV;
}
if (
!normalizeString(activeCharacterPovOwner) &&
allowImplicitCharacterPovFallback &&
normalizedActiveCharacterOwners.size === 0 &&
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.CHARACTER
) {
return MEMORY_SCOPE_BUCKETS.CHARACTER_POV;
}
if (
!normalizeString(activeUserPovOwner) &&
normalizedActiveUserOwners.size === 0 &&
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.USER
) {
return MEMORY_SCOPE_BUCKETS.USER_POV;