openclaw-cortex-memory 0.1.0-Alpha.3 → 0.1.0-Alpha.31
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 +296 -203
- package/SIGNATURE.md +7 -0
- package/SKILL.md +92 -268
- package/dist/index.d.ts +100 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1249 -1252
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +501 -16
- package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
- package/dist/src/dedup/three_stage_deduplicator.js +224 -0
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
- package/dist/src/engine/memory_engine.d.ts +6 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +242 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +1468 -52
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +29 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +125 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +1237 -0
- package/dist/src/graph/ontology.js.map +1 -0
- 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 +66 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +659 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts +7 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +352 -8
- 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 +33 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +67 -64
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +136 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +635 -0
- package/dist/src/store/archive_store.js.map +1 -0
- package/dist/src/store/embedding_utils.d.ts +32 -0
- package/dist/src/store/embedding_utils.d.ts.map +1 -0
- package/dist/src/store/embedding_utils.js +173 -0
- package/dist/src/store/embedding_utils.js.map +1 -0
- package/dist/src/store/graph_memory_store.d.ts +114 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +841 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +89 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +2459 -28
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +45 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +202 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +54 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +284 -6
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +119 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2377 -31
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/utils/runtime_env.d.ts +4 -0
- package/dist/src/utils/runtime_env.d.ts.map +1 -0
- package/dist/src/utils/runtime_env.js +51 -0
- package/dist/src/utils/runtime_env.js.map +1 -0
- package/dist/src/wiki/wiki_linter.d.ts +25 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +268 -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 +36 -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 +33 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +633 -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 +501 -16
- package/package.json +58 -7
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +19 -14
- package/scripts/repair-memory.js +321 -0
- package/scripts/uninstall.js +22 -5
- package/skills/cortex-memory/SKILL.md +49 -0
- package/skills/cortex-memory/references/agent-manual.md +115 -0
- package/skills/cortex-memory/references/configuration.md +92 -0
- package/skills/cortex-memory/references/publish-checklist.md +46 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +181 -0
- package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
- package/index.ts +0 -2142
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeEntityName = normalizeEntityName;
|
|
37
|
+
exports.getEntityMatchKeys = getEntityMatchKeys;
|
|
38
|
+
exports.loadGraphSchema = loadGraphSchema;
|
|
39
|
+
exports.normalizeEventType = normalizeEventType;
|
|
40
|
+
exports.normalizeRelationType = normalizeRelationType;
|
|
41
|
+
exports.isCanonicalRelationType = isCanonicalRelationType;
|
|
42
|
+
exports.getDefaultGraphSchema = getDefaultGraphSchema;
|
|
43
|
+
exports.buildRelationPromptHint = buildRelationPromptHint;
|
|
44
|
+
exports.buildCanonicalId = buildCanonicalId;
|
|
45
|
+
exports.validateRelations = validateRelations;
|
|
46
|
+
exports.normalizeEntityType = normalizeEntityType;
|
|
47
|
+
exports.validateGraphPayload = validateGraphPayload;
|
|
48
|
+
const crypto = __importStar(require("crypto"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const DEFAULT_SCHEMA = {
|
|
52
|
+
eventTypes: [
|
|
53
|
+
"decision",
|
|
54
|
+
"issue",
|
|
55
|
+
"fix",
|
|
56
|
+
"preference",
|
|
57
|
+
"plan",
|
|
58
|
+
"risk",
|
|
59
|
+
"insight",
|
|
60
|
+
"action_item",
|
|
61
|
+
"conversation_summary",
|
|
62
|
+
"constraint",
|
|
63
|
+
"requirement",
|
|
64
|
+
"milestone",
|
|
65
|
+
"blocker",
|
|
66
|
+
"dependency",
|
|
67
|
+
"assumption",
|
|
68
|
+
"retrospective",
|
|
69
|
+
"follow_up",
|
|
70
|
+
],
|
|
71
|
+
eventTypeAliases: {
|
|
72
|
+
problem: "issue",
|
|
73
|
+
error: "issue",
|
|
74
|
+
bug: "issue",
|
|
75
|
+
solution: "fix",
|
|
76
|
+
workaround: "fix",
|
|
77
|
+
todo: "action_item",
|
|
78
|
+
next_step: "action_item",
|
|
79
|
+
limitation: "constraint",
|
|
80
|
+
guardrail: "constraint",
|
|
81
|
+
spec: "requirement",
|
|
82
|
+
acceptance_criteria: "requirement",
|
|
83
|
+
deadline: "milestone",
|
|
84
|
+
target: "milestone",
|
|
85
|
+
roadblock: "blocker",
|
|
86
|
+
stuck: "blocker",
|
|
87
|
+
depends_on: "dependency",
|
|
88
|
+
upstream: "dependency",
|
|
89
|
+
hypothesis: "assumption",
|
|
90
|
+
lesson: "retrospective",
|
|
91
|
+
postmortem: "retrospective",
|
|
92
|
+
followup: "follow_up",
|
|
93
|
+
next_action: "follow_up",
|
|
94
|
+
},
|
|
95
|
+
entityTypes: [
|
|
96
|
+
"Person",
|
|
97
|
+
"FamilyMember",
|
|
98
|
+
"Friend",
|
|
99
|
+
"Team",
|
|
100
|
+
"Project",
|
|
101
|
+
"Task",
|
|
102
|
+
"Plan",
|
|
103
|
+
"Milestone",
|
|
104
|
+
"Location",
|
|
105
|
+
"Event",
|
|
106
|
+
"Schedule",
|
|
107
|
+
"Habit",
|
|
108
|
+
"HealthItem",
|
|
109
|
+
"FinanceItem",
|
|
110
|
+
"Issue",
|
|
111
|
+
"Fix",
|
|
112
|
+
"Decision",
|
|
113
|
+
"Action",
|
|
114
|
+
"Risk",
|
|
115
|
+
"Blocker",
|
|
116
|
+
"Assumption",
|
|
117
|
+
"Concept",
|
|
118
|
+
"Resource",
|
|
119
|
+
"Document",
|
|
120
|
+
"ConfigFile",
|
|
121
|
+
"Preference",
|
|
122
|
+
"Case",
|
|
123
|
+
"Pattern",
|
|
124
|
+
"Date",
|
|
125
|
+
],
|
|
126
|
+
entityAliases: {
|
|
127
|
+
"OpenClaw": ["openclaw", "插件", "该项目", "本项目"],
|
|
128
|
+
"FamilyMember": ["家人", "家庭成员", "亲人"],
|
|
129
|
+
"Friend": ["朋友", "好友"],
|
|
130
|
+
"Team": ["团队", "小组", "组", "班组"],
|
|
131
|
+
"Location": ["地点", "位置", "住址", "地址"],
|
|
132
|
+
"Event": ["活动", "事情", "事项"],
|
|
133
|
+
"Schedule": ["日程", "安排", "计划表"],
|
|
134
|
+
"Habit": ["习惯", "作息"],
|
|
135
|
+
"HealthItem": ["健康", "体检", "药物", "锻炼"],
|
|
136
|
+
"FinanceItem": ["账单", "支出", "收入", "预算"],
|
|
137
|
+
"Plan": ["计划", "方案", "路线图"],
|
|
138
|
+
"Preference": ["偏好", "习惯选择"],
|
|
139
|
+
"Document": ["文档", "说明文档", "手册", "wiki", "README", "PRD", "方案文档"],
|
|
140
|
+
"Resource": ["资源", "物品", "物件", "设备", "工具", "素材", "资产"],
|
|
141
|
+
"ConfigFile": ["配置文件", "config", "配置"],
|
|
142
|
+
"Decision": ["决策", "决定", "拍板"],
|
|
143
|
+
"Action": ["动作", "操作", "执行"],
|
|
144
|
+
"Risk": ["风险", "隐患"],
|
|
145
|
+
"Blocker": ["阻塞", "卡点", "障碍"],
|
|
146
|
+
"Assumption": ["假设", "前提"],
|
|
147
|
+
"Concept": ["概念", "术语"],
|
|
148
|
+
"Case": ["案例", "case"],
|
|
149
|
+
"Pattern": ["模式", "pattern"],
|
|
150
|
+
"Date": ["日期", "时间", "时间点"],
|
|
151
|
+
"Person": ["我", "自己", "本人", "同事", "客户", "用户", "姓名", "名字", "人名", "成员", "联系人"],
|
|
152
|
+
"Project": ["项目", "工程", "项目线"],
|
|
153
|
+
"Task": ["任务", "待办", "todo", "工单", "事项"],
|
|
154
|
+
"Milestone": ["里程碑", "节点"],
|
|
155
|
+
"Issue": ["问题", "故障", "报错"],
|
|
156
|
+
"Fix": ["修复", "解决方案"],
|
|
157
|
+
},
|
|
158
|
+
relationTypes: [
|
|
159
|
+
"depends_on",
|
|
160
|
+
"blocks",
|
|
161
|
+
"unblocks",
|
|
162
|
+
"causes",
|
|
163
|
+
"impacts",
|
|
164
|
+
"resolves",
|
|
165
|
+
"encountered_bug",
|
|
166
|
+
"solved_with",
|
|
167
|
+
"uses_tech",
|
|
168
|
+
"integrates_with",
|
|
169
|
+
"migrates_to",
|
|
170
|
+
"replaced_by",
|
|
171
|
+
"has_subtask",
|
|
172
|
+
"belongs_to",
|
|
173
|
+
"owned_by",
|
|
174
|
+
"implements",
|
|
175
|
+
"requires",
|
|
176
|
+
"plans_to",
|
|
177
|
+
"planned_for",
|
|
178
|
+
"scheduled_for",
|
|
179
|
+
"references",
|
|
180
|
+
"documents",
|
|
181
|
+
"defined_in",
|
|
182
|
+
"configured_in",
|
|
183
|
+
"supports",
|
|
184
|
+
"conflicts_with",
|
|
185
|
+
"duplicates",
|
|
186
|
+
"supersedes",
|
|
187
|
+
"assigned_to",
|
|
188
|
+
"reviewed_by",
|
|
189
|
+
"approved_by",
|
|
190
|
+
"rejected_by",
|
|
191
|
+
"reported_by",
|
|
192
|
+
"lives_in",
|
|
193
|
+
"cares_for",
|
|
194
|
+
"pays_for",
|
|
195
|
+
"prefers",
|
|
196
|
+
"has_spouse",
|
|
197
|
+
"has_child",
|
|
198
|
+
"birthday_on",
|
|
199
|
+
"anniversary_on",
|
|
200
|
+
],
|
|
201
|
+
relationTypeAliases: {
|
|
202
|
+
dependency: "depends_on",
|
|
203
|
+
blocked_by: "blocks",
|
|
204
|
+
unblock: "unblocks",
|
|
205
|
+
impact: "impacts",
|
|
206
|
+
plan_to: "plans_to",
|
|
207
|
+
plan_for: "planned_for",
|
|
208
|
+
schedule_for: "scheduled_for",
|
|
209
|
+
located_in: "lives_in",
|
|
210
|
+
care_for: "cares_for",
|
|
211
|
+
pay_for: "pays_for",
|
|
212
|
+
support: "supports",
|
|
213
|
+
conflict_with: "conflicts_with",
|
|
214
|
+
use_tech: "uses_tech",
|
|
215
|
+
tech_stack: "uses_tech",
|
|
216
|
+
integrate_with: "integrates_with",
|
|
217
|
+
migrate_to: "migrates_to",
|
|
218
|
+
replace_by: "replaced_by",
|
|
219
|
+
replace_with: "replaced_by",
|
|
220
|
+
bug: "encountered_bug",
|
|
221
|
+
bug_on: "encountered_bug",
|
|
222
|
+
fix_with: "solved_with",
|
|
223
|
+
solve_with: "solved_with",
|
|
224
|
+
solved_by: "solved_with",
|
|
225
|
+
subtask_of: "has_subtask",
|
|
226
|
+
child_task: "has_subtask",
|
|
227
|
+
documented_by: "documents",
|
|
228
|
+
defined_by: "defined_in",
|
|
229
|
+
config_in: "configured_in",
|
|
230
|
+
duplicate_of: "duplicates",
|
|
231
|
+
superseded_by: "supersedes",
|
|
232
|
+
assign_to: "assigned_to",
|
|
233
|
+
review_by: "reviewed_by",
|
|
234
|
+
approve_by: "approved_by",
|
|
235
|
+
reject_by: "rejected_by",
|
|
236
|
+
report_by: "reported_by",
|
|
237
|
+
"依赖于": "depends_on",
|
|
238
|
+
"依赖": "depends_on",
|
|
239
|
+
"取决于": "depends_on",
|
|
240
|
+
"阻塞": "blocks",
|
|
241
|
+
"卡住": "blocks",
|
|
242
|
+
"解除阻塞": "unblocks",
|
|
243
|
+
"导致": "causes",
|
|
244
|
+
"引起": "causes",
|
|
245
|
+
"影响": "impacts",
|
|
246
|
+
"解决": "resolves",
|
|
247
|
+
"修复": "resolves",
|
|
248
|
+
"遇到报错": "encountered_bug",
|
|
249
|
+
"通过": "solved_with",
|
|
250
|
+
"使用技术": "uses_tech",
|
|
251
|
+
"集成": "integrates_with",
|
|
252
|
+
"迁移到": "migrates_to",
|
|
253
|
+
"被替代": "replaced_by",
|
|
254
|
+
"子任务": "has_subtask",
|
|
255
|
+
"属于": "belongs_to",
|
|
256
|
+
"归属": "belongs_to",
|
|
257
|
+
"负责": "owned_by",
|
|
258
|
+
"由": "owned_by",
|
|
259
|
+
"参考": "references",
|
|
260
|
+
"引用": "references",
|
|
261
|
+
"偏好": "prefers",
|
|
262
|
+
"更喜欢": "prefers",
|
|
263
|
+
"实现": "implements",
|
|
264
|
+
"需要": "requires",
|
|
265
|
+
"计划做": "plans_to",
|
|
266
|
+
"打算": "plans_to",
|
|
267
|
+
"计划于": "planned_for",
|
|
268
|
+
"安排在": "scheduled_for",
|
|
269
|
+
"约在": "scheduled_for",
|
|
270
|
+
"住在": "lives_in",
|
|
271
|
+
"居住在": "lives_in",
|
|
272
|
+
"照顾": "cares_for",
|
|
273
|
+
"看护": "cares_for",
|
|
274
|
+
"支付": "pays_for",
|
|
275
|
+
"付款": "pays_for",
|
|
276
|
+
"支持": "supports",
|
|
277
|
+
"冲突": "conflicts_with",
|
|
278
|
+
"矛盾": "conflicts_with",
|
|
279
|
+
"记录": "documents",
|
|
280
|
+
"定义于": "defined_in",
|
|
281
|
+
"配置于": "configured_in",
|
|
282
|
+
"重复": "duplicates",
|
|
283
|
+
"取代": "supersedes",
|
|
284
|
+
"分配给": "assigned_to",
|
|
285
|
+
"评审": "reviewed_by",
|
|
286
|
+
"批准": "approved_by",
|
|
287
|
+
"拒绝": "rejected_by",
|
|
288
|
+
"报告": "reported_by",
|
|
289
|
+
belongs: "belongs_to",
|
|
290
|
+
owner_of: "owned_by",
|
|
291
|
+
refer_to: "references",
|
|
292
|
+
preference_for: "prefers",
|
|
293
|
+
implement: "implements",
|
|
294
|
+
need: "requires",
|
|
295
|
+
technology: "uses_tech",
|
|
296
|
+
encountered_issue: "encountered_bug",
|
|
297
|
+
spouse: "has_spouse",
|
|
298
|
+
wife_of: "has_spouse",
|
|
299
|
+
husband_of: "has_spouse",
|
|
300
|
+
child_of: "has_child",
|
|
301
|
+
parent_of: "has_child",
|
|
302
|
+
birthday: "birthday_on",
|
|
303
|
+
born_on: "birthday_on",
|
|
304
|
+
anniversary: "anniversary_on",
|
|
305
|
+
married_on: "anniversary_on",
|
|
306
|
+
},
|
|
307
|
+
relationRules: [
|
|
308
|
+
{ type: "depends_on", fromTypes: ["Task", "Plan", "Milestone"], toTypes: ["Task", "Plan", "Milestone"], allowSelfLoop: false },
|
|
309
|
+
{ type: "blocks", fromTypes: ["Issue", "Task", "Risk"], toTypes: ["Task", "Plan"], allowSelfLoop: false },
|
|
310
|
+
{ type: "causes", fromTypes: ["Issue", "Risk", "Assumption"], toTypes: ["Issue", "Risk"], allowSelfLoop: false },
|
|
311
|
+
{ type: "resolves", fromTypes: ["Fix", "Decision", "Action"], toTypes: ["Issue", "Blocker"], allowSelfLoop: false },
|
|
312
|
+
{ type: "belongs_to", fromTypes: ["Task", "Issue", "Fix", "Decision"], toTypes: ["Project", "Plan", "Milestone"], allowSelfLoop: false },
|
|
313
|
+
{ type: "owned_by", fromTypes: ["Task", "Plan", "Project", "Issue"], toTypes: ["Person", "Team"], allowSelfLoop: false },
|
|
314
|
+
{ type: "uses_tech", fromTypes: ["Project", "Task", "Fix", "Action"], toTypes: ["Resource", "Document", "Concept", "Project"], allowSelfLoop: false },
|
|
315
|
+
{ type: "encountered_bug", fromTypes: ["Project", "Task", "Action"], toTypes: ["Issue", "Blocker"], allowSelfLoop: false },
|
|
316
|
+
{ type: "solved_with", fromTypes: ["Issue", "Blocker"], toTypes: ["Fix", "Action", "Decision", "Resource"], allowSelfLoop: false },
|
|
317
|
+
{ type: "has_subtask", fromTypes: ["Project", "Plan", "Milestone", "Task"], toTypes: ["Task"], allowSelfLoop: false },
|
|
318
|
+
{ type: "planned_for", fromTypes: ["Task", "Plan", "Milestone"], toTypes: ["Date", "Schedule", "Milestone"], allowSelfLoop: false },
|
|
319
|
+
],
|
|
320
|
+
highValueRelationTypes: [
|
|
321
|
+
"depends_on",
|
|
322
|
+
"blocks",
|
|
323
|
+
"unblocks",
|
|
324
|
+
"causes",
|
|
325
|
+
"impacts",
|
|
326
|
+
"resolves",
|
|
327
|
+
"encountered_bug",
|
|
328
|
+
"solved_with",
|
|
329
|
+
"uses_tech",
|
|
330
|
+
"integrates_with",
|
|
331
|
+
"migrates_to",
|
|
332
|
+
"replaced_by",
|
|
333
|
+
"has_subtask",
|
|
334
|
+
"belongs_to",
|
|
335
|
+
"owned_by",
|
|
336
|
+
"implements",
|
|
337
|
+
"requires",
|
|
338
|
+
"planned_for",
|
|
339
|
+
"scheduled_for",
|
|
340
|
+
],
|
|
341
|
+
relatedToMaxRatio: 0,
|
|
342
|
+
relatedToMaxAbsolute: 0,
|
|
343
|
+
minRelationConfidence: 0.35,
|
|
344
|
+
evidenceSpanRequired: true,
|
|
345
|
+
endpointMentionRequired: true,
|
|
346
|
+
defaultEntityType: "Concept",
|
|
347
|
+
};
|
|
348
|
+
function toLowerMap(input) {
|
|
349
|
+
const output = {};
|
|
350
|
+
for (const [key, value] of Object.entries(input)) {
|
|
351
|
+
output[key.trim().toLowerCase()] = value.trim();
|
|
352
|
+
}
|
|
353
|
+
return output;
|
|
354
|
+
}
|
|
355
|
+
function toKeyedRules(input) {
|
|
356
|
+
const map = new Map();
|
|
357
|
+
for (const item of input) {
|
|
358
|
+
map.set(item.type.trim().toLowerCase(), item);
|
|
359
|
+
}
|
|
360
|
+
return map;
|
|
361
|
+
}
|
|
362
|
+
function schemaFilePath(projectRoot) {
|
|
363
|
+
return path.join(projectRoot, "schema", "graph.schema.yaml");
|
|
364
|
+
}
|
|
365
|
+
function clampNumber(value, min, max, fallback) {
|
|
366
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
367
|
+
return fallback;
|
|
368
|
+
}
|
|
369
|
+
return Math.max(min, Math.min(max, value));
|
|
370
|
+
}
|
|
371
|
+
function sanitizeEntityAliases(input) {
|
|
372
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
373
|
+
return DEFAULT_SCHEMA.entityAliases;
|
|
374
|
+
}
|
|
375
|
+
const output = {};
|
|
376
|
+
for (const [canonical, aliasesRaw] of Object.entries(input)) {
|
|
377
|
+
const canonicalName = canonical.trim();
|
|
378
|
+
if (!canonicalName)
|
|
379
|
+
continue;
|
|
380
|
+
const aliases = Array.isArray(aliasesRaw)
|
|
381
|
+
? aliasesRaw.map(item => (typeof item === "string" ? item.trim() : "")).filter(Boolean)
|
|
382
|
+
: [];
|
|
383
|
+
output[canonicalName] = aliases;
|
|
384
|
+
}
|
|
385
|
+
return Object.keys(output).length > 0 ? output : DEFAULT_SCHEMA.entityAliases;
|
|
386
|
+
}
|
|
387
|
+
function normalizeAliasKey(value) {
|
|
388
|
+
return value
|
|
389
|
+
.normalize("NFKC")
|
|
390
|
+
.toLowerCase()
|
|
391
|
+
.replace(/[`"'“”‘’]/g, "")
|
|
392
|
+
.replace(/[【】[\]{}()<>]/g, " ")
|
|
393
|
+
.replace(/[_\-\/\\|]+/g, " ")
|
|
394
|
+
.replace(/[,:;!?]+/g, " ")
|
|
395
|
+
.replace(/\s+/g, " ")
|
|
396
|
+
.trim();
|
|
397
|
+
}
|
|
398
|
+
function buildEntityLookupKeys(value) {
|
|
399
|
+
const trimmed = value.trim();
|
|
400
|
+
if (!trimmed) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
const normalized = normalizeAliasKey(trimmed);
|
|
404
|
+
const compact = normalized.replace(/\s+/g, "");
|
|
405
|
+
const keys = new Set();
|
|
406
|
+
keys.add(trimmed.toLowerCase());
|
|
407
|
+
if (normalized) {
|
|
408
|
+
keys.add(normalized);
|
|
409
|
+
}
|
|
410
|
+
if (compact) {
|
|
411
|
+
keys.add(compact);
|
|
412
|
+
}
|
|
413
|
+
return [...keys];
|
|
414
|
+
}
|
|
415
|
+
function chooseCanonicalAlias(leftRaw, rightRaw) {
|
|
416
|
+
const left = leftRaw.trim();
|
|
417
|
+
const right = rightRaw.trim();
|
|
418
|
+
if (!left)
|
|
419
|
+
return right;
|
|
420
|
+
if (!right)
|
|
421
|
+
return left;
|
|
422
|
+
const leftAscii = /[A-Za-z]/.test(left);
|
|
423
|
+
const rightAscii = /[A-Za-z]/.test(right);
|
|
424
|
+
if (leftAscii && !rightAscii)
|
|
425
|
+
return left;
|
|
426
|
+
if (!leftAscii && rightAscii)
|
|
427
|
+
return right;
|
|
428
|
+
return left.length >= right.length ? left : right;
|
|
429
|
+
}
|
|
430
|
+
function buildRuntimeAliasLookup(sourceText) {
|
|
431
|
+
const lookup = new Map();
|
|
432
|
+
const text = (sourceText || "").trim();
|
|
433
|
+
if (!text) {
|
|
434
|
+
return lookup;
|
|
435
|
+
}
|
|
436
|
+
const pairPattern = /([^()\n()]{1,80})\s*[((]\s*([^()\n()]{1,80})\s*[))]/g;
|
|
437
|
+
let matched = pairPattern.exec(text);
|
|
438
|
+
while (matched) {
|
|
439
|
+
const left = (matched[1] || "").trim();
|
|
440
|
+
const right = (matched[2] || "").trim();
|
|
441
|
+
if (left && right && left !== right) {
|
|
442
|
+
const canonical = chooseCanonicalAlias(left, right);
|
|
443
|
+
const alias = canonical === left ? right : left;
|
|
444
|
+
for (const key of buildEntityLookupKeys(alias)) {
|
|
445
|
+
lookup.set(key, canonical);
|
|
446
|
+
}
|
|
447
|
+
for (const key of buildEntityLookupKeys(canonical)) {
|
|
448
|
+
lookup.set(key, canonical);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
matched = pairPattern.exec(text);
|
|
452
|
+
}
|
|
453
|
+
return lookup;
|
|
454
|
+
}
|
|
455
|
+
function buildAliasLookup(schema) {
|
|
456
|
+
const lookup = new Map();
|
|
457
|
+
for (const [canonical, aliases] of Object.entries(schema.entityAliases || {})) {
|
|
458
|
+
const normalizedCanonical = canonical.trim();
|
|
459
|
+
if (!normalizedCanonical)
|
|
460
|
+
continue;
|
|
461
|
+
for (const key of buildEntityLookupKeys(normalizedCanonical)) {
|
|
462
|
+
lookup.set(key, normalizedCanonical);
|
|
463
|
+
}
|
|
464
|
+
for (const alias of aliases || []) {
|
|
465
|
+
for (const key of buildEntityLookupKeys(alias)) {
|
|
466
|
+
lookup.set(key, normalizedCanonical);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return lookup;
|
|
471
|
+
}
|
|
472
|
+
function normalizeEntityName(raw, schema, runtimeAliasLookup) {
|
|
473
|
+
const value = raw.trim();
|
|
474
|
+
if (!value)
|
|
475
|
+
return "";
|
|
476
|
+
const lookup = buildAliasLookup(schema);
|
|
477
|
+
for (const key of buildEntityLookupKeys(value)) {
|
|
478
|
+
const runtimeMapped = runtimeAliasLookup?.get(key);
|
|
479
|
+
if (runtimeMapped) {
|
|
480
|
+
return runtimeMapped;
|
|
481
|
+
}
|
|
482
|
+
const schemaMapped = lookup.get(key);
|
|
483
|
+
if (schemaMapped) {
|
|
484
|
+
return schemaMapped;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
function getEntityMatchKeys(raw, schema) {
|
|
490
|
+
const value = raw.trim();
|
|
491
|
+
if (!value) {
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
const canonical = normalizeEntityName(value, schema);
|
|
495
|
+
const keys = new Set();
|
|
496
|
+
for (const key of buildEntityLookupKeys(value)) {
|
|
497
|
+
keys.add(key);
|
|
498
|
+
}
|
|
499
|
+
for (const key of buildEntityLookupKeys(canonical)) {
|
|
500
|
+
keys.add(key);
|
|
501
|
+
}
|
|
502
|
+
for (const alias of schema.entityAliases[canonical] || []) {
|
|
503
|
+
for (const key of buildEntityLookupKeys(alias)) {
|
|
504
|
+
keys.add(key);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return [...keys];
|
|
508
|
+
}
|
|
509
|
+
function tokenizeForMatch(value) {
|
|
510
|
+
return value.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
511
|
+
}
|
|
512
|
+
function entityMentionedInText(entity, sourceText, schema) {
|
|
513
|
+
const text = tokenizeForMatch(sourceText || "");
|
|
514
|
+
if (!text)
|
|
515
|
+
return false;
|
|
516
|
+
const canonical = normalizeEntityName(entity, schema);
|
|
517
|
+
const target = tokenizeForMatch(canonical || entity);
|
|
518
|
+
if (target && text.includes(target)) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
const aliases = schema.entityAliases[canonical] || [];
|
|
522
|
+
for (const alias of aliases) {
|
|
523
|
+
const normalized = tokenizeForMatch(alias);
|
|
524
|
+
if (normalized && text.includes(normalized)) {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
function loadGraphSchema(projectRoot) {
|
|
531
|
+
const filePath = schemaFilePath(projectRoot);
|
|
532
|
+
if (!fs.existsSync(filePath)) {
|
|
533
|
+
return DEFAULT_SCHEMA;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const raw = fs.readFileSync(filePath, "utf-8").trim();
|
|
537
|
+
if (!raw) {
|
|
538
|
+
return DEFAULT_SCHEMA;
|
|
539
|
+
}
|
|
540
|
+
const parsed = JSON.parse(raw);
|
|
541
|
+
return {
|
|
542
|
+
eventTypes: Array.isArray(parsed.eventTypes) && parsed.eventTypes.length > 0 ? parsed.eventTypes : DEFAULT_SCHEMA.eventTypes,
|
|
543
|
+
eventTypeAliases: parsed.eventTypeAliases && typeof parsed.eventTypeAliases === "object" ? parsed.eventTypeAliases : DEFAULT_SCHEMA.eventTypeAliases,
|
|
544
|
+
entityTypes: Array.isArray(parsed.entityTypes) && parsed.entityTypes.length > 0 ? parsed.entityTypes : DEFAULT_SCHEMA.entityTypes,
|
|
545
|
+
entityAliases: sanitizeEntityAliases(parsed.entityAliases),
|
|
546
|
+
relationTypes: Array.isArray(parsed.relationTypes) && parsed.relationTypes.length > 0 ? parsed.relationTypes : DEFAULT_SCHEMA.relationTypes,
|
|
547
|
+
relationTypeAliases: parsed.relationTypeAliases && typeof parsed.relationTypeAliases === "object" ? parsed.relationTypeAliases : DEFAULT_SCHEMA.relationTypeAliases,
|
|
548
|
+
relationRules: Array.isArray(parsed.relationRules) && parsed.relationRules.length > 0 ? parsed.relationRules : DEFAULT_SCHEMA.relationRules,
|
|
549
|
+
highValueRelationTypes: Array.isArray(parsed.highValueRelationTypes) && parsed.highValueRelationTypes.length > 0
|
|
550
|
+
? parsed.highValueRelationTypes.map(item => String(item).trim()).filter(Boolean)
|
|
551
|
+
: DEFAULT_SCHEMA.highValueRelationTypes,
|
|
552
|
+
relatedToMaxRatio: clampNumber(parsed.relatedToMaxRatio, 0, 1, DEFAULT_SCHEMA.relatedToMaxRatio),
|
|
553
|
+
relatedToMaxAbsolute: Math.max(0, Math.floor(clampNumber(parsed.relatedToMaxAbsolute, 0, 20, DEFAULT_SCHEMA.relatedToMaxAbsolute))),
|
|
554
|
+
minRelationConfidence: clampNumber(parsed.minRelationConfidence, 0, 1, DEFAULT_SCHEMA.minRelationConfidence),
|
|
555
|
+
evidenceSpanRequired: typeof parsed.evidenceSpanRequired === "boolean" ? parsed.evidenceSpanRequired : DEFAULT_SCHEMA.evidenceSpanRequired,
|
|
556
|
+
endpointMentionRequired: typeof parsed.endpointMentionRequired === "boolean" ? parsed.endpointMentionRequired : DEFAULT_SCHEMA.endpointMentionRequired,
|
|
557
|
+
defaultEntityType: typeof parsed.defaultEntityType === "string" && parsed.defaultEntityType.trim()
|
|
558
|
+
? parsed.defaultEntityType.trim()
|
|
559
|
+
: DEFAULT_SCHEMA.defaultEntityType,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return DEFAULT_SCHEMA;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function normalizeEventType(raw, schema) {
|
|
567
|
+
const value = raw.trim().toLowerCase();
|
|
568
|
+
const aliases = toLowerMap(schema.eventTypeAliases);
|
|
569
|
+
const eventTypes = new Set(schema.eventTypes.map(item => item.toLowerCase()));
|
|
570
|
+
if (eventTypes.has(value)) {
|
|
571
|
+
return value;
|
|
572
|
+
}
|
|
573
|
+
const mapped = aliases[value];
|
|
574
|
+
if (mapped) {
|
|
575
|
+
return mapped;
|
|
576
|
+
}
|
|
577
|
+
return "insight";
|
|
578
|
+
}
|
|
579
|
+
function normalizeRelationType(raw, schema) {
|
|
580
|
+
const value = raw.trim().toLowerCase();
|
|
581
|
+
if (!value) {
|
|
582
|
+
return "";
|
|
583
|
+
}
|
|
584
|
+
const relationTypes = new Set(schema.relationTypes.map(item => item.toLowerCase()));
|
|
585
|
+
const aliases = toLowerMap(schema.relationTypeAliases);
|
|
586
|
+
if (relationTypes.has(value)) {
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
589
|
+
const mapped = aliases[value];
|
|
590
|
+
if (mapped) {
|
|
591
|
+
return mapped;
|
|
592
|
+
}
|
|
593
|
+
const snakeCase = value.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
594
|
+
if (/^[a-z][a-z0-9_]*$/.test(snakeCase)) {
|
|
595
|
+
return snakeCase;
|
|
596
|
+
}
|
|
597
|
+
return "";
|
|
598
|
+
}
|
|
599
|
+
function isCanonicalRelationType(type, schema) {
|
|
600
|
+
const value = type.trim().toLowerCase();
|
|
601
|
+
if (!value) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
const relationTypes = new Set(schema.relationTypes.map(item => item.toLowerCase()));
|
|
605
|
+
return relationTypes.has(value);
|
|
606
|
+
}
|
|
607
|
+
function getDefaultGraphSchema() {
|
|
608
|
+
return DEFAULT_SCHEMA;
|
|
609
|
+
}
|
|
610
|
+
function buildRelationPromptHint(schema) {
|
|
611
|
+
return [
|
|
612
|
+
`Allowed canonical relation types: ${schema.relationTypes.join(", ")}.`,
|
|
613
|
+
"Never use related_to.",
|
|
614
|
+
"If no canonical relation fits, create a snake_case custom relation, set relation_origin=llm_custom, and include relation_definition.",
|
|
615
|
+
].join(" ");
|
|
616
|
+
}
|
|
617
|
+
const GENERIC_ENTITY_BLOCKLIST = new Set([
|
|
618
|
+
"用户",
|
|
619
|
+
"我",
|
|
620
|
+
"我们",
|
|
621
|
+
"你",
|
|
622
|
+
"你们",
|
|
623
|
+
"他",
|
|
624
|
+
"她",
|
|
625
|
+
"他们",
|
|
626
|
+
"问题",
|
|
627
|
+
"方案",
|
|
628
|
+
"实体",
|
|
629
|
+
"系统",
|
|
630
|
+
"task",
|
|
631
|
+
"issue",
|
|
632
|
+
"solution",
|
|
633
|
+
"system",
|
|
634
|
+
"person",
|
|
635
|
+
"user",
|
|
636
|
+
"thing",
|
|
637
|
+
]);
|
|
638
|
+
function isGenericEntityName(raw) {
|
|
639
|
+
const value = normalizeAliasKey(String(raw || ""));
|
|
640
|
+
return value ? GENERIC_ENTITY_BLOCKLIST.has(value) : false;
|
|
641
|
+
}
|
|
642
|
+
function collectEntitiesFromRelations(relations, schema, runtimeAliasLookup) {
|
|
643
|
+
const output = new Set();
|
|
644
|
+
for (const relation of relations) {
|
|
645
|
+
const source = normalizeEntityName(relation.source || "", schema, runtimeAliasLookup);
|
|
646
|
+
const target = normalizeEntityName(relation.target || "", schema, runtimeAliasLookup);
|
|
647
|
+
if (source)
|
|
648
|
+
output.add(source);
|
|
649
|
+
if (target)
|
|
650
|
+
output.add(target);
|
|
651
|
+
}
|
|
652
|
+
return [...output];
|
|
653
|
+
}
|
|
654
|
+
function extractResourceReferences(sourceText) {
|
|
655
|
+
const text = (sourceText || "").trim();
|
|
656
|
+
if (!text) {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
const output = new Set();
|
|
660
|
+
const urlMatches = text.match(/https?:\/\/[^\s)>"'`]+|www\.[^\s)>"'`]+/gi) || [];
|
|
661
|
+
const normalizedUrls = urlMatches.map(item => item.trim()).filter(Boolean);
|
|
662
|
+
for (const item of urlMatches) {
|
|
663
|
+
output.add(item.trim());
|
|
664
|
+
}
|
|
665
|
+
const pathMatches = text.match(/[A-Za-z]:\\[^\s"']+|(?:\.{0,2}\/)?(?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]{1,12}/g) || [];
|
|
666
|
+
for (const item of pathMatches) {
|
|
667
|
+
const value = item.trim();
|
|
668
|
+
const compact = value.replace(/^\.\/+/, "").replace(/^\/+/, "");
|
|
669
|
+
const coveredByUrl = normalizedUrls.some(url => url.includes(value) || (compact && url.includes(compact)));
|
|
670
|
+
if (coveredByUrl) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (value.length >= 4) {
|
|
674
|
+
output.add(value);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return [...output].slice(0, 12);
|
|
678
|
+
}
|
|
679
|
+
function inferEntityTypeFromName(entity, schema) {
|
|
680
|
+
const valid = new Set(schema.entityTypes);
|
|
681
|
+
const value = entity.trim();
|
|
682
|
+
if (!value) {
|
|
683
|
+
return schema.defaultEntityType;
|
|
684
|
+
}
|
|
685
|
+
if (valid.has("Date") && /(?:\d{4}-\d{2}-\d{2}|\d{1,2}月\d{1,2}日|\d{1,2}[/-]\d{1,2})/.test(value)) {
|
|
686
|
+
return "Date";
|
|
687
|
+
}
|
|
688
|
+
if (valid.has("Resource") && /^(https?:\/\/|www\.)/i.test(value)) {
|
|
689
|
+
return "Resource";
|
|
690
|
+
}
|
|
691
|
+
if (valid.has("Document")
|
|
692
|
+
&& (/([/\\].+\.[A-Za-z0-9]{1,12})$/.test(value) || /\.(md|txt|pdf|docx?|pptx?|xlsx?|json|yaml|yml|xml|html?)$/i.test(value))) {
|
|
693
|
+
return "Document";
|
|
694
|
+
}
|
|
695
|
+
if (valid.has("Team") && /(team|org|organization|团队|组织|公司)/i.test(value)) {
|
|
696
|
+
return "Team";
|
|
697
|
+
}
|
|
698
|
+
if (valid.has("Project") && /(project|repo|仓库|项目|工程)/i.test(value)) {
|
|
699
|
+
return "Project";
|
|
700
|
+
}
|
|
701
|
+
return schema.defaultEntityType;
|
|
702
|
+
}
|
|
703
|
+
function inferEvidenceSpanFromSource(sourceText, candidates) {
|
|
704
|
+
const text = (sourceText || "").trim();
|
|
705
|
+
if (!text) {
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
const normalizedText = tokenizeForMatch(text);
|
|
709
|
+
const uniqueCandidates = [...new Set(candidates.map(item => item.trim()).filter(Boolean))]
|
|
710
|
+
.sort((a, b) => b.length - a.length);
|
|
711
|
+
for (const candidate of uniqueCandidates) {
|
|
712
|
+
const normalized = tokenizeForMatch(candidate);
|
|
713
|
+
if (normalized && normalizedText.includes(normalized)) {
|
|
714
|
+
return candidate;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
function inferContextChunkFromSource(sourceText, anchors) {
|
|
720
|
+
const text = (sourceText || "").trim().replace(/\s+/g, " ");
|
|
721
|
+
if (!text)
|
|
722
|
+
return undefined;
|
|
723
|
+
const normalizedAnchors = anchors.map(item => String(item || "").trim()).filter(Boolean);
|
|
724
|
+
let hitIndex = -1;
|
|
725
|
+
let hitAnchor = "";
|
|
726
|
+
for (const anchor of normalizedAnchors) {
|
|
727
|
+
const idx = text.indexOf(anchor);
|
|
728
|
+
if (idx >= 0) {
|
|
729
|
+
hitIndex = idx;
|
|
730
|
+
hitAnchor = anchor;
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (hitIndex < 0) {
|
|
735
|
+
const fallback = text.slice(0, Math.min(text.length, 100)).trim();
|
|
736
|
+
return fallback || undefined;
|
|
737
|
+
}
|
|
738
|
+
const targetLength = 80;
|
|
739
|
+
const minLength = 50;
|
|
740
|
+
const maxLength = 120;
|
|
741
|
+
let start = Math.max(0, hitIndex - Math.floor((targetLength - hitAnchor.length) / 2));
|
|
742
|
+
let end = Math.min(text.length, start + targetLength);
|
|
743
|
+
if ((end - start) < minLength) {
|
|
744
|
+
end = Math.min(text.length, start + minLength);
|
|
745
|
+
}
|
|
746
|
+
if ((end - start) > maxLength) {
|
|
747
|
+
end = start + maxLength;
|
|
748
|
+
}
|
|
749
|
+
if (end >= text.length && (end - start) < minLength) {
|
|
750
|
+
start = Math.max(0, end - minLength);
|
|
751
|
+
}
|
|
752
|
+
const chunk = text.slice(start, end).trim();
|
|
753
|
+
return chunk || undefined;
|
|
754
|
+
}
|
|
755
|
+
function summaryMentionsEntity(summary, entity, schema, runtimeAliasLookup) {
|
|
756
|
+
const normalizedSummary = tokenizeForMatch(summary || "");
|
|
757
|
+
if (!normalizedSummary) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
const canonical = normalizeEntityName(entity, schema, runtimeAliasLookup);
|
|
761
|
+
const candidates = new Set([
|
|
762
|
+
entity,
|
|
763
|
+
canonical,
|
|
764
|
+
...(schema.entityAliases[canonical] || []),
|
|
765
|
+
]);
|
|
766
|
+
for (const candidateRaw of candidates) {
|
|
767
|
+
const candidate = tokenizeForMatch(candidateRaw || "");
|
|
768
|
+
if (candidate && normalizedSummary.includes(candidate)) {
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
function missingEntitiesInSummary(args) {
|
|
775
|
+
const missing = [];
|
|
776
|
+
for (const entity of args.entities) {
|
|
777
|
+
if (!summaryMentionsEntity(args.summary, entity, args.schema, args.runtimeAliasLookup)) {
|
|
778
|
+
missing.push(entity);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return missing;
|
|
782
|
+
}
|
|
783
|
+
function normalizeSourceTextNav(args) {
|
|
784
|
+
const nav = args.sourceTextNav || {};
|
|
785
|
+
const layerRaw = typeof nav.layer === "string" ? nav.layer.trim() : "";
|
|
786
|
+
const layer = layerRaw === "archive_event" || layerRaw === "active_only"
|
|
787
|
+
? layerRaw
|
|
788
|
+
: args.sourceLayer;
|
|
789
|
+
const sourceEventId = (typeof nav.source_event_id === "string" ? nav.source_event_id : "").trim()
|
|
790
|
+
|| args.sourceEventId.trim()
|
|
791
|
+
|| (typeof args.archiveEventId === "string" ? args.archiveEventId.trim() : "");
|
|
792
|
+
const sourceMemoryId = (typeof nav.source_memory_id === "string" ? nav.source_memory_id : "").trim()
|
|
793
|
+
|| sourceEventId;
|
|
794
|
+
const sessionId = (typeof nav.session_id === "string" ? nav.session_id : "").trim()
|
|
795
|
+
|| args.sessionId.trim();
|
|
796
|
+
const sourceFile = (typeof nav.source_file === "string" ? nav.source_file : "").trim()
|
|
797
|
+
|| (typeof args.sourceFile === "string" ? args.sourceFile.trim() : "");
|
|
798
|
+
const fulltextAnchor = typeof nav.fulltext_anchor === "string" ? nav.fulltext_anchor.trim() : "";
|
|
799
|
+
if (!layer || !sessionId || !sourceFile || !sourceEventId || !sourceMemoryId) {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
layer,
|
|
804
|
+
session_id: sessionId,
|
|
805
|
+
source_file: sourceFile,
|
|
806
|
+
source_memory_id: sourceMemoryId,
|
|
807
|
+
source_event_id: sourceEventId,
|
|
808
|
+
fulltext_anchor: fulltextAnchor || undefined,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function shouldRetryWithFallbackRelations(rejectedReasons) {
|
|
812
|
+
const hardStopReasons = new Set([
|
|
813
|
+
"missing_relation_confidence",
|
|
814
|
+
"missing_evidence_span",
|
|
815
|
+
"low_relation_confidence",
|
|
816
|
+
"empty_edge",
|
|
817
|
+
]);
|
|
818
|
+
for (const reason of rejectedReasons) {
|
|
819
|
+
if (hardStopReasons.has(reason)) {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
function buildFallbackRelations(args) {
|
|
826
|
+
const output = [];
|
|
827
|
+
const dedupe = new Set();
|
|
828
|
+
const entitySet = new Set(args.entities);
|
|
829
|
+
const sourceText = (args.sourceText || "").trim();
|
|
830
|
+
const fallbackConfidence = Math.max(args.schema.minRelationConfidence + 0.05, 0.55);
|
|
831
|
+
const pushRelation = (relation) => {
|
|
832
|
+
const source = normalizeEntityName(relation.source || "", args.schema, args.runtimeAliasLookup);
|
|
833
|
+
const target = normalizeEntityName(relation.target || "", args.schema, args.runtimeAliasLookup);
|
|
834
|
+
const type = normalizeRelationType(relation.type || "", args.schema);
|
|
835
|
+
const isCanonical = isCanonicalRelationType(type, args.schema);
|
|
836
|
+
const relationOrigin = relation.relation_origin || (isCanonical ? "canonical" : "llm_custom");
|
|
837
|
+
const relationDefinition = typeof relation.relation_definition === "string" ? relation.relation_definition.trim() : "";
|
|
838
|
+
const evidenceSpan = typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : "";
|
|
839
|
+
const contextChunkRaw = typeof relation.context_chunk === "string" ? relation.context_chunk.trim() : "";
|
|
840
|
+
const contextChunk = contextChunkRaw || inferContextChunkFromSource(sourceText, [evidenceSpan, source, target].filter(Boolean));
|
|
841
|
+
const confidence = typeof relation.confidence === "number"
|
|
842
|
+
? Math.max(0, Math.min(1, relation.confidence))
|
|
843
|
+
: fallbackConfidence;
|
|
844
|
+
if (!source || !target || source === target)
|
|
845
|
+
return;
|
|
846
|
+
if (!entitySet.has(source) || !entitySet.has(target))
|
|
847
|
+
return;
|
|
848
|
+
if (!type || type === "related_to")
|
|
849
|
+
return;
|
|
850
|
+
if (relationOrigin === "llm_custom" && !relationDefinition)
|
|
851
|
+
return;
|
|
852
|
+
if (!evidenceSpan)
|
|
853
|
+
return;
|
|
854
|
+
const key = `${source}|${type}|${target}`;
|
|
855
|
+
if (dedupe.has(key))
|
|
856
|
+
return;
|
|
857
|
+
dedupe.add(key);
|
|
858
|
+
output.push({
|
|
859
|
+
source,
|
|
860
|
+
target,
|
|
861
|
+
type,
|
|
862
|
+
relation_origin: relationOrigin,
|
|
863
|
+
relation_definition: relationDefinition || undefined,
|
|
864
|
+
mapping_hint: typeof relation.mapping_hint === "string" ? relation.mapping_hint.trim() || undefined : undefined,
|
|
865
|
+
evidence_span: evidenceSpan,
|
|
866
|
+
context_chunk: contextChunk,
|
|
867
|
+
confidence,
|
|
868
|
+
});
|
|
869
|
+
};
|
|
870
|
+
for (const relation of args.relations) {
|
|
871
|
+
const sourceRaw = (relation.source || "").trim();
|
|
872
|
+
const targetRaw = (relation.target || "").trim();
|
|
873
|
+
const evidence = (typeof relation.evidence_span === "string" && relation.evidence_span.trim())
|
|
874
|
+
|| inferEvidenceSpanFromSource(sourceText, [sourceRaw, targetRaw])
|
|
875
|
+
|| "";
|
|
876
|
+
pushRelation({
|
|
877
|
+
source: sourceRaw,
|
|
878
|
+
target: targetRaw,
|
|
879
|
+
type: relation.type || "",
|
|
880
|
+
relation_origin: relation.relation_origin,
|
|
881
|
+
relation_definition: relation.relation_definition,
|
|
882
|
+
mapping_hint: relation.mapping_hint,
|
|
883
|
+
evidence_span: evidence,
|
|
884
|
+
context_chunk: inferContextChunkFromSource(sourceText, [evidence, sourceRaw, targetRaw].filter(Boolean)),
|
|
885
|
+
confidence: typeof relation.confidence === "number" ? relation.confidence : fallbackConfidence,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
if (output.length === 0) {
|
|
889
|
+
const resources = args.entities.filter(entity => {
|
|
890
|
+
const type = (args.entityTypes[entity] || "").trim();
|
|
891
|
+
return type === "Resource" || type === "Document";
|
|
892
|
+
});
|
|
893
|
+
const anchors = args.entities.filter(entity => !resources.includes(entity) && (args.entityTypes[entity] || "").trim() !== "Date");
|
|
894
|
+
const anchor = anchors[0];
|
|
895
|
+
if (anchor) {
|
|
896
|
+
for (const resource of resources.slice(0, 3)) {
|
|
897
|
+
const evidence = inferEvidenceSpanFromSource(sourceText, [resource, anchor]) || "";
|
|
898
|
+
pushRelation({
|
|
899
|
+
source: anchor,
|
|
900
|
+
target: resource,
|
|
901
|
+
type: "references",
|
|
902
|
+
evidence_span: evidence,
|
|
903
|
+
context_chunk: inferContextChunkFromSource(sourceText, [evidence, anchor, resource].filter(Boolean)),
|
|
904
|
+
confidence: fallbackConfidence,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (output.length === 0) {
|
|
910
|
+
const nonDateEntities = args.entities.filter(entity => (args.entityTypes[entity] || "").trim() !== "Date");
|
|
911
|
+
if (nonDateEntities.length >= 2) {
|
|
912
|
+
const source = nonDateEntities[0];
|
|
913
|
+
for (const target of nonDateEntities.slice(1)) {
|
|
914
|
+
const evidence = inferEvidenceSpanFromSource(sourceText, [source, target]) || "";
|
|
915
|
+
pushRelation({
|
|
916
|
+
source,
|
|
917
|
+
target,
|
|
918
|
+
type: "co_occurs_with",
|
|
919
|
+
relation_origin: "llm_custom",
|
|
920
|
+
relation_definition: "Source and target are explicitly co-mentioned within the same source chunk.",
|
|
921
|
+
mapping_hint: "references",
|
|
922
|
+
evidence_span: evidence,
|
|
923
|
+
context_chunk: inferContextChunkFromSource(sourceText, [evidence, source, target].filter(Boolean)),
|
|
924
|
+
confidence: fallbackConfidence,
|
|
925
|
+
});
|
|
926
|
+
if (output.length > 0) {
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return output;
|
|
933
|
+
}
|
|
934
|
+
function buildCanonicalId(args) {
|
|
935
|
+
const entities = (args.entities || []).map(item => item.trim().toLowerCase()).filter(Boolean).sort();
|
|
936
|
+
const relations = (args.relations || [])
|
|
937
|
+
.map(item => `${item.source.trim().toLowerCase()}|${item.type.trim().toLowerCase()}|${item.target.trim().toLowerCase()}`)
|
|
938
|
+
.sort();
|
|
939
|
+
const payload = JSON.stringify({
|
|
940
|
+
eventType: args.eventType.trim().toLowerCase(),
|
|
941
|
+
summary: args.summary.trim().toLowerCase(),
|
|
942
|
+
entities,
|
|
943
|
+
relations,
|
|
944
|
+
outcome: (args.outcome || "").trim().toLowerCase(),
|
|
945
|
+
});
|
|
946
|
+
return `can_${crypto.createHash("sha1").update(payload).digest("hex").slice(0, 20)}`;
|
|
947
|
+
}
|
|
948
|
+
function validateRelations(args) {
|
|
949
|
+
const mode = args.qualityMode || "warn";
|
|
950
|
+
const accepted = [];
|
|
951
|
+
const rejected = [];
|
|
952
|
+
const warnings = [];
|
|
953
|
+
const runtimeAliasLookup = args.runtimeAliasLookup || new Map();
|
|
954
|
+
const normalizedSourceText = (args.sourceText || "").trim().replace(/\s+/g, " ");
|
|
955
|
+
const entitySet = new Set(args.entities
|
|
956
|
+
.map(item => normalizeEntityName(item, args.schema, runtimeAliasLookup))
|
|
957
|
+
.filter(Boolean));
|
|
958
|
+
const rules = toKeyedRules(args.schema.relationRules);
|
|
959
|
+
const typeMap = {};
|
|
960
|
+
for (const [name, type] of Object.entries(args.entityTypes || {})) {
|
|
961
|
+
if (typeof name === "string" && typeof type === "string" && name.trim() && type.trim()) {
|
|
962
|
+
const normalizedName = normalizeEntityName(name, args.schema, runtimeAliasLookup);
|
|
963
|
+
if (normalizedName) {
|
|
964
|
+
typeMap[normalizedName] = type.trim();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
for (const relation of args.relations) {
|
|
969
|
+
const source = normalizeEntityName(relation.source || "", args.schema, runtimeAliasLookup);
|
|
970
|
+
const target = normalizeEntityName(relation.target || "", args.schema, runtimeAliasLookup);
|
|
971
|
+
const type = normalizeRelationType(relation.type, args.schema);
|
|
972
|
+
const typeIsCanonical = isCanonicalRelationType(type, args.schema);
|
|
973
|
+
const relationOriginRaw = typeof relation.relation_origin === "string" ? relation.relation_origin.trim() : "";
|
|
974
|
+
const relationOrigin = relationOriginRaw === "canonical" || relationOriginRaw === "llm_custom"
|
|
975
|
+
? relationOriginRaw
|
|
976
|
+
: (typeIsCanonical ? "canonical" : "llm_custom");
|
|
977
|
+
const relationDefinition = typeof relation.relation_definition === "string" ? relation.relation_definition.trim() : "";
|
|
978
|
+
const mappingHint = typeof relation.mapping_hint === "string" ? relation.mapping_hint.trim() : "";
|
|
979
|
+
const confidence = typeof relation.confidence === "number"
|
|
980
|
+
? Math.max(0, Math.min(1, relation.confidence))
|
|
981
|
+
: undefined;
|
|
982
|
+
const evidenceSpan = typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : "";
|
|
983
|
+
const contextChunkRaw = typeof relation.context_chunk === "string" ? relation.context_chunk.trim() : "";
|
|
984
|
+
const contextChunk = contextChunkRaw || inferContextChunkFromSource(normalizedSourceText, [evidenceSpan, source, target].filter(Boolean));
|
|
985
|
+
const normalized = {
|
|
986
|
+
source,
|
|
987
|
+
target,
|
|
988
|
+
type,
|
|
989
|
+
relation_origin: relationOrigin,
|
|
990
|
+
relation_definition: relationDefinition || undefined,
|
|
991
|
+
mapping_hint: mappingHint || undefined,
|
|
992
|
+
confidence,
|
|
993
|
+
evidence_span: evidenceSpan || undefined,
|
|
994
|
+
context_chunk: contextChunk || undefined,
|
|
995
|
+
};
|
|
996
|
+
if (!source || !target) {
|
|
997
|
+
rejected.push({ reason: "empty_edge", relation: normalized });
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (!type) {
|
|
1001
|
+
rejected.push({ reason: "invalid_relation_type", relation: normalized });
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (type === "related_to") {
|
|
1005
|
+
rejected.push({ reason: "related_to_detected", relation: normalized });
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (relationOrigin === "canonical" && !typeIsCanonical) {
|
|
1009
|
+
rejected.push({ reason: "relation_origin_mismatch", relation: normalized });
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (relationOrigin === "llm_custom" && typeIsCanonical) {
|
|
1013
|
+
rejected.push({ reason: "relation_origin_mismatch", relation: normalized });
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (relationOrigin === "llm_custom" && !relationDefinition) {
|
|
1017
|
+
rejected.push({ reason: "llm_custom_missing_definition", relation: normalized });
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (!entitySet.has(source) || !entitySet.has(target)) {
|
|
1021
|
+
rejected.push({ reason: "edge_entity_missing", relation: normalized });
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const rule = typeIsCanonical ? rules.get(type) : undefined;
|
|
1025
|
+
if (source === target && !(rule?.allowSelfLoop ?? false)) {
|
|
1026
|
+
rejected.push({ reason: "self_loop_blocked", relation: normalized });
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (rule) {
|
|
1030
|
+
const fromType = (typeMap[source] || args.schema.defaultEntityType).toLowerCase();
|
|
1031
|
+
const toType = (typeMap[target] || args.schema.defaultEntityType).toLowerCase();
|
|
1032
|
+
const allowedFrom = new Set(rule.fromTypes.map(item => item.toLowerCase()));
|
|
1033
|
+
const allowedTo = new Set(rule.toTypes.map(item => item.toLowerCase()));
|
|
1034
|
+
if (allowedFrom.size > 0 && !allowedFrom.has(fromType)) {
|
|
1035
|
+
rejected.push({ reason: "from_type_invalid", relation: normalized });
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (allowedTo.size > 0 && !allowedTo.has(toType)) {
|
|
1039
|
+
rejected.push({ reason: "to_type_invalid", relation: normalized });
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof confidence === "number" && confidence < args.schema.minRelationConfidence) {
|
|
1044
|
+
rejected.push({ reason: "low_relation_confidence", relation: normalized });
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof confidence !== "number") {
|
|
1048
|
+
rejected.push({ reason: "missing_relation_confidence", relation: normalized });
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (args.schema.evidenceSpanRequired && !evidenceSpan) {
|
|
1052
|
+
rejected.push({ reason: "missing_evidence_span", relation: normalized });
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (mode !== "off" && args.schema.evidenceSpanRequired && args.sourceText) {
|
|
1056
|
+
if (evidenceSpan && !tokenizeForMatch(args.sourceText).includes(tokenizeForMatch(evidenceSpan))) {
|
|
1057
|
+
if (mode === "strict") {
|
|
1058
|
+
rejected.push({ reason: "evidence_span_not_in_source", relation: normalized });
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
warnings.push({ reason: "evidence_span_not_in_source", relation: normalized });
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (mode !== "off") {
|
|
1065
|
+
if (!contextChunk) {
|
|
1066
|
+
warnings.push({ reason: "missing_context_chunk", relation: normalized });
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
const length = contextChunk.length;
|
|
1070
|
+
if (length < 50 || length > 120) {
|
|
1071
|
+
warnings.push({ reason: "context_chunk_length_out_of_range", relation: normalized });
|
|
1072
|
+
}
|
|
1073
|
+
if (normalizedSourceText && !normalizedSourceText.includes(contextChunk)) {
|
|
1074
|
+
warnings.push({ reason: "context_chunk_not_in_source", relation: normalized });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (mode !== "off" && args.schema.endpointMentionRequired && args.sourceText) {
|
|
1079
|
+
const sourceHit = entityMentionedInText(source, args.sourceText, args.schema);
|
|
1080
|
+
const targetHit = entityMentionedInText(target, args.sourceText, args.schema);
|
|
1081
|
+
if (!sourceHit || !targetHit) {
|
|
1082
|
+
if (mode === "strict") {
|
|
1083
|
+
rejected.push({ reason: "endpoint_not_in_source_text", relation: normalized });
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
warnings.push({ reason: "endpoint_not_in_source_text", relation: normalized });
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
accepted.push(normalized);
|
|
1090
|
+
}
|
|
1091
|
+
return { accepted, rejected, warnings };
|
|
1092
|
+
}
|
|
1093
|
+
function normalizeEntityType(raw, schema) {
|
|
1094
|
+
const value = raw.trim();
|
|
1095
|
+
const entityTypes = new Set(schema.entityTypes.map(item => item));
|
|
1096
|
+
if (entityTypes.has(value)) {
|
|
1097
|
+
return value;
|
|
1098
|
+
}
|
|
1099
|
+
return schema.defaultEntityType;
|
|
1100
|
+
}
|
|
1101
|
+
function validateGraphPayload(args) {
|
|
1102
|
+
const sourceEventId = (args.sourceEventId || "").trim();
|
|
1103
|
+
if (!sourceEventId) {
|
|
1104
|
+
return { valid: false, reason: "source_event_id_empty" };
|
|
1105
|
+
}
|
|
1106
|
+
const summary = typeof args.summary === "string" ? args.summary.trim() : "";
|
|
1107
|
+
if (!summary) {
|
|
1108
|
+
return { valid: false, reason: "missing_summary" };
|
|
1109
|
+
}
|
|
1110
|
+
const sourceTextNav = normalizeSourceTextNav({
|
|
1111
|
+
sourceTextNav: args.source_text_nav,
|
|
1112
|
+
sourceLayer: args.sourceLayer,
|
|
1113
|
+
sourceEventId,
|
|
1114
|
+
archiveEventId: args.archiveEventId,
|
|
1115
|
+
sessionId: args.sessionId,
|
|
1116
|
+
sourceFile: args.sourceFile,
|
|
1117
|
+
});
|
|
1118
|
+
if (!sourceTextNav) {
|
|
1119
|
+
return { valid: false, reason: "fulltext_navigation_missing" };
|
|
1120
|
+
}
|
|
1121
|
+
const baseWarnings = [];
|
|
1122
|
+
const runtimeAliasLookup = buildRuntimeAliasLookup(args.sourceText);
|
|
1123
|
+
const normalizedInputEntities = Array.isArray(args.entities)
|
|
1124
|
+
? args.entities
|
|
1125
|
+
.map(item => normalizeEntityName(typeof item === "string" ? item : "", args.schema, runtimeAliasLookup))
|
|
1126
|
+
.filter(Boolean)
|
|
1127
|
+
: [];
|
|
1128
|
+
const relationEndpoints = collectEntitiesFromRelations(Array.isArray(args.relations) ? args.relations : [], args.schema, runtimeAliasLookup);
|
|
1129
|
+
const resourceEntities = extractResourceReferences(args.sourceText)
|
|
1130
|
+
.map(item => normalizeEntityName(item, args.schema, runtimeAliasLookup))
|
|
1131
|
+
.filter(Boolean);
|
|
1132
|
+
const dedupedEntities = [...new Set([...normalizedInputEntities, ...relationEndpoints, ...resourceEntities])];
|
|
1133
|
+
const entities = dedupedEntities.filter(entity => !isGenericEntityName(entity));
|
|
1134
|
+
if (entities.length !== dedupedEntities.length) {
|
|
1135
|
+
baseWarnings.push("generic_entity_rejected");
|
|
1136
|
+
}
|
|
1137
|
+
if (entities.length === 0) {
|
|
1138
|
+
return { valid: false, reason: "entities_empty" };
|
|
1139
|
+
}
|
|
1140
|
+
if (missingEntitiesInSummary({ summary, entities, schema: args.schema, runtimeAliasLookup }).length > 0) {
|
|
1141
|
+
return { valid: false, reason: "summary_missing_entities" };
|
|
1142
|
+
}
|
|
1143
|
+
const entityTypes = args.entity_types || {};
|
|
1144
|
+
const validEntityTypes = new Set(args.schema.entityTypes);
|
|
1145
|
+
const normalizedEntityTypes = {};
|
|
1146
|
+
for (const [nameRaw, typeRaw] of Object.entries(entityTypes)) {
|
|
1147
|
+
if (typeof typeRaw !== "string")
|
|
1148
|
+
continue;
|
|
1149
|
+
const normalizedName = normalizeEntityName(nameRaw.trim(), args.schema, runtimeAliasLookup);
|
|
1150
|
+
if (!normalizedName)
|
|
1151
|
+
continue;
|
|
1152
|
+
normalizedEntityTypes[normalizedName] = typeRaw.trim();
|
|
1153
|
+
}
|
|
1154
|
+
for (const entity of entities) {
|
|
1155
|
+
const providedType = normalizedEntityTypes[entity];
|
|
1156
|
+
if (providedType && validEntityTypes.has(providedType)) {
|
|
1157
|
+
normalizedEntityTypes[entity] = providedType;
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
normalizedEntityTypes[entity] = inferEntityTypeFromName(entity, args.schema);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const relationValidation = validateRelations({
|
|
1164
|
+
relations: Array.isArray(args.relations) ? args.relations : [],
|
|
1165
|
+
entities,
|
|
1166
|
+
entityTypes: normalizedEntityTypes,
|
|
1167
|
+
schema: args.schema,
|
|
1168
|
+
sourceText: args.sourceText,
|
|
1169
|
+
qualityMode: args.qualityMode,
|
|
1170
|
+
runtimeAliasLookup,
|
|
1171
|
+
});
|
|
1172
|
+
let acceptedRelations = relationValidation.accepted;
|
|
1173
|
+
const warnings = [...baseWarnings, ...relationValidation.warnings.map(item => item.reason)];
|
|
1174
|
+
if (acceptedRelations.length === 0) {
|
|
1175
|
+
const rejectedReasons = new Set(relationValidation.rejected.map(item => item.reason));
|
|
1176
|
+
if (!shouldRetryWithFallbackRelations(rejectedReasons)) {
|
|
1177
|
+
return { valid: false, reason: "relations_empty_or_invalid" };
|
|
1178
|
+
}
|
|
1179
|
+
const fallbackRelations = buildFallbackRelations({
|
|
1180
|
+
entities,
|
|
1181
|
+
entityTypes: normalizedEntityTypes,
|
|
1182
|
+
relations: Array.isArray(args.relations) ? args.relations : [],
|
|
1183
|
+
sourceText: args.sourceText,
|
|
1184
|
+
schema: args.schema,
|
|
1185
|
+
runtimeAliasLookup,
|
|
1186
|
+
});
|
|
1187
|
+
if (fallbackRelations.length > 0) {
|
|
1188
|
+
const fallbackValidation = validateRelations({
|
|
1189
|
+
relations: fallbackRelations,
|
|
1190
|
+
entities,
|
|
1191
|
+
entityTypes: normalizedEntityTypes,
|
|
1192
|
+
schema: args.schema,
|
|
1193
|
+
sourceText: args.sourceText,
|
|
1194
|
+
qualityMode: args.qualityMode,
|
|
1195
|
+
runtimeAliasLookup,
|
|
1196
|
+
});
|
|
1197
|
+
if (fallbackValidation.accepted.length > 0) {
|
|
1198
|
+
acceptedRelations = fallbackValidation.accepted;
|
|
1199
|
+
warnings.push("fallback_relations_applied");
|
|
1200
|
+
warnings.push(...fallbackValidation.warnings.map(item => item.reason));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (acceptedRelations.length === 0) {
|
|
1205
|
+
return { valid: false, reason: "relations_empty_or_invalid" };
|
|
1206
|
+
}
|
|
1207
|
+
const id = `gph_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
1208
|
+
return {
|
|
1209
|
+
valid: true,
|
|
1210
|
+
warnings: [...new Set(warnings)],
|
|
1211
|
+
normalized: {
|
|
1212
|
+
id,
|
|
1213
|
+
summary,
|
|
1214
|
+
source_text_nav: sourceTextNav,
|
|
1215
|
+
source_event_id: args.sourceEventId.trim(),
|
|
1216
|
+
source_layer: args.sourceLayer,
|
|
1217
|
+
archive_event_id: typeof args.archiveEventId === "string" && args.archiveEventId.trim()
|
|
1218
|
+
? args.archiveEventId.trim()
|
|
1219
|
+
: undefined,
|
|
1220
|
+
session_id: args.sessionId,
|
|
1221
|
+
source_file: typeof args.sourceFile === "string" && args.sourceFile.trim()
|
|
1222
|
+
? args.sourceFile.trim()
|
|
1223
|
+
: undefined,
|
|
1224
|
+
timestamp: new Date().toISOString(),
|
|
1225
|
+
entities,
|
|
1226
|
+
entity_types: normalizedEntityTypes,
|
|
1227
|
+
relations: acceptedRelations,
|
|
1228
|
+
gate_source: args.gateSource,
|
|
1229
|
+
event_type: typeof args.eventType === "string" && args.eventType.trim()
|
|
1230
|
+
? normalizeEventType(args.eventType, args.schema)
|
|
1231
|
+
: undefined,
|
|
1232
|
+
schema_version: "1.0.0",
|
|
1233
|
+
confidence: args.confidence,
|
|
1234
|
+
},
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
//# sourceMappingURL=ontology.js.map
|