mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
feat(cognition): finish multi-character knowledge and monitor workflow
This commit is contained in:
@@ -46,6 +46,10 @@ assert.equal(defaultSettings.recallObjectiveAdjacentRegionWeight, 0.9);
|
||||
assert.equal(defaultSettings.recallObjectiveGlobalWeight, 0.75);
|
||||
assert.equal(defaultSettings.injectUserPovMemory, true);
|
||||
assert.equal(defaultSettings.injectObjectiveGlobalMemory, true);
|
||||
assert.equal(defaultSettings.enableCognitiveMemory, true);
|
||||
assert.equal(defaultSettings.enableSpatialAdjacency, true);
|
||||
assert.equal(defaultSettings.enableAiMonitor, false);
|
||||
assert.equal(defaultSettings.injectLowConfidenceObjectiveMemory, false);
|
||||
assert.equal(defaultSettings.injectDepth, 9999);
|
||||
assert.equal(defaultSettings.enabled, true);
|
||||
assert.equal(defaultSettings.debugLoggingEnabled, false);
|
||||
|
||||
123
tests/knowledge-state.mjs
Normal file
123
tests/knowledge-state.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createEmptyGraph, createNode, addNode } from "../graph/graph.js";
|
||||
import {
|
||||
applyCognitionUpdates,
|
||||
applyManualKnowledgeOverride,
|
||||
clearManualKnowledgeOverride,
|
||||
applyRegionUpdates,
|
||||
computeKnowledgeGateForNode,
|
||||
listKnowledgeOwners,
|
||||
resolveActiveRegionContext,
|
||||
resolveAdjacentRegions,
|
||||
resolveKnowledgeOwner,
|
||||
setManualActiveRegion,
|
||||
} from "../graph/knowledge-state.js";
|
||||
|
||||
const graph = createEmptyGraph();
|
||||
const erinA = createNode({
|
||||
type: "character",
|
||||
fields: { name: "艾琳", state: "守塔人" },
|
||||
seq: 1,
|
||||
});
|
||||
const erinB = createNode({
|
||||
type: "character",
|
||||
fields: { name: "艾琳", state: "伪装者" },
|
||||
seq: 2,
|
||||
});
|
||||
const lucia = createNode({
|
||||
type: "character",
|
||||
fields: { name: "露西亚", state: "旁观者" },
|
||||
seq: 2,
|
||||
});
|
||||
const bellEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "钟楼异响", summary: "钟楼深夜传出异响" },
|
||||
seq: 3,
|
||||
scope: { layer: "objective", regionPrimary: "钟楼" },
|
||||
});
|
||||
addNode(graph, erinA);
|
||||
addNode(graph, erinB);
|
||||
addNode(graph, lucia);
|
||||
addNode(graph, bellEvent);
|
||||
|
||||
const ownerA = resolveKnowledgeOwner(graph, {
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
nodeId: erinA.id,
|
||||
});
|
||||
const ownerB = resolveKnowledgeOwner(graph, {
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
nodeId: erinB.id,
|
||||
});
|
||||
assert.notEqual(ownerA.ownerKey, ownerB.ownerKey);
|
||||
|
||||
applyCognitionUpdates(
|
||||
graph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
ownerNodeId: erinA.id,
|
||||
knownRefs: [bellEvent.id],
|
||||
visibility: [{ ref: bellEvent.id, score: 1 }],
|
||||
},
|
||||
],
|
||||
{
|
||||
changedNodeIds: [bellEvent.id],
|
||||
scopeRuntime: {
|
||||
activeCharacterOwner: "艾琳",
|
||||
activeUserOwner: "玩家",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const gateVisible = computeKnowledgeGateForNode(graph, bellEvent, ownerA.ownerKey, {
|
||||
scopeBucket: "objectiveCurrentRegion",
|
||||
});
|
||||
assert.equal(gateVisible.visible, true);
|
||||
assert.equal(gateVisible.anchored, true);
|
||||
|
||||
applyManualKnowledgeOverride(graph, {
|
||||
ownerKey: ownerA.ownerKey,
|
||||
nodeId: bellEvent.id,
|
||||
mode: "mistaken",
|
||||
});
|
||||
const gateSuppressed = computeKnowledgeGateForNode(graph, bellEvent, ownerA.ownerKey, {
|
||||
scopeBucket: "objectiveCurrentRegion",
|
||||
});
|
||||
assert.equal(gateSuppressed.visible, false);
|
||||
assert.equal(gateSuppressed.suppressedReason, "mistaken-objective");
|
||||
|
||||
const clearedOverride = clearManualKnowledgeOverride(graph, {
|
||||
ownerKey: ownerA.ownerKey,
|
||||
nodeId: bellEvent.id,
|
||||
});
|
||||
assert.equal(clearedOverride.ok, true);
|
||||
const gateRestored = computeKnowledgeGateForNode(graph, bellEvent, ownerA.ownerKey, {
|
||||
scopeBucket: "objectiveCurrentRegion",
|
||||
});
|
||||
assert.equal(gateRestored.visible, true);
|
||||
assert.notEqual(gateRestored.suppressedReason, "mistaken-objective");
|
||||
|
||||
applyRegionUpdates(graph, {
|
||||
activeRegionHint: "钟楼",
|
||||
adjacency: [{ region: "钟楼", adjacent: ["旧城区", "内廷"] }],
|
||||
});
|
||||
assert.equal(resolveActiveRegionContext(graph).activeRegion, "钟楼");
|
||||
assert.deepEqual(resolveAdjacentRegions(graph, "钟楼").adjacentRegions, ["旧城区", "内廷"]);
|
||||
|
||||
setManualActiveRegion(graph, "旧城区");
|
||||
assert.equal(resolveActiveRegionContext(graph).source, "manual");
|
||||
assert.equal(resolveActiveRegionContext(graph).activeRegion, "旧城区");
|
||||
|
||||
const ownerList = listKnowledgeOwners(graph);
|
||||
assert.ok(ownerList.some((entry) => entry.ownerKey === ownerA.ownerKey));
|
||||
assert.ok(
|
||||
ownerList.some(
|
||||
(entry) => entry.ownerName === "露西亚" && entry.knownCount === 0,
|
||||
),
|
||||
);
|
||||
|
||||
console.log("knowledge-state tests passed");
|
||||
@@ -79,6 +79,15 @@ assert.deepEqual(
|
||||
.map((message) => message.blockName),
|
||||
["输出格式", "行为规则"],
|
||||
);
|
||||
const extractFormatBlock = extractPayload.promptMessages.find(
|
||||
(message) => message.blockName === "输出格式",
|
||||
);
|
||||
const extractRulesBlock = extractPayload.promptMessages.find(
|
||||
(message) => message.blockName === "行为规则",
|
||||
);
|
||||
assert.match(String(extractFormatBlock?.content || ""), /cognitionUpdates/);
|
||||
assert.match(String(extractFormatBlock?.content || ""), /regionUpdates/);
|
||||
assert.match(String(extractRulesBlock?.content || ""), /涉及到的角色都尽量尝试补 cognitionUpdates/);
|
||||
assert.deepEqual(
|
||||
extractPayload.promptMessages
|
||||
.map((message) => message.sourceKey)
|
||||
|
||||
@@ -134,6 +134,47 @@ const retrieve = await loadRetrieve({
|
||||
resolveScopeBucketWeight(bucket, overrides = {}) {
|
||||
return Number(overrides?.[bucket] ?? 1) || 1;
|
||||
},
|
||||
computeKnowledgeGateForNode(_graph, _node, _ownerKey, options = {}) {
|
||||
return {
|
||||
visible: true,
|
||||
anchored: false,
|
||||
rescued: false,
|
||||
suppressed: false,
|
||||
suppressedReason: "",
|
||||
visibilityScore:
|
||||
options.scopeBucket === "objectiveCurrentRegion" ? 0.8 : 0.45,
|
||||
mode: "soft-visible",
|
||||
threshold: 0.4,
|
||||
};
|
||||
},
|
||||
resolveKnowledgeOwner(_graph, input = {}) {
|
||||
const ownerType = String(input.ownerType || "").trim();
|
||||
const ownerName = String(input.ownerName || input.ownerId || "").trim();
|
||||
return {
|
||||
ownerType,
|
||||
ownerName,
|
||||
nodeId: String(input.nodeId || "").trim(),
|
||||
aliases: ownerName ? [ownerName] : [],
|
||||
ownerKey: ownerType && ownerName ? `${ownerType}:${ownerName}` : "",
|
||||
};
|
||||
},
|
||||
resolveActiveRegionContext(graph, preferredRegion = "") {
|
||||
return {
|
||||
activeRegion:
|
||||
String(preferredRegion || graph?.historyState?.activeRegion || "").trim(),
|
||||
source: preferredRegion ? "runtime" : "history",
|
||||
};
|
||||
},
|
||||
resolveAdjacentRegions() {
|
||||
return {
|
||||
canonicalRegion: "",
|
||||
adjacentRegions: [],
|
||||
};
|
||||
},
|
||||
pushRecentRecallOwner(historyState, ownerKey = "") {
|
||||
historyState.activeRecallOwnerKey = ownerKey;
|
||||
historyState.recentRecallOwnerKeys = ownerKey ? [ownerKey] : [];
|
||||
},
|
||||
describeMemoryScope(scope = {}) {
|
||||
return `${scope.layer || "objective"}:${scope.ownerType || ""}:${scope.regionPrimary || ""}`;
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ assert.equal(latestObjective?.id, objectiveNode.id);
|
||||
assert.equal(latestPov?.id, povNode.id);
|
||||
|
||||
const legacyGraph = deserializeGraph({
|
||||
version: 5,
|
||||
version: 6,
|
||||
lastProcessedSeq: 0,
|
||||
nodes: [
|
||||
{
|
||||
@@ -79,10 +79,46 @@ const legacyGraph = deserializeGraph({
|
||||
edges: [],
|
||||
});
|
||||
assert.equal(legacyGraph.nodes[0]?.scope?.layer, "objective");
|
||||
assert.equal(legacyGraph.version, 6);
|
||||
assert.equal(legacyGraph.version, 7);
|
||||
assert.equal(legacyGraph.knowledgeState?.version, 1);
|
||||
assert.equal(legacyGraph.regionState?.version, 1);
|
||||
assert.equal(legacyGraph.historyState?.activeRegionSource, "");
|
||||
assert.deepEqual(legacyGraph.historyState?.recentRecallOwnerKeys, []);
|
||||
|
||||
const restored = deserializeGraph(serializeGraph(graph));
|
||||
assert.equal(restored.nodes.find((node) => node.id === povNode.id)?.scope?.ownerType, "character");
|
||||
assert.equal(restored.nodes.find((node) => node.id === povNode.id)?.scope?.regionPrimary, "钟楼");
|
||||
assert.equal(restored.knowledgeState?.version, 1);
|
||||
assert.equal(restored.regionState?.version, 1);
|
||||
|
||||
restored.knowledgeState.owners["character:艾琳"] = {
|
||||
ownerType: "character",
|
||||
ownerKey: "character:艾琳",
|
||||
ownerName: "艾琳",
|
||||
nodeId: "",
|
||||
aliases: ["艾琳"],
|
||||
knownNodeIds: [objectiveNode.id],
|
||||
mistakenNodeIds: [],
|
||||
visibilityScores: { [objectiveNode.id]: 1 },
|
||||
manualKnownNodeIds: [],
|
||||
manualHiddenNodeIds: [],
|
||||
updatedAt: Date.now(),
|
||||
lastSource: "test",
|
||||
};
|
||||
restored.regionState.adjacencyMap["钟楼"] = {
|
||||
adjacent: ["旧城区"],
|
||||
aliases: [],
|
||||
source: "test",
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
const roundTrip = deserializeGraph(serializeGraph(restored));
|
||||
assert.equal(
|
||||
roundTrip.knowledgeState?.owners?.["character:艾琳"]?.knownNodeIds?.[0],
|
||||
objectiveNode.id,
|
||||
);
|
||||
assert.equal(
|
||||
roundTrip.regionState?.adjacencyMap?.["钟楼"]?.adjacent?.[0],
|
||||
"旧城区",
|
||||
);
|
||||
|
||||
console.log("scoped-memory tests passed");
|
||||
|
||||
@@ -301,6 +301,11 @@ assert.equal(
|
||||
refreshedDefaultExtract.metadata.defaultTemplateFingerprint,
|
||||
currentDefaultExtract.metadata.defaultTemplateFingerprint,
|
||||
);
|
||||
assert.match(
|
||||
refreshedDefaultExtract.blocks.find((block) => block.id === "default-format")
|
||||
?.content || "",
|
||||
/cognitionUpdates/,
|
||||
);
|
||||
assert.ok(preservedCustomExtract);
|
||||
assert.equal(
|
||||
preservedCustomExtract.blocks[0].content,
|
||||
|
||||
Reference in New Issue
Block a user