@cc-soul/openclaw 1.0.0

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.
@@ -0,0 +1,207 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { createHash } from "crypto";
4
+ import { SYNC_CONFIG_PATH, saveJson } from "./persistence.ts";
5
+ import { memoryState, addMemory } from "./memory.ts";
6
+ import { evalMetrics } from "./quality.ts";
7
+ import { getSyncConfig, getInstanceId } from "./sync.ts";
8
+ const PII_PATTERNS = [
9
+ /\b1[3-9]\d{9}\b/g,
10
+ // Chinese phone numbers
11
+ /\b\d{17}[\dXx]\b/g,
12
+ // Chinese ID card
13
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
14
+ // Email
15
+ /\b(?:sk-|api[_-]?key|token|secret|password)[=:]\s*\S+/gi,
16
+ // API keys/secrets
17
+ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
18
+ // Credit card numbers
19
+ /\b(?:ssh-rsa|ssh-ed25519)\s+\S+/g
20
+ // SSH keys
21
+ ];
22
+ function stripPII(text) {
23
+ let clean = text;
24
+ for (const pattern of PII_PATTERNS) {
25
+ clean = clean.replace(pattern, "[REDACTED]");
26
+ }
27
+ return clean;
28
+ }
29
+ __name(stripPII, "stripPII");
30
+ function hasPII(text) {
31
+ return PII_PATTERNS.some((p) => {
32
+ p.lastIndex = 0;
33
+ return p.test(text);
34
+ });
35
+ }
36
+ __name(hasPII, "hasPII");
37
+ function contentHash(content) {
38
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
39
+ }
40
+ __name(contentHash, "contentHash");
41
+ let autoRegisterAttempted = false;
42
+ async function autoRegisterIfNeeded() {
43
+ const config = getSyncConfig();
44
+ if (config.hubApiKey || !config.hubUrl || !config.federationEnabled || autoRegisterAttempted) {
45
+ return Boolean(config.hubApiKey);
46
+ }
47
+ autoRegisterAttempted = true;
48
+ const instanceId = getInstanceId();
49
+ const instanceName = config.instanceName || "unnamed";
50
+ try {
51
+ const resp = await fetch(`${config.hubUrl}/federation/auto-register`, {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({ instanceId, instanceName })
55
+ });
56
+ if (!resp.ok) {
57
+ console.error(`[cc-soul][federation] auto-register failed: ${resp.status}`);
58
+ return false;
59
+ }
60
+ const data = await resp.json();
61
+ if (data.key) {
62
+ config.hubApiKey = data.key;
63
+ saveJson(SYNC_CONFIG_PATH, config);
64
+ console.log(`[cc-soul][federation] auto-registered! key: ${data.key.slice(0, 10)}...`);
65
+ return true;
66
+ }
67
+ } catch (e) {
68
+ console.error(`[cc-soul][federation] auto-register error: ${e.message}`);
69
+ }
70
+ return false;
71
+ }
72
+ __name(autoRegisterIfNeeded, "autoRegisterIfNeeded");
73
+ let lastUpload = 0;
74
+ const UPLOAD_COOLDOWN = 6 * 36e5;
75
+ async function uploadToHub() {
76
+ const config = getSyncConfig();
77
+ if (!config.federationEnabled || !config.hubUrl) return 0;
78
+ if (!config.hubApiKey) {
79
+ const ok = await autoRegisterIfNeeded();
80
+ if (!ok) return 0;
81
+ }
82
+ const now = Date.now();
83
+ if (now - lastUpload < UPLOAD_COOLDOWN) return 0;
84
+ lastUpload = now;
85
+ const shareable = memoryState.memories.filter(
86
+ (m) => (m.scope === "fact" || m.scope === "discovery" || m.scope === "consolidated") && m.visibility === "global" && m.content.length > 10 && !m.content.startsWith("[\u7F51\u7EDC\u77E5\u8BC6") && !hasPII(m.content)
87
+ ).map((m) => ({
88
+ content: stripPII(m.content),
89
+ scope: m.scope,
90
+ tags: m.tags,
91
+ sourceInstance: getInstanceId(),
92
+ sourceQuality: evalMetrics.avgQuality,
93
+ timestamp: m.ts,
94
+ contentHash: contentHash(m.content)
95
+ }));
96
+ if (shareable.length === 0) return 0;
97
+ try {
98
+ const resp = await fetch(`${config.hubUrl}/federation/upload`, {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "Authorization": `Bearer ${config.hubApiKey}`,
103
+ "X-Instance-Id": getInstanceId(),
104
+ "X-Quality-Score": String(evalMetrics.avgQuality)
105
+ },
106
+ body: JSON.stringify({ memories: shareable })
107
+ });
108
+ if (resp.ok) {
109
+ const result = await resp.json();
110
+ console.log(`[cc-soul][federation] uploaded ${shareable.length} memories, accepted: ${result.accepted || "?"}`);
111
+ return result.accepted || shareable.length;
112
+ }
113
+ console.error(`[cc-soul][federation] upload failed: ${resp.status}`);
114
+ } catch (e) {
115
+ console.error(`[cc-soul][federation] upload error: ${e.message}`);
116
+ }
117
+ return 0;
118
+ }
119
+ __name(uploadToHub, "uploadToHub");
120
+ let lastDownload = 0;
121
+ const DOWNLOAD_COOLDOWN = 6 * 36e5;
122
+ async function downloadFromHub() {
123
+ const config = getSyncConfig();
124
+ if (!config.federationEnabled || !config.hubUrl) return 0;
125
+ if (!config.hubApiKey) {
126
+ const ok = await autoRegisterIfNeeded();
127
+ if (!ok) return 0;
128
+ }
129
+ const now = Date.now();
130
+ if (now - lastDownload < DOWNLOAD_COOLDOWN) return 0;
131
+ lastDownload = now;
132
+ try {
133
+ const resp = await fetch(
134
+ `${config.hubUrl}/federation/download?since=${config.lastSync}&instance=${getInstanceId()}`,
135
+ {
136
+ headers: {
137
+ "Authorization": `Bearer ${config.hubApiKey}`,
138
+ "X-Instance-Id": getInstanceId()
139
+ }
140
+ }
141
+ );
142
+ if (!resp.ok) {
143
+ console.error(`[cc-soul][federation] download failed: ${resp.status}`);
144
+ return 0;
145
+ }
146
+ const data = await resp.json();
147
+ if (!data.memories || data.memories.length === 0) return 0;
148
+ const existingHashes = new Set(
149
+ memoryState.memories.map((m) => contentHash(m.content))
150
+ );
151
+ let imported = 0;
152
+ for (const fm of data.memories) {
153
+ if (fm.sourceInstance === getInstanceId()) continue;
154
+ if (existingHashes.has(fm.contentHash)) continue;
155
+ if (fm.sourceQuality < 5) continue;
156
+ if (hasPII(fm.content)) continue;
157
+ const agedays = (Date.now() - fm.timestamp) / 864e5;
158
+ const freshness = Math.max(0.3, 1 - agedays / 365);
159
+ const trustScore = fm.sourceQuality / 10 * freshness;
160
+ if (agedays > 180 && fm.sourceQuality < 7) continue;
161
+ const trustLabel = trustScore > 0.7 ? "\u9AD8\u53EF\u4FE1" : trustScore > 0.4 ? "\u5F85\u9A8C\u8BC1" : "\u4F4E\u53EF\u4FE1";
162
+ const content = `[\u7F51\u7EDC\u77E5\u8BC6|${trustLabel}] ${fm.content}`;
163
+ addMemory(content, fm.scope, void 0, "global");
164
+ existingHashes.add(fm.contentHash);
165
+ imported++;
166
+ }
167
+ console.log(`[cc-soul][federation] downloaded ${data.memories.length}, imported ${imported} (dedup + quality filter)`);
168
+ return imported;
169
+ } catch (e) {
170
+ console.error(`[cc-soul][federation] download error: ${e.message}`);
171
+ }
172
+ return 0;
173
+ }
174
+ __name(downloadFromHub, "downloadFromHub");
175
+ function autoFederate() {
176
+ const config = getSyncConfig();
177
+ if (!config.federationEnabled) return;
178
+ uploadToHub().catch((e) => console.error(`[cc-soul][federation] auto-upload: ${e}`));
179
+ downloadFromHub().catch((e) => console.error(`[cc-soul][federation] auto-download: ${e}`));
180
+ }
181
+ __name(autoFederate, "autoFederate");
182
+ async function reportBadKnowledge(memoryContent) {
183
+ const config = getSyncConfig();
184
+ if (!config.federationEnabled || !config.hubUrl || !config.hubApiKey) return;
185
+ const hash = contentHash(memoryContent.replace(/^\[网络知识[||][^\]]*\]\s*/, ""));
186
+ try {
187
+ await fetch(`${config.hubUrl}/federation/report`, {
188
+ method: "POST",
189
+ headers: {
190
+ "Content-Type": "application/json",
191
+ "Authorization": `Bearer ${config.hubApiKey}`
192
+ },
193
+ body: JSON.stringify({ contentHash: hash })
194
+ });
195
+ console.log(`[cc-soul][federation] reported bad knowledge: ${memoryContent.slice(0, 60)}`);
196
+ } catch (e) {
197
+ console.error(`[cc-soul][federation] report failed: ${e.message}`);
198
+ }
199
+ }
200
+ __name(reportBadKnowledge, "reportBadKnowledge");
201
+ export {
202
+ autoFederate,
203
+ autoRegisterIfNeeded,
204
+ downloadFromHub,
205
+ reportBadKnowledge,
206
+ uploadToHub
207
+ };
@@ -0,0 +1 @@
1
+ import{resolve}from"path";import{DATA_DIR,loadJson,debouncedSave}from"./persistence.ts";const FINGERPRINT_PATH=resolve(DATA_DIR,"fingerprint.json");let fingerprint=loadJson(FINGERPRINT_PATH,{i:0,t:0,p:0,o:0,u:0,F:0,h:0,I:0});function saveFingerprint(){debouncedSave(FINGERPRINT_PATH,fingerprint)}function updateFingerprint(n){if(!n||n.length<10)return;const r=fingerprint.h<50?.2:.05,i=n.length,e=n.split(/[。!?!?.\n]+/).filter(n=>n.trim().length>0),t=e.length>0?i/e.length:i,g=/[??]/.test(n)?1:0,a=n.includes("```")?1:0,f=/[\u{1F300}-\u{1FAFF}]|[😀-🙏]/u.test(n)?1:0,p=(n.match(/我/g)||[]).length/Math.max(1,n.length/100);fingerprint.i=fingerprint.i*(1-r)+i*r,fingerprint.t=fingerprint.t*(1-r)+t*r,fingerprint.p=fingerprint.p*(1-r)+g*r,fingerprint.o=fingerprint.o*(1-r)+a*r,fingerprint.u=fingerprint.u*(1-r)+f*r,fingerprint.F=fingerprint.F*(1-r)+p*r,fingerprint.h++,fingerprint.I=Date.now(),saveFingerprint()}function checkPersonaConsistency(n){if(fingerprint.h<30)return"";const r=[];n.length>3*fingerprint.i&&r.push("回复异常长"),n.length<.15*fingerprint.i&&n.length>5&&r.push("回复异常短"),/作为一个?AI|作为语言模型|作为人工智能|I am an AI|as an AI/i.test(n)&&r.push("人设泄露:提到了AI身份");return(n.match(/可能|也许|或许|不太确定|我不确定|大概/g)||[]).length>5&&r.push("过度犹豫"),0===r.length?"":`[风格偏离] ${r.join(";")}`}function getFingerprintSummary(){return fingerprint.h<20?"":`[回复风格基线] 平均长度${Math.round(fingerprint.i)}字 | 提问率${(100*fingerprint.p).toFixed(0)}% | 代码率${(100*fingerprint.o).toFixed(0)}% | 样本${fingerprint.h}条`}function loadFingerprint(){const n=loadJson(FINGERPRINT_PATH,fingerprint);Object.assign(fingerprint,n)}let cachedDriftWarning="";function getCachedDriftWarning(){const n=cachedDriftWarning;return cachedDriftWarning="",n}function setCachedDriftWarning(n){cachedDriftWarning=n}export{checkPersonaConsistency,getCachedDriftWarning,getFingerprintSummary,loadFingerprint,setCachedDriftWarning,updateFingerprint};
@@ -0,0 +1,199 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { spawnCLI } from "./cli.ts";
4
+ import { memoryState, addMemory } from "./memory.ts";
5
+ const lastResolvedFlows = /* @__PURE__ */ new Map();
6
+ function createEmptyFlow() {
7
+ return {
8
+ topic: "",
9
+ turnCount: 0,
10
+ frustration: 0,
11
+ resolved: false,
12
+ depth: "shallow",
13
+ lastMsgLengths: [],
14
+ topicKeywords: [],
15
+ lastUpdate: Date.now()
16
+ };
17
+ }
18
+ __name(createEmptyFlow, "createEmptyFlow");
19
+ const flows = /* @__PURE__ */ new Map();
20
+ const MAX_FLOWS = 50;
21
+ let onSessionResolved = null;
22
+ function setOnSessionResolved(cb) {
23
+ onSessionResolved = cb;
24
+ }
25
+ __name(setOnSessionResolved, "setOnSessionResolved");
26
+ function updateFlow(userMsg, botResponse, flowKey) {
27
+ let flow = flows.get(flowKey) || createEmptyFlow();
28
+ const msgWords = (userMsg.match(/[\u4e00-\u9fff]{2,}|[a-z]{3,}/gi) || []).map((w) => w.toLowerCase());
29
+ const overlap = flow.topicKeywords.filter((w) => msgWords.includes(w)).length;
30
+ const isSameTopic = overlap >= 2 || flow.turnCount > 0 && overlap >= 1 && userMsg.length < 50;
31
+ if (isSameTopic) {
32
+ flow.turnCount++;
33
+ for (const w of msgWords.slice(0, 5)) {
34
+ if (!flow.topicKeywords.includes(w)) flow.topicKeywords.push(w);
35
+ }
36
+ if (flow.topicKeywords.length > 15) flow.topicKeywords = flow.topicKeywords.slice(-10);
37
+ } else {
38
+ flow = {
39
+ topic: userMsg.slice(0, 50),
40
+ turnCount: 1,
41
+ frustration: 0,
42
+ resolved: false,
43
+ depth: "shallow",
44
+ lastMsgLengths: [],
45
+ topicKeywords: msgWords.slice(0, 8),
46
+ lastUpdate: Date.now()
47
+ };
48
+ }
49
+ flow.lastMsgLengths.push(userMsg.length);
50
+ if (flow.lastMsgLengths.length > 5) flow.lastMsgLengths.shift();
51
+ if (flow.lastMsgLengths.length >= 3) {
52
+ const lengths = flow.lastMsgLengths;
53
+ const trend = lengths[lengths.length - 1] - lengths[0];
54
+ if (trend < -50) flow.frustration = Math.min(1, flow.frustration + 0.2);
55
+ if (userMsg.length < 10 && flow.turnCount > 3) flow.frustration = Math.min(1, flow.frustration + 0.15);
56
+ }
57
+ if (["\u7B97\u4E86", "\u4E0D\u5BF9", "\u8FD8\u662F\u4E0D\u884C", "\u600E\u4E48\u53C8", "\u8BF4\u4E86\u591A\u5C11\u904D"].some((w) => userMsg.includes(w))) {
58
+ flow.frustration = Math.min(1, flow.frustration + 0.3);
59
+ }
60
+ if (["\u641E\u5B9A", "\u53EF\u4EE5\u4E86", "\u597D\u4E86", "\u89E3\u51B3\u4E86", "\u8C22\u8C22", "thanks", "\u6210\u529F\u4E86"].some((w) => userMsg.toLowerCase().includes(w))) {
61
+ flow.resolved = true;
62
+ flow.frustration = Math.max(0, flow.frustration - 0.3);
63
+ if (typeof onSessionResolved === "function") onSessionResolved();
64
+ if (!lastResolvedFlows.has(flowKey)) {
65
+ lastResolvedFlows.set(flowKey, {
66
+ resolvedAt: Date.now(),
67
+ summarized: false,
68
+ topic: flow.topic,
69
+ turnCount: flow.turnCount
70
+ });
71
+ }
72
+ }
73
+ if (flow.turnCount <= 2) flow.depth = "shallow";
74
+ else if (flow.turnCount <= 6 && flow.frustration < 0.5) flow.depth = "deep";
75
+ else if (flow.turnCount > 6 || flow.frustration >= 0.5) flow.depth = "stuck";
76
+ flow.lastUpdate = Date.now();
77
+ flows.set(flowKey, flow);
78
+ if (flows.size > MAX_FLOWS) {
79
+ const oldest = [...flows.entries()].sort((a, b) => a[1].lastUpdate - b[1].lastUpdate)[0];
80
+ if (oldest) flows.delete(oldest[0]);
81
+ }
82
+ return flow;
83
+ }
84
+ __name(updateFlow, "updateFlow");
85
+ function getFlowHints(flowKey) {
86
+ const flow = flows.get(flowKey);
87
+ if (!flow) return [];
88
+ const hints = [];
89
+ if (flow.depth === "stuck") {
90
+ hints.push(`\u5DF2\u7ECF\u8BA8\u8BBA${flow.turnCount}\u8F6E\u4E86\uFF0C\u53EF\u80FD\u9677\u5165\u50F5\u5C40\u3002\u8BD5\u8BD5\u6362\u4E2A\u601D\u8DEF\u6216\u76F4\u63A5\u7ED9\u6700\u7EC8\u65B9\u6848`);
91
+ }
92
+ if (flow.frustration >= 0.6) {
93
+ hints.push("\u7528\u6237\u53EF\u80FD\u8D8A\u6765\u8D8A\u4E0D\u8010\u70E6\u4E86\uFF0C\u7B80\u5316\u56DE\u7B54\uFF0C\u76F4\u63A5\u7ED9\u65B9\u6848");
94
+ }
95
+ if (flow.resolved) {
96
+ hints.push("\u95EE\u9898\u4F3C\u4E4E\u5DF2\u89E3\u51B3\uFF0C\u81EA\u7136\u6536\u5C3E\u5373\u53EF");
97
+ }
98
+ if (flow.turnCount >= 4 && !flow.resolved && flow.frustration < 0.3) {
99
+ hints.push(`\u8BA8\u8BBA\u5DF2${flow.turnCount}\u8F6E\uFF0C\u7528\u6237\u5F88\u6709\u8010\u5FC3\uFF0C\u53EF\u4EE5\u7EE7\u7EED\u6DF1\u5165`);
100
+ }
101
+ return hints;
102
+ }
103
+ __name(getFlowHints, "getFlowHints");
104
+ function getFlowContext(flowKey) {
105
+ const flow = flows.get(flowKey);
106
+ if (!flow || flow.turnCount <= 1) return "";
107
+ return `[\u5BF9\u8BDD\u6D41] \u5F53\u524D\u8BDD\u9898\u5DF2${flow.turnCount}\u8F6E | \u6DF1\u5EA6:${flow.depth} | \u7528\u6237\u8010\u5FC3:${(1 - flow.frustration).toFixed(1)} | ${flow.resolved ? "\u5DF2\u89E3\u51B3" : "\u8FDB\u884C\u4E2D"}`;
108
+ }
109
+ __name(getFlowContext, "getFlowContext");
110
+ function getCurrentFlowDepth() {
111
+ let worst = "shallow";
112
+ for (const flow of flows.values()) {
113
+ if (flow.depth === "stuck") return "stuck";
114
+ if (flow.depth === "deep") worst = "deep";
115
+ }
116
+ return worst;
117
+ }
118
+ __name(getCurrentFlowDepth, "getCurrentFlowDepth");
119
+ function resetFlow(flowKey) {
120
+ if (flowKey) {
121
+ flows.delete(flowKey);
122
+ } else {
123
+ flows.clear();
124
+ }
125
+ }
126
+ __name(resetFlow, "resetFlow");
127
+ function checkSessionEnd(flowKey) {
128
+ const resolved = lastResolvedFlows.get(flowKey);
129
+ if (!resolved) return null;
130
+ if (resolved.summarized) return null;
131
+ if (Date.now() - resolved.resolvedAt > 5 * 60 * 1e3) {
132
+ resolved.summarized = true;
133
+ return { ended: true, topic: resolved.topic, turnCount: resolved.turnCount };
134
+ }
135
+ return null;
136
+ }
137
+ __name(checkSessionEnd, "checkSessionEnd");
138
+ function checkAllSessionEnds() {
139
+ const ended = [];
140
+ for (const [key] of lastResolvedFlows) {
141
+ const result = checkSessionEnd(key);
142
+ if (result) {
143
+ ended.push({ flowKey: key, topic: result.topic, turnCount: result.turnCount });
144
+ }
145
+ }
146
+ return ended;
147
+ }
148
+ __name(checkAllSessionEnds, "checkAllSessionEnds");
149
+ function generateSessionSummary(topic, turnCount, _flowKey) {
150
+ const chatHistory = memoryState.chatHistory;
151
+ const recentHistory = chatHistory.slice(-(turnCount * 2));
152
+ const historyText = recentHistory.map(
153
+ (t) => `\u7528\u6237: ${t.user.slice(0, 100)}
154
+ \u52A9\u624B: ${t.assistant.slice(0, 100)}`
155
+ ).join("\n\n");
156
+ spawnCLI(
157
+ `\u4EE5\u4E0B\u662F\u4E00\u6BB5${turnCount}\u8F6E\u7684\u5BF9\u8BDD\uFF08\u8BDD\u9898: ${topic}\uFF09\u3002\u8BF7\u603B\u7ED3\uFF1A
158
+ 1. \u804A\u4E86\u4EC0\u4E48
159
+ 2. \u7528\u6237\u6EE1\u610F\u5417
160
+ 3. \u6709\u6CA1\u6709\u9057\u7559\u95EE\u9898
161
+ 4. \u503C\u5F97\u8BB0\u4F4F\u7684\u4E8B\u5B9E/\u504F\u597D
162
+
163
+ ${historyText.slice(0, 2e3)}
164
+
165
+ \u683C\u5F0F: {"summary":"\u4E00\u6BB5\u8BDD\u603B\u7ED3","facts":["\u503C\u5F97\u8BB0\u4F4F\u7684\u4E8B\u5B9E"],"satisfied":true/false,"pending":"\u9057\u7559\u95EE\u9898\u6216null"}`,
166
+ (output) => {
167
+ try {
168
+ const m = output.match(/\{[\s\S]*?\}/);
169
+ if (m) {
170
+ const result = JSON.parse(m[0]);
171
+ if (result.summary) {
172
+ addMemory(`[\u4F1A\u8BDD\u603B\u7ED3] ${topic}: ${result.summary}`, "consolidated");
173
+ }
174
+ for (const fact of result.facts || []) {
175
+ addMemory(fact, "fact");
176
+ }
177
+ if (result.pending) {
178
+ addMemory(`[\u9057\u7559\u95EE\u9898] ${result.pending}`, "task");
179
+ }
180
+ console.log(`[cc-soul][session] summarized: ${topic} (${turnCount} turns, satisfied=${result.satisfied})`);
181
+ }
182
+ } catch (e) {
183
+ console.error(`[cc-soul][session] summary parse error: ${e.message}`);
184
+ }
185
+ }
186
+ );
187
+ }
188
+ __name(generateSessionSummary, "generateSessionSummary");
189
+ export {
190
+ checkAllSessionEnds,
191
+ checkSessionEnd,
192
+ generateSessionSummary,
193
+ getCurrentFlowDepth,
194
+ getFlowContext,
195
+ getFlowHints,
196
+ resetFlow,
197
+ setOnSessionResolved,
198
+ updateFlow
199
+ };
@@ -0,0 +1,85 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { GRAPH_PATH, loadJson, debouncedSave } from "./persistence.ts";
4
+ const graphState = {
5
+ entities: [],
6
+ relations: []
7
+ };
8
+ function loadGraph() {
9
+ const data = loadJson(GRAPH_PATH, { entities: [], relations: [] });
10
+ graphState.entities.length = 0;
11
+ graphState.entities.push(...data.entities || []);
12
+ graphState.relations.length = 0;
13
+ graphState.relations.push(...data.relations || []);
14
+ }
15
+ __name(loadGraph, "loadGraph");
16
+ function saveGraph() {
17
+ debouncedSave(GRAPH_PATH, { entities: graphState.entities, relations: graphState.relations });
18
+ }
19
+ __name(saveGraph, "saveGraph");
20
+ function addEntity(name, type, attrs = []) {
21
+ if (!name || name.length < 2) return;
22
+ const existing = graphState.entities.find((e) => e.name === name);
23
+ if (existing) {
24
+ existing.mentions++;
25
+ for (const a of attrs) {
26
+ if (!existing.attrs.includes(a)) existing.attrs.push(a);
27
+ }
28
+ } else {
29
+ graphState.entities.push({ name, type, attrs, firstSeen: Date.now(), mentions: 1 });
30
+ }
31
+ if (graphState.entities.length > 500) {
32
+ graphState.entities.sort((a, b) => b.mentions - a.mentions);
33
+ const trimmed = graphState.entities.slice(0, 400);
34
+ graphState.entities.length = 0;
35
+ graphState.entities.push(...trimmed);
36
+ }
37
+ saveGraph();
38
+ }
39
+ __name(addEntity, "addEntity");
40
+ function addRelation(source, target, type) {
41
+ if (!source || !target) return;
42
+ const exists = graphState.relations.some((r) => r.source === source && r.target === target && r.type === type);
43
+ if (exists) return;
44
+ graphState.relations.push({ source, target, type, ts: Date.now() });
45
+ if (graphState.relations.length > 1e3) {
46
+ const trimmed = graphState.relations.slice(-800);
47
+ graphState.relations.length = 0;
48
+ graphState.relations.push(...trimmed);
49
+ }
50
+ saveGraph();
51
+ }
52
+ __name(addRelation, "addRelation");
53
+ function addEntitiesFromAnalysis(entities) {
54
+ for (const e of entities) {
55
+ if (e.name && e.name.length >= 2) {
56
+ addEntity(e.name, e.type);
57
+ if (e.relation) addRelation(e.name, "\u7528\u6237", e.relation.slice(0, 30));
58
+ }
59
+ }
60
+ }
61
+ __name(addEntitiesFromAnalysis, "addEntitiesFromAnalysis");
62
+ function queryEntityContext(msg) {
63
+ const results = [];
64
+ for (const entity of graphState.entities) {
65
+ if (msg.includes(entity.name)) {
66
+ const rels = graphState.relations.filter((r) => r.source === entity.name || r.target === entity.name);
67
+ if (rels.length > 0) {
68
+ const relStr = rels.map((r) => `${r.source} ${r.type} ${r.target}`).join(", ");
69
+ results.push(`[${entity.type}] ${entity.name}: ${relStr}`);
70
+ } else if (entity.attrs.length > 0) {
71
+ results.push(`[${entity.type}] ${entity.name}: ${entity.attrs.join(", ")}`);
72
+ }
73
+ }
74
+ }
75
+ return results.slice(0, 3);
76
+ }
77
+ __name(queryEntityContext, "queryEntityContext");
78
+ export {
79
+ addEntitiesFromAnalysis,
80
+ addEntity,
81
+ addRelation,
82
+ graphState,
83
+ loadGraph,
84
+ queryEntityContext
85
+ };