mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix manual maintenance action execution feedback
This commit is contained in:
2
index.js
2
index.js
@@ -9347,6 +9347,7 @@ async function onManualCompress() {
|
|||||||
getEmbeddingConfig,
|
getEmbeddingConfig,
|
||||||
getSchema,
|
getSchema,
|
||||||
getSettings,
|
getSettings,
|
||||||
|
inspectCompressionCandidates: inspectAutoCompressionCandidates,
|
||||||
recordMaintenanceAction,
|
recordMaintenanceAction,
|
||||||
recordGraphMutation,
|
recordGraphMutation,
|
||||||
toastr,
|
toastr,
|
||||||
@@ -9531,6 +9532,7 @@ async function onManualEvolve() {
|
|||||||
recordMaintenanceAction,
|
recordMaintenanceAction,
|
||||||
recordGraphMutation,
|
recordGraphMutation,
|
||||||
toastr,
|
toastr,
|
||||||
|
validateVectorConfig,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ import {
|
|||||||
setBatchStageOutcome,
|
setBatchStageOutcome,
|
||||||
shouldRunRecallForTransaction,
|
shouldRunRecallForTransaction,
|
||||||
} from "../ui-status.js";
|
} from "../ui-status.js";
|
||||||
|
import {
|
||||||
|
onManualCompressController,
|
||||||
|
onManualEvolveController,
|
||||||
|
onManualSleepController,
|
||||||
|
} from "../ui-actions-controller.js";
|
||||||
|
|
||||||
const waitForTick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
const waitForTick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
const extensionsShimSource = [
|
const extensionsShimSource = [
|
||||||
@@ -5276,6 +5281,255 @@ async function testLlmOutputRegexCleansResponseBeforeJsonParse() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testManualCompressSkipsWithoutCandidatesAndDoesNotPretendItRan() {
|
||||||
|
const calls = {
|
||||||
|
compressAll: 0,
|
||||||
|
recordGraphMutation: 0,
|
||||||
|
recordMaintenanceAction: 0,
|
||||||
|
};
|
||||||
|
const toastMessages = [];
|
||||||
|
const graph = { nodes: [], historyState: {} };
|
||||||
|
|
||||||
|
const result = await onManualCompressController({
|
||||||
|
getCurrentGraph: () => graph,
|
||||||
|
ensureGraphMutationReady: () => true,
|
||||||
|
getSchema: () => [],
|
||||||
|
inspectCompressionCandidates: () => ({
|
||||||
|
hasCandidates: false,
|
||||||
|
reason: "当前没有可压缩候选组,本次未发起 LLM 压缩",
|
||||||
|
}),
|
||||||
|
cloneGraphSnapshot: (value) => JSON.parse(JSON.stringify(value ?? null)),
|
||||||
|
compressAll: async () => {
|
||||||
|
calls.compressAll += 1;
|
||||||
|
return { created: 1, archived: 1 };
|
||||||
|
},
|
||||||
|
getEmbeddingConfig: () => ({}),
|
||||||
|
getSettings: () => ({}),
|
||||||
|
recordMaintenanceAction() {
|
||||||
|
calls.recordMaintenanceAction += 1;
|
||||||
|
},
|
||||||
|
recordGraphMutation: async () => {
|
||||||
|
calls.recordGraphMutation += 1;
|
||||||
|
},
|
||||||
|
toastr: {
|
||||||
|
info(message) {
|
||||||
|
toastMessages.push(["info", message]);
|
||||||
|
},
|
||||||
|
success(message) {
|
||||||
|
toastMessages.push(["success", message]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(calls.compressAll, 0);
|
||||||
|
assert.equal(calls.recordMaintenanceAction, 0);
|
||||||
|
assert.equal(calls.recordGraphMutation, 0);
|
||||||
|
assert.equal(result?.handledToast, true);
|
||||||
|
assert.equal(result?.requestDispatched, false);
|
||||||
|
assert.match(String(toastMessages[0]?.[1] || ""), /未发起 LLM 压缩/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testManualCompressUsesForcedCompressionAndPersistsRealMutation() {
|
||||||
|
const calls = {
|
||||||
|
forceFlag: null,
|
||||||
|
recordGraphMutation: 0,
|
||||||
|
recordMaintenanceAction: 0,
|
||||||
|
};
|
||||||
|
const graph = { nodes: [], historyState: {} };
|
||||||
|
|
||||||
|
const result = await onManualCompressController({
|
||||||
|
getCurrentGraph: () => graph,
|
||||||
|
ensureGraphMutationReady: () => true,
|
||||||
|
getSchema: () => [{ id: "event", compression: { mode: "hierarchical" } }],
|
||||||
|
inspectCompressionCandidates: () => ({
|
||||||
|
hasCandidates: true,
|
||||||
|
reason: "",
|
||||||
|
}),
|
||||||
|
cloneGraphSnapshot: (value) => JSON.parse(JSON.stringify(value ?? null)),
|
||||||
|
compressAll: async (_graph, _schema, _embeddingConfig, force) => {
|
||||||
|
calls.forceFlag = force;
|
||||||
|
return { created: 1, archived: 2 };
|
||||||
|
},
|
||||||
|
getEmbeddingConfig: () => ({}),
|
||||||
|
getSettings: () => ({}),
|
||||||
|
recordMaintenanceAction() {
|
||||||
|
calls.recordMaintenanceAction += 1;
|
||||||
|
},
|
||||||
|
recordGraphMutation: async () => {
|
||||||
|
calls.recordGraphMutation += 1;
|
||||||
|
},
|
||||||
|
buildMaintenanceSummary: () => "手动压缩",
|
||||||
|
toastr: {
|
||||||
|
info() {},
|
||||||
|
success() {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(calls.forceFlag, true);
|
||||||
|
assert.equal(calls.recordMaintenanceAction, 1);
|
||||||
|
assert.equal(calls.recordGraphMutation, 1);
|
||||||
|
assert.equal(result?.handledToast, true);
|
||||||
|
assert.equal(result?.requestDispatched, true);
|
||||||
|
assert.equal(result?.mutated, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testManualEvolveFallsBackToLatestExtractionBatchAfterRefresh() {
|
||||||
|
const graph = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: "evt-1",
|
||||||
|
type: "event",
|
||||||
|
archived: false,
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
historyState: {
|
||||||
|
extractionCount: 3,
|
||||||
|
},
|
||||||
|
batchJournal: [
|
||||||
|
{
|
||||||
|
stateBefore: {
|
||||||
|
extractionCount: 3,
|
||||||
|
},
|
||||||
|
createdNodeIds: ["compression-1"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stateBefore: {
|
||||||
|
extractionCount: 2,
|
||||||
|
},
|
||||||
|
createdNodeIds: ["evt-1"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let receivedCandidateIds = null;
|
||||||
|
let recordGraphMutationCalls = 0;
|
||||||
|
const toastMessages = [];
|
||||||
|
|
||||||
|
const result = await onManualEvolveController({
|
||||||
|
getCurrentGraph: () => graph,
|
||||||
|
ensureGraphMutationReady: () => true,
|
||||||
|
getEmbeddingConfig: () => ({ mode: "direct" }),
|
||||||
|
validateVectorConfig: () => ({ valid: true }),
|
||||||
|
getLastExtractedItems: () => [],
|
||||||
|
cloneGraphSnapshot: (value) => JSON.parse(JSON.stringify(value ?? null)),
|
||||||
|
getSettings: () => ({
|
||||||
|
consolidationNeighborCount: 5,
|
||||||
|
consolidationThreshold: 0.85,
|
||||||
|
}),
|
||||||
|
consolidateMemories: async ({ newNodeIds }) => {
|
||||||
|
receivedCandidateIds = [...newNodeIds];
|
||||||
|
return {
|
||||||
|
merged: 0,
|
||||||
|
skipped: 0,
|
||||||
|
kept: 1,
|
||||||
|
evolved: 0,
|
||||||
|
connections: 0,
|
||||||
|
updates: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
recordMaintenanceAction() {
|
||||||
|
throw new Error("keep-only 结果不应写入维护账本");
|
||||||
|
},
|
||||||
|
recordGraphMutation: async () => {
|
||||||
|
recordGraphMutationCalls += 1;
|
||||||
|
},
|
||||||
|
toastr: {
|
||||||
|
info(message) {
|
||||||
|
toastMessages.push(["info", message]);
|
||||||
|
},
|
||||||
|
success(message) {
|
||||||
|
toastMessages.push(["success", message]);
|
||||||
|
},
|
||||||
|
warning(message) {
|
||||||
|
toastMessages.push(["warning", message]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(receivedCandidateIds, ["evt-1"]);
|
||||||
|
assert.equal(recordGraphMutationCalls, 0);
|
||||||
|
assert.equal(result?.handledToast, true);
|
||||||
|
assert.equal(result?.requestDispatched, true);
|
||||||
|
assert.equal(result?.mutated, false);
|
||||||
|
assert.match(String(toastMessages[0]?.[1] || ""), /最近一批提取落盘/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testManualEvolveWarnsOnInvalidVectorConfigInsteadOfPretendingComplete() {
|
||||||
|
let consolidateCalls = 0;
|
||||||
|
const toastMessages = [];
|
||||||
|
|
||||||
|
const result = await onManualEvolveController({
|
||||||
|
getCurrentGraph: () => ({
|
||||||
|
nodes: [{ id: "evt-2", type: "event", archived: false, level: 0 }],
|
||||||
|
historyState: { extractionCount: 1 },
|
||||||
|
batchJournal: [],
|
||||||
|
}),
|
||||||
|
ensureGraphMutationReady: () => true,
|
||||||
|
getEmbeddingConfig: () => ({ mode: "direct" }),
|
||||||
|
validateVectorConfig: () => ({
|
||||||
|
valid: false,
|
||||||
|
error: "Embedding 配置无效",
|
||||||
|
}),
|
||||||
|
getLastExtractedItems: () => [{ id: "evt-2" }],
|
||||||
|
consolidateMemories: async () => {
|
||||||
|
consolidateCalls += 1;
|
||||||
|
return {
|
||||||
|
merged: 1,
|
||||||
|
skipped: 0,
|
||||||
|
kept: 0,
|
||||||
|
evolved: 0,
|
||||||
|
connections: 0,
|
||||||
|
updates: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toastr: {
|
||||||
|
warning(message) {
|
||||||
|
toastMessages.push(["warning", message]);
|
||||||
|
},
|
||||||
|
info(message) {
|
||||||
|
toastMessages.push(["info", message]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(consolidateCalls, 0);
|
||||||
|
assert.equal(result?.handledToast, true);
|
||||||
|
assert.equal(result?.requestDispatched, false);
|
||||||
|
assert.match(String(toastMessages[0]?.[1] || ""), /配置无效/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testManualSleepExplainsThatItIsLocalOnlyWhenNothingChanges() {
|
||||||
|
let recordGraphMutationCalls = 0;
|
||||||
|
const toastMessages = [];
|
||||||
|
|
||||||
|
const result = await onManualSleepController({
|
||||||
|
getCurrentGraph: () => ({ nodes: [] }),
|
||||||
|
ensureGraphMutationReady: () => true,
|
||||||
|
cloneGraphSnapshot: (value) => JSON.parse(JSON.stringify(value ?? null)),
|
||||||
|
sleepCycle: () => ({ forgotten: 0 }),
|
||||||
|
getSettings: () => ({ forgetThreshold: 0.5 }),
|
||||||
|
recordMaintenanceAction() {
|
||||||
|
throw new Error("无归档时不应写入维护账本");
|
||||||
|
},
|
||||||
|
recordGraphMutation: async () => {
|
||||||
|
recordGraphMutationCalls += 1;
|
||||||
|
},
|
||||||
|
toastr: {
|
||||||
|
info(message) {
|
||||||
|
toastMessages.push(["info", message]);
|
||||||
|
},
|
||||||
|
success(message) {
|
||||||
|
toastMessages.push(["success", message]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(recordGraphMutationCalls, 0);
|
||||||
|
assert.equal(result?.handledToast, true);
|
||||||
|
assert.equal(result?.requestDispatched, false);
|
||||||
|
assert.match(String(toastMessages[0]?.[1] || ""), /不会发送 LLM 请求/);
|
||||||
|
}
|
||||||
|
|
||||||
await testCompressorMigratesEdgesToCompressedNode();
|
await testCompressorMigratesEdgesToCompressedNode();
|
||||||
await testVectorIndexKeepsDirtyOnDirectPartialEmbeddingFailure();
|
await testVectorIndexKeepsDirtyOnDirectPartialEmbeddingFailure();
|
||||||
await testExtractorFailsOnUnknownOperation();
|
await testExtractorFailsOnUnknownOperation();
|
||||||
@@ -5358,5 +5612,10 @@ await testRerollPreservesPrefixHashesWhenReextractDoesNotAdvance();
|
|||||||
await testLlmDebugSnapshotRedactsSecretsBeforeStorage();
|
await testLlmDebugSnapshotRedactsSecretsBeforeStorage();
|
||||||
await testEmbeddingUsesConfigTimeoutInsteadOfDefault();
|
await testEmbeddingUsesConfigTimeoutInsteadOfDefault();
|
||||||
await testLlmOutputRegexCleansResponseBeforeJsonParse();
|
await testLlmOutputRegexCleansResponseBeforeJsonParse();
|
||||||
|
await testManualCompressSkipsWithoutCandidatesAndDoesNotPretendItRan();
|
||||||
|
await testManualCompressUsesForcedCompressionAndPersistsRealMutation();
|
||||||
|
await testManualEvolveFallsBackToLatestExtractionBatchAfterRefresh();
|
||||||
|
await testManualEvolveWarnsOnInvalidVectorConfigInsteadOfPretendingComplete();
|
||||||
|
await testManualSleepExplainsThatItIsLocalOnlyWhenNothingChanges();
|
||||||
|
|
||||||
console.log("p0-regressions tests passed");
|
console.log("p0-regressions tests passed");
|
||||||
|
|||||||
@@ -18,6 +18,107 @@ function getTimerApi(runtime = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasCompressionMutation(result = {}) {
|
||||||
|
return (
|
||||||
|
Math.max(0, Number(result?.created) || 0) > 0 ||
|
||||||
|
Math.max(0, Number(result?.archived) || 0) > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSleepMutation(result = {}) {
|
||||||
|
return Math.max(0, Number(result?.forgotten) || 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasConsolidationMutation(result = {}) {
|
||||||
|
return (
|
||||||
|
Math.max(0, Number(result?.merged) || 0) > 0 ||
|
||||||
|
Math.max(0, Number(result?.skipped) || 0) > 0 ||
|
||||||
|
Math.max(0, Number(result?.evolved) || 0) > 0 ||
|
||||||
|
Math.max(0, Number(result?.connections) || 0) > 0 ||
|
||||||
|
Math.max(0, Number(result?.updates) || 0) > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findGraphNode(graph, nodeId) {
|
||||||
|
if (!graph || !Array.isArray(graph.nodes)) return null;
|
||||||
|
return graph.nodes.find((node) => node?.id === nodeId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isManualEvolutionCandidateNode(node) {
|
||||||
|
if (!node || node.archived) return false;
|
||||||
|
if (Number(node.level || 0) > 0) return false;
|
||||||
|
return !["synopsis", "reflection"].includes(String(node.type || ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeManualEvolutionCandidateIds(graph, nodeIds = []) {
|
||||||
|
const unique = new Set();
|
||||||
|
for (const rawId of Array.isArray(nodeIds) ? nodeIds : []) {
|
||||||
|
const nodeId = String(rawId || "").trim();
|
||||||
|
if (!nodeId || unique.has(nodeId)) continue;
|
||||||
|
const node = findGraphNode(graph, nodeId);
|
||||||
|
if (!isManualEvolutionCandidateNode(node)) continue;
|
||||||
|
unique.add(nodeId);
|
||||||
|
}
|
||||||
|
return [...unique];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveManualEvolutionCandidates(runtime, graph) {
|
||||||
|
const liveRecentIds = normalizeManualEvolutionCandidateIds(
|
||||||
|
graph,
|
||||||
|
runtime.getLastExtractedItems?.()
|
||||||
|
?.map((item) => item?.id)
|
||||||
|
.filter(Boolean) || [],
|
||||||
|
);
|
||||||
|
if (liveRecentIds.length > 0) {
|
||||||
|
return {
|
||||||
|
ids: liveRecentIds,
|
||||||
|
source: "recent-extract",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentExtractionCount = Math.max(
|
||||||
|
0,
|
||||||
|
Number(graph?.historyState?.extractionCount) || 0,
|
||||||
|
);
|
||||||
|
const batchJournal = Array.isArray(graph?.batchJournal) ? graph.batchJournal : [];
|
||||||
|
for (let index = batchJournal.length - 1; index >= 0; index -= 1) {
|
||||||
|
const entry = batchJournal[index];
|
||||||
|
const beforeExtractionCount = Math.max(
|
||||||
|
0,
|
||||||
|
Number(entry?.stateBefore?.extractionCount) || 0,
|
||||||
|
);
|
||||||
|
if (beforeExtractionCount >= currentExtractionCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const fallbackIds = normalizeManualEvolutionCandidateIds(
|
||||||
|
graph,
|
||||||
|
entry?.createdNodeIds || [],
|
||||||
|
);
|
||||||
|
if (fallbackIds.length > 0) {
|
||||||
|
return {
|
||||||
|
ids: fallbackIds,
|
||||||
|
source: "latest-extraction-batch",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ids: [],
|
||||||
|
source: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeManualEvolutionSource(source, count) {
|
||||||
|
switch (String(source || "")) {
|
||||||
|
case "recent-extract":
|
||||||
|
return `使用最近提取的 ${count} 个节点`;
|
||||||
|
case "latest-extraction-batch":
|
||||||
|
return `使用最近一批提取落盘的 ${count} 个节点`;
|
||||||
|
default:
|
||||||
|
return `候选节点 ${count} 个`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function onViewGraphController(runtime) {
|
export async function onViewGraphController(runtime) {
|
||||||
const graph = runtime.getCurrentGraph();
|
const graph = runtime.getCurrentGraph();
|
||||||
if (!graph) {
|
if (!graph) {
|
||||||
@@ -111,28 +212,55 @@ export async function onManualCompressController(runtime) {
|
|||||||
if (!graph) return;
|
if (!graph) return;
|
||||||
if (!runtime.ensureGraphMutationReady("手动压缩")) return;
|
if (!runtime.ensureGraphMutationReady("手动压缩")) return;
|
||||||
|
|
||||||
|
const schema = runtime.getSchema();
|
||||||
|
const inspection = runtime.inspectCompressionCandidates?.(graph, schema, true);
|
||||||
|
if (inspection && !inspection.hasCandidates) {
|
||||||
|
runtime.toastr.info(
|
||||||
|
String(inspection.reason || "当前没有可压缩候选组,本次未发起 LLM 压缩"),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: false,
|
||||||
|
mutated: false,
|
||||||
|
reason: String(inspection.reason || ""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
||||||
const result = await runtime.compressAll(
|
const result = await runtime.compressAll(
|
||||||
graph,
|
graph,
|
||||||
runtime.getSchema(),
|
schema,
|
||||||
runtime.getEmbeddingConfig(),
|
runtime.getEmbeddingConfig(),
|
||||||
false,
|
true,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
runtime.getSettings(),
|
runtime.getSettings(),
|
||||||
);
|
);
|
||||||
runtime.recordMaintenanceAction?.({
|
const mutated = hasCompressionMutation(result);
|
||||||
action: "compress",
|
if (mutated) {
|
||||||
beforeSnapshot,
|
runtime.recordMaintenanceAction?.({
|
||||||
mode: "manual",
|
action: "compress",
|
||||||
summary: runtime.buildMaintenanceSummary?.("compress", result, "manual"),
|
beforeSnapshot,
|
||||||
});
|
mode: "manual",
|
||||||
await runtime.recordGraphMutation({
|
summary: runtime.buildMaintenanceSummary?.("compress", result, "manual"),
|
||||||
beforeSnapshot,
|
});
|
||||||
artifactTags: ["compression"],
|
await runtime.recordGraphMutation({
|
||||||
});
|
beforeSnapshot,
|
||||||
|
artifactTags: ["compression"],
|
||||||
|
});
|
||||||
|
runtime.toastr.success(
|
||||||
|
`手动压缩完成:新建 ${result.created},归档 ${result.archived}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
runtime.toastr.info("已尝试手动压缩,但本轮没有产生可持久化变化");
|
||||||
|
}
|
||||||
|
|
||||||
runtime.toastr.info(`压缩完成: 新建 ${result.created}, 归档 ${result.archived}`);
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: true,
|
||||||
|
mutated,
|
||||||
|
result,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onExportGraphController(runtime) {
|
export async function onExportGraphController(runtime) {
|
||||||
@@ -413,17 +541,30 @@ export async function onManualSleepController(runtime) {
|
|||||||
|
|
||||||
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
||||||
const result = runtime.sleepCycle(graph, runtime.getSettings());
|
const result = runtime.sleepCycle(graph, runtime.getSettings());
|
||||||
runtime.recordMaintenanceAction?.({
|
const mutated = hasSleepMutation(result);
|
||||||
action: "sleep",
|
if (mutated) {
|
||||||
beforeSnapshot,
|
runtime.recordMaintenanceAction?.({
|
||||||
mode: "manual",
|
action: "sleep",
|
||||||
summary: runtime.buildMaintenanceSummary?.("sleep", result, "manual"),
|
beforeSnapshot,
|
||||||
});
|
mode: "manual",
|
||||||
await runtime.recordGraphMutation({
|
summary: runtime.buildMaintenanceSummary?.("sleep", result, "manual"),
|
||||||
beforeSnapshot,
|
});
|
||||||
artifactTags: ["sleep"],
|
await runtime.recordGraphMutation({
|
||||||
});
|
beforeSnapshot,
|
||||||
runtime.toastr.info(`执行完成:归档 ${result.forgotten} 个节点`);
|
artifactTags: ["sleep"],
|
||||||
|
});
|
||||||
|
runtime.toastr.success(`执行遗忘完成:归档 ${result.forgotten} 个节点`);
|
||||||
|
} else {
|
||||||
|
runtime.toastr.info(
|
||||||
|
"当前没有符合遗忘条件的节点。本操作只做本地图清理,不会发送 LLM 请求。",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: false,
|
||||||
|
mutated,
|
||||||
|
result,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onManualSynopsisController(runtime) {
|
export async function onManualSynopsisController(runtime) {
|
||||||
@@ -451,12 +592,28 @@ export async function onManualEvolveController(runtime) {
|
|||||||
if (!graph) return;
|
if (!graph) return;
|
||||||
if (!runtime.ensureGraphMutationReady("强制进化")) return;
|
if (!runtime.ensureGraphMutationReady("强制进化")) return;
|
||||||
|
|
||||||
const candidateIds = runtime.getLastExtractedItems()
|
const embeddingConfig = runtime.getEmbeddingConfig();
|
||||||
.map((item) => item.id)
|
const vectorValidation = runtime.validateVectorConfig?.(embeddingConfig);
|
||||||
.filter(Boolean);
|
if (vectorValidation && !vectorValidation.valid) {
|
||||||
|
runtime.toastr.warning(vectorValidation.error);
|
||||||
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: false,
|
||||||
|
mutated: false,
|
||||||
|
reason: vectorValidation.error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateResolution = resolveManualEvolutionCandidates(runtime, graph);
|
||||||
|
const candidateIds = candidateResolution.ids;
|
||||||
if (candidateIds.length === 0) {
|
if (candidateIds.length === 0) {
|
||||||
runtime.toastr.info("暂无最近提取节点可用于进化");
|
runtime.toastr.info("当前没有可用于进化的最近提取节点,本次未发起整合请求");
|
||||||
return;
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: false,
|
||||||
|
mutated: false,
|
||||||
|
reason: "no-candidates",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
const beforeSnapshot = runtime.cloneGraphSnapshot(graph);
|
||||||
@@ -464,7 +621,7 @@ export async function onManualEvolveController(runtime) {
|
|||||||
const result = await runtime.consolidateMemories({
|
const result = await runtime.consolidateMemories({
|
||||||
graph,
|
graph,
|
||||||
newNodeIds: candidateIds,
|
newNodeIds: candidateIds,
|
||||||
embeddingConfig: runtime.getEmbeddingConfig(),
|
embeddingConfig,
|
||||||
customPrompt: undefined,
|
customPrompt: undefined,
|
||||||
settings,
|
settings,
|
||||||
options: {
|
options: {
|
||||||
@@ -472,19 +629,38 @@ export async function onManualEvolveController(runtime) {
|
|||||||
conflictThreshold: settings.consolidationThreshold,
|
conflictThreshold: settings.consolidationThreshold,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
runtime.recordMaintenanceAction?.({
|
const mutated = hasConsolidationMutation(result);
|
||||||
action: "consolidate",
|
const sourceLabel = describeManualEvolutionSource(
|
||||||
beforeSnapshot,
|
candidateResolution.source,
|
||||||
mode: "manual",
|
candidateIds.length,
|
||||||
summary: runtime.buildMaintenanceSummary?.("consolidate", result, "manual"),
|
|
||||||
});
|
|
||||||
await runtime.recordGraphMutation({
|
|
||||||
beforeSnapshot,
|
|
||||||
artifactTags: ["consolidation"],
|
|
||||||
});
|
|
||||||
runtime.toastr.success(
|
|
||||||
`整合完成:合并 ${result.merged},跳过 ${result.skipped},保留 ${result.kept},进化 ${result.evolved},新链接 ${result.connections},回溯更新 ${result.updates}`,
|
|
||||||
);
|
);
|
||||||
|
if (mutated) {
|
||||||
|
runtime.recordMaintenanceAction?.({
|
||||||
|
action: "consolidate",
|
||||||
|
beforeSnapshot,
|
||||||
|
mode: "manual",
|
||||||
|
summary: runtime.buildMaintenanceSummary?.("consolidate", result, "manual"),
|
||||||
|
});
|
||||||
|
await runtime.recordGraphMutation({
|
||||||
|
beforeSnapshot,
|
||||||
|
artifactTags: ["consolidation"],
|
||||||
|
});
|
||||||
|
runtime.toastr.success(
|
||||||
|
`强制进化完成:合并 ${result.merged},跳过 ${result.skipped},保留 ${result.kept},进化 ${result.evolved},新链接 ${result.connections},回溯更新 ${result.updates}。${sourceLabel}。`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
runtime.toastr.info(
|
||||||
|
`已完成整合判定,但本轮没有产生图谱变更。${sourceLabel}。`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handledToast: true,
|
||||||
|
requestDispatched: true,
|
||||||
|
mutated,
|
||||||
|
result,
|
||||||
|
candidateSource: candidateResolution.source,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onUndoLastMaintenanceController(runtime) {
|
export async function onUndoLastMaintenanceController(runtime) {
|
||||||
|
|||||||
Reference in New Issue
Block a user