openclaw-cortex-memory 0.1.0-Alpha.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +347 -290
- package/SIGNATURE.md +7 -0
- package/SKILL.md +96 -345
- package/dist/index.d.ts +69 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1130 -1330
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +397 -18
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
- package/dist/src/dedup/three_stage_deduplicator.js +13 -3
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
- package/dist/src/engine/memory_engine.d.ts +5 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +149 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +863 -203
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +20 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +87 -15
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +999 -12
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/net/http_post.d.ts +17 -0
- package/dist/src/net/http_post.d.ts.map +1 -0
- package/dist/src/net/http_post.js +56 -0
- package/dist/src/net/http_post.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +65 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +635 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +296 -26
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +20 -42
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +21 -218
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +28 -7
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +367 -130
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +115 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +1061 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +75 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1837 -312
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +2 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +19 -3
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +11 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +242 -42
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +72 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2246 -126
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/wiki/wiki_linter.d.ts +26 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +339 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +39 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +35 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +1151 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +397 -18
- package/package.json +51 -5
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +67 -13
- package/scripts/repair-memory.js +321 -0
- package/skills/cortex-memory/SKILL.md +83 -0
- package/skills/cortex-memory/references/agent-manual.md +127 -0
- package/skills/cortex-memory/references/configuration.md +109 -0
- package/skills/cortex-memory/references/publish-checklist.md +45 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +191 -0
|
@@ -36,22 +36,197 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.createArchiveStore = createArchiveStore;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const http_post_1 = require("../net/http_post");
|
|
39
40
|
const ontology_1 = require("../graph/ontology");
|
|
41
|
+
const llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
40
42
|
function normalizeBaseUrl(value) {
|
|
41
43
|
if (!value)
|
|
42
44
|
return "";
|
|
43
45
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
44
46
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (score >= 0.75) {
|
|
49
|
-
return { score, level: "high" };
|
|
47
|
+
function resolveArchiveSourceCharLimit(value) {
|
|
48
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
49
|
+
return Math.max(1000, Math.floor(value));
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return 500000;
|
|
52
|
+
}
|
|
53
|
+
function clampTailText(text, maxChars) {
|
|
54
|
+
const source = (text || "").trim();
|
|
55
|
+
if (!source)
|
|
56
|
+
return "";
|
|
57
|
+
if (!Number.isFinite(maxChars) || maxChars <= 0 || source.length <= maxChars) {
|
|
58
|
+
return source;
|
|
59
|
+
}
|
|
60
|
+
return source.slice(-Math.floor(maxChars)).trim();
|
|
61
|
+
}
|
|
62
|
+
const ARCHIVE_LOW_INFORMATION_LINE = /^(ok|okay|got it|roger|noted|sure|thanks|thank you|received|copy that|understood|好的|收到|明白|了解|谢谢|感谢|可以|行|嗯|嗯嗯|没问题)(?:\b|$)/i;
|
|
63
|
+
function denoiseArchiveSourceText(text) {
|
|
64
|
+
const raw = (text || "").trim();
|
|
65
|
+
if (!raw)
|
|
66
|
+
return "";
|
|
67
|
+
const output = [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
continue;
|
|
73
|
+
const content = trimmed.replace(/^\[[^\]]+\]\s*/, "").trim();
|
|
74
|
+
if (!content)
|
|
75
|
+
continue;
|
|
76
|
+
const hasSignal = /(https?:\/\/|www\.|[A-Za-z0-9._-]+\.[A-Za-z]{2,}|[`#/:\\]|@\w+|\b\d{2,}\b)/.test(content);
|
|
77
|
+
if (!hasSignal && ARCHIVE_LOW_INFORMATION_LINE.test(content)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const dedupKey = content.toLowerCase();
|
|
81
|
+
if (!hasSignal && seen.has(dedupKey)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
seen.add(dedupKey);
|
|
85
|
+
output.push(trimmed);
|
|
86
|
+
}
|
|
87
|
+
return output.length > 0 ? output.join("\n") : raw;
|
|
88
|
+
}
|
|
89
|
+
function normalizeOneLineText(value) {
|
|
90
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
91
|
+
}
|
|
92
|
+
const ARCHIVE_TASK_INSTRUCTION_PATTERNS = [
|
|
93
|
+
/请|需要|帮我|麻烦|任务|需求|实现|修复|排查|优化|上线|部署|整理|编写|启用|查看|检查|分析|导入|生成|提高|改进/,
|
|
94
|
+
/please|can you|need to|task|implement|fix|investigate|optimi[sz]e|deploy|enable|review/i,
|
|
95
|
+
];
|
|
96
|
+
const ARCHIVE_COMPLETION_REPORT_PATTERNS = [
|
|
97
|
+
/已完成|完成了|处理完|搞定|已修复|修复了|已实现|已上线|已部署|结果|汇报|完成情况|报告|已通过|验证通过|测试通过/,
|
|
98
|
+
/done|completed|fixed|implemented|deployed|resolved|report|summary|finished/i,
|
|
99
|
+
];
|
|
100
|
+
const ARCHIVE_USER_ACCEPTANCE_PATTERNS = [
|
|
101
|
+
/确认|认可|通过|验收|OK|可以|好的|收到|辛苦|谢谢|没问题|就这样|接受|效果可以/,
|
|
102
|
+
/approved|accepted|looks good|great|works|thank you|confirmed/i,
|
|
103
|
+
];
|
|
104
|
+
const ARCHIVE_ACTION_PATTERNS = [
|
|
105
|
+
/决定|完成|修复|发布|上线|部署|提交|交付|验证|关闭|推进|落地|实施|启用|导入|生成|优化|改进/,
|
|
106
|
+
/decide|complete|fix|release|deploy|ship|deliver|verify|close|implement|enable|migrate/i,
|
|
107
|
+
];
|
|
108
|
+
const ARCHIVE_FAILURE_PATTERNS = [
|
|
109
|
+
/失败|报错|错误|异常|阻塞|卡住|不行|超时|回滚|故障|不通过|无法/,
|
|
110
|
+
/failed|error|exception|blocked|timeout|rollback|incident/i,
|
|
111
|
+
];
|
|
112
|
+
const ARCHIVE_SUCCESS_PATTERNS = [
|
|
113
|
+
/成功|完成|修复|解决|通过|已上线|稳定|正常|恢复|可用|生效/,
|
|
114
|
+
/success|completed|fixed|resolved|passed|stable|recovered|works/i,
|
|
115
|
+
];
|
|
116
|
+
function matchesAnyPattern(text, patterns) {
|
|
117
|
+
return patterns.some(pattern => pattern.test(text));
|
|
118
|
+
}
|
|
119
|
+
function firstMatchIndex(text, patterns) {
|
|
120
|
+
let minIndex = -1;
|
|
121
|
+
for (const pattern of patterns) {
|
|
122
|
+
const idx = text.search(pattern);
|
|
123
|
+
if (idx < 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (minIndex < 0 || idx < minIndex) {
|
|
127
|
+
minIndex = idx;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return minIndex;
|
|
131
|
+
}
|
|
132
|
+
function scoreQuality(args) {
|
|
133
|
+
const summary = (args.summary || "").trim();
|
|
134
|
+
const cause = (args.cause || "").trim();
|
|
135
|
+
const process = (args.process || "").trim();
|
|
136
|
+
const result = (args.result || "").trim();
|
|
137
|
+
const outcome = (args.outcome || "").trim();
|
|
138
|
+
const sourceText = (args.sourceText || "").trim();
|
|
139
|
+
const mergedText = [summary, cause, process, result, outcome, sourceText].filter(Boolean).join("\n");
|
|
140
|
+
const hasStructuredTriplet = cause.length > 0 && process.length > 0 && result.length > 0;
|
|
141
|
+
const hasTaskInstruction = matchesAnyPattern(mergedText, ARCHIVE_TASK_INSTRUCTION_PATTERNS);
|
|
142
|
+
const hasCompletionReport = matchesAnyPattern(mergedText, ARCHIVE_COMPLETION_REPORT_PATTERNS);
|
|
143
|
+
const hasUserAcceptance = matchesAnyPattern(mergedText, ARCHIVE_USER_ACCEPTANCE_PATTERNS);
|
|
144
|
+
const hasAction = matchesAnyPattern(mergedText, ARCHIVE_ACTION_PATTERNS);
|
|
145
|
+
const hasFailure = matchesAnyPattern(mergedText, ARCHIVE_FAILURE_PATTERNS);
|
|
146
|
+
const hasSuccess = matchesAnyPattern(mergedText, ARCHIVE_SUCCESS_PATTERNS);
|
|
147
|
+
const hasOutcome = outcome.length >= 6 || hasSuccess;
|
|
148
|
+
const firstFailureIdx = hasFailure ? firstMatchIndex(mergedText, ARCHIVE_FAILURE_PATTERNS) : -1;
|
|
149
|
+
const firstSuccessIdx = hasSuccess ? firstMatchIndex(mergedText, ARCHIVE_SUCCESS_PATTERNS) : -1;
|
|
150
|
+
const failThenSuccess = hasFailure && hasSuccess && firstFailureIdx >= 0 && firstSuccessIdx > firstFailureIdx;
|
|
151
|
+
const workflowComplete = hasStructuredTriplet || (hasTaskInstruction && hasCompletionReport && hasUserAcceptance);
|
|
152
|
+
let score = 0;
|
|
153
|
+
if (summary.length >= 24)
|
|
154
|
+
score += 0.1;
|
|
155
|
+
if (summary.length >= 60)
|
|
156
|
+
score += 0.1;
|
|
157
|
+
if (summary.length >= 120)
|
|
158
|
+
score += 0.06;
|
|
159
|
+
if (summary.length >= 180)
|
|
160
|
+
score += 0.04;
|
|
161
|
+
if (hasStructuredTriplet)
|
|
162
|
+
score += 0.22;
|
|
163
|
+
if (hasAction)
|
|
164
|
+
score += 0.14;
|
|
165
|
+
if (hasOutcome)
|
|
166
|
+
score += 0.12;
|
|
167
|
+
if (hasTaskInstruction)
|
|
168
|
+
score += 0.12;
|
|
169
|
+
if (hasCompletionReport)
|
|
170
|
+
score += 0.12;
|
|
171
|
+
if (hasUserAcceptance)
|
|
172
|
+
score += 0.14;
|
|
173
|
+
if (workflowComplete)
|
|
174
|
+
score += 0.12;
|
|
175
|
+
if (failThenSuccess)
|
|
176
|
+
score += 0.1;
|
|
177
|
+
const normalizedScore = Math.max(0, Math.min(1, Number(score.toFixed(2))));
|
|
178
|
+
if (normalizedScore >= 0.75) {
|
|
179
|
+
return {
|
|
180
|
+
score: normalizedScore,
|
|
181
|
+
level: "high",
|
|
182
|
+
signals: { hasStructuredTriplet, hasTaskInstruction, hasCompletionReport, hasUserAcceptance, workflowComplete, failThenSuccess },
|
|
183
|
+
};
|
|
53
184
|
}
|
|
54
|
-
|
|
185
|
+
if (normalizedScore >= 0.4) {
|
|
186
|
+
return {
|
|
187
|
+
score: normalizedScore,
|
|
188
|
+
level: "medium",
|
|
189
|
+
signals: { hasStructuredTriplet, hasTaskInstruction, hasCompletionReport, hasUserAcceptance, workflowComplete, failThenSuccess },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
score: normalizedScore,
|
|
194
|
+
level: "low",
|
|
195
|
+
signals: { hasStructuredTriplet, hasTaskInstruction, hasCompletionReport, hasUserAcceptance, workflowComplete, failThenSuccess },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function normalizeArchiveDedupText(value) {
|
|
199
|
+
return String(value || "")
|
|
200
|
+
.toLowerCase()
|
|
201
|
+
.replace(/[^\p{L}\p{N}:./\\#@_-]+/gu, " ")
|
|
202
|
+
.replace(/\s+/g, " ")
|
|
203
|
+
.trim();
|
|
204
|
+
}
|
|
205
|
+
function buildArchiveDedupText(args) {
|
|
206
|
+
const relationText = Array.isArray(args.relations)
|
|
207
|
+
? args.relations
|
|
208
|
+
.map(relation => `${relation.source || ""}|${relation.type || ""}|${relation.target || ""}`)
|
|
209
|
+
.filter(item => item.replace(/\|/g, "").trim().length > 0)
|
|
210
|
+
.sort()
|
|
211
|
+
.join("\n")
|
|
212
|
+
: "";
|
|
213
|
+
const entityText = Array.isArray(args.entities)
|
|
214
|
+
? args.entities.map(item => String(item || "").trim()).filter(Boolean).sort().join("\n")
|
|
215
|
+
: "";
|
|
216
|
+
return [
|
|
217
|
+
`canonical:${args.canonicalId || ""}`,
|
|
218
|
+
`event_type:${args.eventType}`,
|
|
219
|
+
`summary:${args.summary}`,
|
|
220
|
+
`cause:${args.cause || ""}`,
|
|
221
|
+
`process:${args.process || ""}`,
|
|
222
|
+
`result:${args.result || args.outcome || ""}`,
|
|
223
|
+
entityText ? `entities:${entityText}` : "",
|
|
224
|
+
relationText ? `relations:${relationText}` : "",
|
|
225
|
+
]
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.map(normalizeArchiveDedupText)
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.join("\n");
|
|
55
230
|
}
|
|
56
231
|
async function requestEmbedding(args) {
|
|
57
232
|
const endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
@@ -70,24 +245,18 @@ async function requestEmbedding(args) {
|
|
|
70
245
|
: 4;
|
|
71
246
|
let lastError = null;
|
|
72
247
|
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
73
|
-
const
|
|
74
|
-
|
|
248
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
249
|
+
endpoint,
|
|
250
|
+
apiKey: args.apiKey,
|
|
251
|
+
body,
|
|
252
|
+
timeoutMs,
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
lastError = new Error(response.status > 0 ? `embedding_http_${response.status}` : (response.error || "embedding_network_error"));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
75
258
|
try {
|
|
76
|
-
const
|
|
77
|
-
method: "POST",
|
|
78
|
-
headers: {
|
|
79
|
-
"content-type": "application/json",
|
|
80
|
-
authorization: `Bearer ${args.apiKey}`,
|
|
81
|
-
},
|
|
82
|
-
body: JSON.stringify(body),
|
|
83
|
-
signal: controller.signal,
|
|
84
|
-
});
|
|
85
|
-
clearTimeout(timeoutId);
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
lastError = new Error(`embedding_http_${response.status}`);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
const json = await response.json();
|
|
259
|
+
const json = (response.json || {});
|
|
91
260
|
const embedding = json?.data?.[0]?.embedding;
|
|
92
261
|
if (Array.isArray(embedding) && embedding.length > 0) {
|
|
93
262
|
return embedding.filter(item => Number.isFinite(item));
|
|
@@ -95,7 +264,6 @@ async function requestEmbedding(args) {
|
|
|
95
264
|
lastError = new Error("embedding_empty");
|
|
96
265
|
}
|
|
97
266
|
catch (error) {
|
|
98
|
-
clearTimeout(timeoutId);
|
|
99
267
|
lastError = error;
|
|
100
268
|
}
|
|
101
269
|
if (attempt < maxRetries - 1) {
|
|
@@ -131,34 +299,6 @@ function inferGateSource(event) {
|
|
|
131
299
|
}
|
|
132
300
|
return "manual";
|
|
133
301
|
}
|
|
134
|
-
function buildArchiveVectorText(event, normalizedEventType, entities) {
|
|
135
|
-
const lines = [
|
|
136
|
-
`event_type: ${normalizedEventType}`,
|
|
137
|
-
`summary: ${(event.summary || "").trim()}`,
|
|
138
|
-
`outcome: ${(event.outcome || "").trim()}`,
|
|
139
|
-
`entities: ${entities.join(", ")}`,
|
|
140
|
-
`source_file: ${(event.source_file || "").trim()}`,
|
|
141
|
-
`actor: ${(event.actor || "").trim()}`,
|
|
142
|
-
].map(line => line.trim()).filter(Boolean);
|
|
143
|
-
if (Array.isArray(event.relations) && event.relations.length > 0) {
|
|
144
|
-
const relationLines = event.relations
|
|
145
|
-
.map(relation => {
|
|
146
|
-
if (!relation || typeof relation !== "object")
|
|
147
|
-
return "";
|
|
148
|
-
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
149
|
-
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
150
|
-
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
151
|
-
if (!source || !target)
|
|
152
|
-
return "";
|
|
153
|
-
return `${source} -[${type}]-> ${target}`;
|
|
154
|
-
})
|
|
155
|
-
.filter(Boolean);
|
|
156
|
-
if (relationLines.length > 0) {
|
|
157
|
-
lines.push(`relations: ${relationLines.join(" ; ")}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return lines.join("\n").trim();
|
|
161
|
-
}
|
|
162
302
|
function splitTextChunks(text, chunkSize, chunkOverlap) {
|
|
163
303
|
const normalizedSize = Number.isFinite(chunkSize) && chunkSize >= 200 ? Math.floor(chunkSize) : 600;
|
|
164
304
|
const normalizedOverlap = Number.isFinite(chunkOverlap) && chunkOverlap >= 0
|
|
@@ -210,6 +350,32 @@ function splitTextChunks(text, chunkSize, chunkOverlap) {
|
|
|
210
350
|
}
|
|
211
351
|
return output;
|
|
212
352
|
}
|
|
353
|
+
function pickEvidenceChunks(chunks, maxCount) {
|
|
354
|
+
if (!chunks.length || maxCount <= 0)
|
|
355
|
+
return [];
|
|
356
|
+
if (chunks.length <= maxCount)
|
|
357
|
+
return chunks;
|
|
358
|
+
const picked = new Map();
|
|
359
|
+
picked.set(chunks[0].index, chunks[0]);
|
|
360
|
+
if (maxCount >= 2) {
|
|
361
|
+
const mid = chunks[Math.floor(chunks.length / 2)];
|
|
362
|
+
picked.set(mid.index, mid);
|
|
363
|
+
}
|
|
364
|
+
if (maxCount >= 3) {
|
|
365
|
+
const last = chunks[chunks.length - 1];
|
|
366
|
+
picked.set(last.index, last);
|
|
367
|
+
}
|
|
368
|
+
if (picked.size < maxCount) {
|
|
369
|
+
for (const chunk of chunks) {
|
|
370
|
+
if (!picked.has(chunk.index)) {
|
|
371
|
+
picked.set(chunk.index, chunk);
|
|
372
|
+
}
|
|
373
|
+
if (picked.size >= maxCount)
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return [...picked.values()].sort((a, b) => a.index - b.index).slice(0, maxCount);
|
|
378
|
+
}
|
|
213
379
|
async function mapWithConcurrency(items, maxConcurrency, mapper) {
|
|
214
380
|
if (items.length === 0) {
|
|
215
381
|
return [];
|
|
@@ -232,8 +398,9 @@ async function mapWithConcurrency(items, maxConcurrency, mapper) {
|
|
|
232
398
|
}
|
|
233
399
|
function createArchiveStore(options) {
|
|
234
400
|
const archivePath = path.join(options.memoryRoot, "sessions", "archive", "sessions.jsonl");
|
|
235
|
-
const mutationLogPath = path.join(options.memoryRoot, "
|
|
401
|
+
const mutationLogPath = path.join(options.memoryRoot, "sessions", "archive", "mutation_log.jsonl");
|
|
236
402
|
const graphSchema = (0, ontology_1.loadGraphSchema)(options.projectRoot);
|
|
403
|
+
const archiveSourceTextMaxChars = resolveArchiveSourceCharLimit(options.writePolicy?.archiveSourceTextMaxChars);
|
|
237
404
|
async function storeEvents(events) {
|
|
238
405
|
const stored = [];
|
|
239
406
|
const skipped = [];
|
|
@@ -243,46 +410,112 @@ function createArchiveStore(options) {
|
|
|
243
410
|
const lines = [];
|
|
244
411
|
const mutationLines = [];
|
|
245
412
|
for (const event of events) {
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
413
|
+
const rawSummary = normalizeOneLineText(event.summary || "");
|
|
414
|
+
if (!rawSummary) {
|
|
248
415
|
skipped.push({ summary: "", reason: "empty_summary" });
|
|
249
416
|
options.logger.info("archive_skip reason=empty_summary");
|
|
250
417
|
continue;
|
|
251
418
|
}
|
|
419
|
+
const cause = normalizeOneLineText(event.cause || "");
|
|
420
|
+
const process = normalizeOneLineText(event.process || "");
|
|
421
|
+
const result = normalizeOneLineText(event.result || event.outcome || "");
|
|
422
|
+
const summary = rawSummary;
|
|
252
423
|
const confidence = typeof event.confidence === "number"
|
|
253
424
|
? Math.max(0, Math.min(1, event.confidence))
|
|
254
425
|
: undefined;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
426
|
+
const quality = scoreQuality({
|
|
427
|
+
summary,
|
|
428
|
+
cause,
|
|
429
|
+
process,
|
|
430
|
+
result,
|
|
431
|
+
outcome: event.outcome,
|
|
432
|
+
sourceText: event.source_text,
|
|
433
|
+
});
|
|
434
|
+
const gateSource = inferGateSource(event);
|
|
435
|
+
const lifecycleComplete = quality.signals.workflowComplete;
|
|
436
|
+
if (gateSource === "sync" && !quality.signals.hasStructuredTriplet) {
|
|
437
|
+
skipped.push({ summary, reason: "incomplete_cause_process_result" });
|
|
438
|
+
options.logger.info("archive_skip reason=incomplete_cause_process_result gate_source=sync");
|
|
258
439
|
continue;
|
|
259
440
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
441
|
+
const archiveMinConfidence = typeof options.writePolicy?.archiveMinConfidence === "number"
|
|
442
|
+
? Math.max(0, Math.min(1, options.writePolicy.archiveMinConfidence))
|
|
443
|
+
: 0.35;
|
|
444
|
+
if (typeof confidence === "number" && confidence < archiveMinConfidence) {
|
|
445
|
+
if (!lifecycleComplete) {
|
|
446
|
+
skipped.push({ summary, reason: "low_confidence" });
|
|
447
|
+
options.logger.info("archive_skip reason=filtered_low_quality detail=low_confidence");
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
options.logger.info(`archive_confidence_override reason=workflow_complete confidence=${confidence.toFixed(2)} threshold=${archiveMinConfidence.toFixed(2)}`);
|
|
451
|
+
}
|
|
452
|
+
const archiveMinQualityScore = typeof options.writePolicy?.archiveMinQualityScore === "number"
|
|
453
|
+
? Math.max(0, Math.min(1, options.writePolicy.archiveMinQualityScore))
|
|
454
|
+
: 0.4;
|
|
455
|
+
if (quality.score < archiveMinQualityScore) {
|
|
456
|
+
if (!lifecycleComplete) {
|
|
457
|
+
skipped.push({ summary, reason: "low_quality" });
|
|
458
|
+
options.logger.info("archive_skip reason=filtered_low_quality detail=low_quality");
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
options.logger.info(`archive_quality_override reason=workflow_complete quality=${quality.score.toFixed(2)} threshold=${archiveMinQualityScore.toFixed(2)}`);
|
|
265
462
|
}
|
|
266
463
|
const normalizedEventType = (0, ontology_1.normalizeEventType)(event.event_type || "insight", graphSchema);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
464
|
+
const id = `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
465
|
+
const sourceTextRaw = typeof event.source_text === "string" ? event.source_text : "";
|
|
466
|
+
const sourceText = clampTailText(denoiseArchiveSourceText(sourceTextRaw), archiveSourceTextMaxChars);
|
|
467
|
+
const canonicalId = event.canonical_id || (0, ontology_1.buildCanonicalId)({
|
|
468
|
+
eventType: normalizedEventType,
|
|
469
|
+
summary,
|
|
470
|
+
outcome: event.outcome,
|
|
471
|
+
});
|
|
472
|
+
const dedupText = buildArchiveDedupText({
|
|
473
|
+
eventType: normalizedEventType,
|
|
474
|
+
summary,
|
|
475
|
+
cause,
|
|
476
|
+
process,
|
|
477
|
+
result,
|
|
478
|
+
outcome: event.outcome,
|
|
479
|
+
canonicalId,
|
|
480
|
+
entities: event.entities,
|
|
481
|
+
relations: event.relations,
|
|
482
|
+
});
|
|
483
|
+
const dedup = options.deduplicator.check({
|
|
484
|
+
id,
|
|
485
|
+
summary: dedupText || `${normalizedEventType}: ${summary}`,
|
|
275
486
|
});
|
|
276
|
-
if (
|
|
277
|
-
skipped.push({ summary, reason: "
|
|
278
|
-
options.logger.info(
|
|
487
|
+
if (dedup.duplicate) {
|
|
488
|
+
skipped.push({ summary, reason: `duplicate_${dedup.stage || "unknown"}` });
|
|
489
|
+
options.logger.info(`archive_skip reason=duplicate_dedup_stage_${dedup.stage || "unknown"}`);
|
|
279
490
|
continue;
|
|
280
491
|
}
|
|
281
|
-
const
|
|
492
|
+
const record = {
|
|
493
|
+
id,
|
|
494
|
+
timestamp: new Date().toISOString(),
|
|
495
|
+
layer: "archive",
|
|
496
|
+
event_type: normalizedEventType,
|
|
497
|
+
summary,
|
|
498
|
+
cause,
|
|
499
|
+
process,
|
|
500
|
+
result,
|
|
501
|
+
source_text: sourceText || undefined,
|
|
502
|
+
outcome: event.outcome,
|
|
503
|
+
session_id: event.session_id,
|
|
504
|
+
source_file: event.source_file,
|
|
505
|
+
gate_source: gateSource,
|
|
506
|
+
embedding_status: "pending",
|
|
507
|
+
quality_score: quality.score,
|
|
508
|
+
quality_level: quality.level,
|
|
509
|
+
char_count: (sourceText || summary).length,
|
|
510
|
+
token_count: estimateTokenCount(sourceText || summary),
|
|
511
|
+
vector_chunks_total: 0,
|
|
512
|
+
vector_chunks_ok: 0,
|
|
513
|
+
confidence,
|
|
514
|
+
source_event_id: event.source_event_id,
|
|
515
|
+
actor: event.actor || "system",
|
|
516
|
+
canonical_id: canonicalId,
|
|
517
|
+
};
|
|
282
518
|
let embedding = undefined;
|
|
283
|
-
let embeddingStatus = "pending";
|
|
284
|
-
let vectorChunksTotal = 0;
|
|
285
|
-
let vectorChunksOk = 0;
|
|
286
519
|
const vectorUpsertRows = [];
|
|
287
520
|
const embeddingModel = options.embedding?.model || "";
|
|
288
521
|
const embeddingApiKey = options.embedding?.apiKey || "";
|
|
@@ -291,10 +524,38 @@ function createArchiveStore(options) {
|
|
|
291
524
|
if (embeddingModel && embeddingApiKey && embeddingBaseUrl) {
|
|
292
525
|
const chunkSize = options.vectorChunking?.chunkSize ?? 600;
|
|
293
526
|
const chunkOverlap = options.vectorChunking?.chunkOverlap ?? 100;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const
|
|
527
|
+
const evidenceMaxChunks = typeof options.vectorChunking?.evidenceMaxChunks === "number"
|
|
528
|
+
? Math.max(0, Math.min(8, Math.floor(options.vectorChunking.evidenceMaxChunks)))
|
|
529
|
+
: 2;
|
|
530
|
+
const summaryText = (record.summary || "").trim();
|
|
531
|
+
const evidenceChunks = record.source_text
|
|
532
|
+
? pickEvidenceChunks(splitTextChunks(record.source_text, chunkSize, chunkOverlap), evidenceMaxChunks)
|
|
533
|
+
: [];
|
|
534
|
+
const summaryChunk = summaryText
|
|
535
|
+
? [
|
|
536
|
+
{
|
|
537
|
+
text: summaryText,
|
|
538
|
+
source_field: "summary",
|
|
539
|
+
index: 0,
|
|
540
|
+
total: 1 + evidenceChunks.length,
|
|
541
|
+
start: 0,
|
|
542
|
+
end: summaryText.length,
|
|
543
|
+
},
|
|
544
|
+
]
|
|
545
|
+
: [];
|
|
546
|
+
const embeddingInputs = [
|
|
547
|
+
...summaryChunk,
|
|
548
|
+
...evidenceChunks.map((chunk, idx) => ({
|
|
549
|
+
text: chunk.text,
|
|
550
|
+
source_field: "evidence",
|
|
551
|
+
index: idx + summaryChunk.length,
|
|
552
|
+
total: summaryChunk.length + evidenceChunks.length,
|
|
553
|
+
start: chunk.start,
|
|
554
|
+
end: chunk.end,
|
|
555
|
+
})),
|
|
556
|
+
];
|
|
557
|
+
record.vector_chunks_total = embeddingInputs.length;
|
|
558
|
+
const chunkEmbeddings = await mapWithConcurrency(embeddingInputs, maxParallel, async (chunk) => {
|
|
298
559
|
try {
|
|
299
560
|
const chunkEmbedding = await requestEmbedding({
|
|
300
561
|
text: chunk.text,
|
|
@@ -314,14 +575,18 @@ function createArchiveStore(options) {
|
|
|
314
575
|
return null;
|
|
315
576
|
}
|
|
316
577
|
catch (error) {
|
|
317
|
-
options.logger.warn(`Archive chunk embedding failed id=${id} chunk=${chunk.index} error=${error}`);
|
|
578
|
+
options.logger.warn(`Archive chunk embedding failed id=${id} chunk=${chunk.index} field=${chunk.source_field} error=${error}`);
|
|
318
579
|
return null;
|
|
319
580
|
}
|
|
320
581
|
});
|
|
321
582
|
const validEmbeddings = chunkEmbeddings
|
|
322
583
|
.filter((item) => Boolean(item))
|
|
323
584
|
.sort((a, b) => a.chunk.index - b.chunk.index);
|
|
324
|
-
|
|
585
|
+
const primary = validEmbeddings.find(item => item.chunk.source_field === "summary");
|
|
586
|
+
if (primary) {
|
|
587
|
+
embedding = primary.embedding;
|
|
588
|
+
}
|
|
589
|
+
else if (validEmbeddings.length > 0) {
|
|
325
590
|
embedding = validEmbeddings[0].embedding;
|
|
326
591
|
}
|
|
327
592
|
for (const item of validEmbeddings) {
|
|
@@ -329,58 +594,24 @@ function createArchiveStore(options) {
|
|
|
329
594
|
id: `${id}_c${item.chunk.index}`,
|
|
330
595
|
summary: item.chunk.text,
|
|
331
596
|
embedding: item.embedding,
|
|
597
|
+
source_field: item.chunk.source_field,
|
|
332
598
|
chunk_index: item.chunk.index,
|
|
333
|
-
chunk_total:
|
|
599
|
+
chunk_total: item.chunk.total,
|
|
334
600
|
chunk_start: item.chunk.start,
|
|
335
601
|
chunk_end: item.chunk.end,
|
|
336
602
|
});
|
|
337
603
|
}
|
|
338
|
-
|
|
339
|
-
|
|
604
|
+
record.vector_chunks_ok = validEmbeddings.length;
|
|
605
|
+
record.embedding_status = record.vector_chunks_total > 0 && record.vector_chunks_ok === record.vector_chunks_total
|
|
606
|
+
? "ok"
|
|
607
|
+
: "failed";
|
|
340
608
|
}
|
|
341
|
-
|
|
342
|
-
id,
|
|
343
|
-
summary: `${normalizedEventType}: ${summary}`,
|
|
344
|
-
embedding,
|
|
345
|
-
});
|
|
346
|
-
if (dedup.duplicate) {
|
|
347
|
-
skipped.push({ summary, reason: `duplicate_${dedup.stage || "unknown"}` });
|
|
348
|
-
options.logger.info(`archive_skip reason=duplicate_dedup_stage_${dedup.stage || "unknown"}`);
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
const gateSource = inferGateSource(event);
|
|
352
|
-
const record = {
|
|
353
|
-
...event,
|
|
354
|
-
event_type: normalizedEventType,
|
|
355
|
-
entities,
|
|
356
|
-
relations: relationValidation.accepted,
|
|
357
|
-
canonical_id: event.canonical_id || (0, ontology_1.buildCanonicalId)({
|
|
358
|
-
eventType: normalizedEventType,
|
|
359
|
-
summary,
|
|
360
|
-
entities,
|
|
361
|
-
relations: relationValidation.accepted,
|
|
362
|
-
outcome: event.outcome,
|
|
363
|
-
}),
|
|
364
|
-
actor: event.actor || "system",
|
|
365
|
-
confidence,
|
|
366
|
-
id,
|
|
367
|
-
timestamp: new Date().toISOString(),
|
|
368
|
-
layer: "archive",
|
|
369
|
-
gate_source: gateSource,
|
|
370
|
-
embedding_status: embeddingStatus,
|
|
371
|
-
quality_score: quality.score,
|
|
372
|
-
quality_level: quality.level,
|
|
373
|
-
char_count: buildArchiveVectorText(event, normalizedEventType, entities).length,
|
|
374
|
-
token_count: estimateTokenCount(buildArchiveVectorText(event, normalizedEventType, entities)),
|
|
375
|
-
vector_chunks_total: vectorChunksTotal,
|
|
376
|
-
vector_chunks_ok: vectorChunksOk,
|
|
377
|
-
embedding,
|
|
378
|
-
};
|
|
609
|
+
record.embedding = embedding;
|
|
379
610
|
lines.push(JSON.stringify(record));
|
|
380
611
|
stored.push(record);
|
|
381
612
|
options.deduplicator.append({
|
|
382
613
|
id: record.id,
|
|
383
|
-
summary: `${record.event_type}: ${summary}`,
|
|
614
|
+
summary: dedupText || `${record.event_type}: ${summary}`,
|
|
384
615
|
embedding: embedding,
|
|
385
616
|
});
|
|
386
617
|
mutationLines.push(JSON.stringify({
|
|
@@ -405,13 +636,13 @@ function createArchiveStore(options) {
|
|
|
405
636
|
summary: chunkRow.summary,
|
|
406
637
|
timestamp: record.timestamp,
|
|
407
638
|
outcome: record.outcome,
|
|
408
|
-
entities: record.entities,
|
|
409
|
-
relations: record.relations,
|
|
410
639
|
embedding: chunkRow.embedding,
|
|
411
640
|
quality_score: record.quality_score,
|
|
412
641
|
layer: "archive",
|
|
413
642
|
source_memory_id: record.id,
|
|
414
643
|
source_memory_canonical_id: record.canonical_id,
|
|
644
|
+
source_event_id: record.source_event_id || record.id,
|
|
645
|
+
source_field: chunkRow.source_field,
|
|
415
646
|
char_count: chunkRow.summary.length,
|
|
416
647
|
token_count: estimateTokenCount(chunkRow.summary),
|
|
417
648
|
chunk_index: chunkRow.chunk_index,
|
|
@@ -435,6 +666,12 @@ function createArchiveStore(options) {
|
|
|
435
666
|
if (lines.length > 0) {
|
|
436
667
|
ensureDirForFile(archivePath);
|
|
437
668
|
fs.appendFileSync(archivePath, `${lines.join("\n")}\n`, "utf-8");
|
|
669
|
+
for (let i = 0; i < lines.length; i++) {
|
|
670
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
671
|
+
if (!validation.valid && validation.errors.length > 0) {
|
|
672
|
+
options.logger.warn(`archive_write_integrity_check_failed line=${i} errors=${validation.errors.join("|")}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
438
675
|
ensureDirForFile(mutationLogPath);
|
|
439
676
|
fs.appendFileSync(mutationLogPath, `${mutationLines.join("\n")}\n`, "utf-8");
|
|
440
677
|
}
|