@rubytech/create-realagent 1.0.625 → 1.0.626
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/dist/index.js +2 -1
- package/package.json +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +5 -8
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +24 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +66 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +411 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js +80 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js +10 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js.map +1 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +20 -4
- package/payload/platform/templates/agents/public/IDENTITY.md +18 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +18 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +18 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +18 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +18 -0
- package/payload/server/server.js +20 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* graph-prune — shared helpers for the daily graph-hygiene cron and on-demand tools.
|
|
3
|
+
*
|
|
4
|
+
* Three classes of action (per Task 549 scope-in):
|
|
5
|
+
* 1. Autonomous deletes — orphan nodes older than 7d (safety-ceiling gated)
|
|
6
|
+
* 2. Autonomous nulls — string properties containing a deny-list value (safety-ceiling gated, audit entry)
|
|
7
|
+
* 3. Flag-only — exact + near-duplicate detection, contradictions (no auto-merge per user decision 2026-04-19)
|
|
8
|
+
*
|
|
9
|
+
* Conversation-bound :Memory nodes get the same deny-list null treatment plus
|
|
10
|
+
* are the target of the conversation-memory-expunge manual tool.
|
|
11
|
+
*
|
|
12
|
+
* Per the user's "deterministic = shell script" doctrine, the cron is a
|
|
13
|
+
* Node script that runs Cypher queries with no LLM in the per-rule loop.
|
|
14
|
+
* Rule decisions are pure data: canonical keys, deny-list strings, age
|
|
15
|
+
* thresholds. The runPrune function is the same code path the cron and
|
|
16
|
+
* graph-prune-run MCP tool both invoke, so behaviour matches whether the
|
|
17
|
+
* trigger is a 03:00 cron or an operator chat command.
|
|
18
|
+
*
|
|
19
|
+
* Safety ceiling: any single rule that would touch more than 5 % of total
|
|
20
|
+
* account nodes aborts that rule and emits [graph:prune:ceiling-tripped].
|
|
21
|
+
* Other rules in the same run continue; the ceiling protects against a
|
|
22
|
+
* deny-list typo or schema drift nuking legitimate data.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { resolve } from "node:path";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
const DEFAULT_CONFIG = {
|
|
28
|
+
enabled: true,
|
|
29
|
+
runHour: 3,
|
|
30
|
+
orphanAgeDays: 7,
|
|
31
|
+
safetyCeilingPercent: 5,
|
|
32
|
+
};
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Config + deny-list paths
|
|
35
|
+
//
|
|
36
|
+
// Both files live under ${configDir} (e.g. ~/.maxy/) so they are stable across
|
|
37
|
+
// upgrades and shared across accounts on the device. Per-account override via
|
|
38
|
+
// account-scoped subdirs is left for a future task — Phase 0 is single-account
|
|
39
|
+
// in practice and the deny-list is a coarse "this string is poison" instrument.
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export function configPath(configDir) {
|
|
42
|
+
return resolve(configDir, "graph-prune.config.json");
|
|
43
|
+
}
|
|
44
|
+
export function denylistPath(configDir) {
|
|
45
|
+
return resolve(configDir, "graph-prune-denylist.json");
|
|
46
|
+
}
|
|
47
|
+
export function loadConfig(configDir) {
|
|
48
|
+
const path = configPath(configDir);
|
|
49
|
+
if (!existsSync(path))
|
|
50
|
+
return { ...DEFAULT_CONFIG };
|
|
51
|
+
try {
|
|
52
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
53
|
+
return { ...DEFAULT_CONFIG, ...raw };
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Malformed config falls back to defaults but the failure is loud.
|
|
57
|
+
process.stderr.write(`[graph-prune:config] failed to parse ${path}: ${err instanceof Error ? err.message : String(err)} — using defaults\n`);
|
|
58
|
+
return { ...DEFAULT_CONFIG };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function saveConfig(configDir, config) {
|
|
62
|
+
mkdirSync(configDir, { recursive: true });
|
|
63
|
+
writeFileSync(configPath(configDir), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
64
|
+
}
|
|
65
|
+
export function loadDenylist(configDir) {
|
|
66
|
+
const path = denylistPath(configDir);
|
|
67
|
+
if (!existsSync(path)) {
|
|
68
|
+
return { values: [], updatedAt: new Date(0).toISOString() };
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
72
|
+
return {
|
|
73
|
+
values: Array.isArray(raw.values) ? raw.values.filter((v) => typeof v === "string") : [],
|
|
74
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date(0).toISOString(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
process.stderr.write(`[graph-prune:denylist] failed to parse ${path}: ${err instanceof Error ? err.message : String(err)} — treating as empty\n`);
|
|
79
|
+
return { values: [], updatedAt: new Date(0).toISOString() };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function saveDenylist(configDir, list) {
|
|
83
|
+
mkdirSync(configDir, { recursive: true });
|
|
84
|
+
writeFileSync(denylistPath(configDir), JSON.stringify(list, null, 2) + "\n", "utf-8");
|
|
85
|
+
}
|
|
86
|
+
export function resolveConfigDir() {
|
|
87
|
+
const platformRoot = process.env.PLATFORM_ROOT;
|
|
88
|
+
if (!platformRoot) {
|
|
89
|
+
throw new Error("PLATFORM_ROOT environment variable is required");
|
|
90
|
+
}
|
|
91
|
+
const brandPath = resolve(platformRoot, "config", "brand.json");
|
|
92
|
+
if (!existsSync(brandPath)) {
|
|
93
|
+
throw new Error(`brand.json not found at ${brandPath} — platform not properly installed`);
|
|
94
|
+
}
|
|
95
|
+
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
96
|
+
if (!brand.configDir) {
|
|
97
|
+
throw new Error(`brand.json at ${brandPath} is missing the configDir field`);
|
|
98
|
+
}
|
|
99
|
+
return resolve(homedir(), brand.configDir);
|
|
100
|
+
}
|
|
101
|
+
const CANONICAL_KEYS = [
|
|
102
|
+
{ label: "Person", keyTuples: [["email"], ["phone"]] },
|
|
103
|
+
{ label: "LocalBusiness", keyTuples: [["name"]] },
|
|
104
|
+
{ label: "Tunnel", keyTuples: [["tunnelId"]] },
|
|
105
|
+
{ label: "Account", keyTuples: [["accountId"]] },
|
|
106
|
+
{ label: "Conversation", keyTuples: [["conversationId"]] },
|
|
107
|
+
];
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Helpers
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
function logLine(line) {
|
|
112
|
+
process.stderr.write(line.endsWith("\n") ? line : line + "\n");
|
|
113
|
+
}
|
|
114
|
+
function tripsCeiling(wouldAffect, totalNodes, ceilingPercent) {
|
|
115
|
+
if (totalNodes === 0)
|
|
116
|
+
return false;
|
|
117
|
+
return (wouldAffect / totalNodes) * 100 > ceilingPercent;
|
|
118
|
+
}
|
|
119
|
+
export async function runPrune(driver, accountId, config, denylist, opts = {}) {
|
|
120
|
+
const dryRun = opts.dryRun ?? false;
|
|
121
|
+
const t0 = Date.now();
|
|
122
|
+
const summary = {
|
|
123
|
+
accountId,
|
|
124
|
+
totalNodes: 0,
|
|
125
|
+
orphansDeleted: 0,
|
|
126
|
+
denylistNulled: 0,
|
|
127
|
+
conversationMemoryNulled: 0,
|
|
128
|
+
candidateMergesFlagged: 0,
|
|
129
|
+
contradictionsFlagged: 0,
|
|
130
|
+
ceilingTripped: [],
|
|
131
|
+
elapsedMs: 0,
|
|
132
|
+
dryRun,
|
|
133
|
+
};
|
|
134
|
+
const session = driver.session();
|
|
135
|
+
try {
|
|
136
|
+
// Total node count for safety ceiling.
|
|
137
|
+
const totalRes = await session.run("MATCH (n) WHERE n.accountId = $accountId RETURN count(n) AS c", { accountId });
|
|
138
|
+
summary.totalNodes = Number(totalRes.records[0]?.get("c") ?? 0);
|
|
139
|
+
logLine(`[graph:prune:start] account=${accountId} totalNodes=${summary.totalNodes} dryRun=${dryRun} denylistSize=${denylist.values.length}`);
|
|
140
|
+
if (summary.totalNodes === 0) {
|
|
141
|
+
logLine(`[graph:prune:summary] account=${accountId} deleted=0 nulled=0 convNulled=0 flagged=0 contradictions=0 elapsed=${Date.now() - t0}ms (empty graph)`);
|
|
142
|
+
summary.elapsedMs = Date.now() - t0;
|
|
143
|
+
return summary;
|
|
144
|
+
}
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
// Rule 1: orphan delete (no relations, age > config.orphanAgeDays)
|
|
147
|
+
// Excludes UserProfile, Account, LocalBusiness — those legitimately exist
|
|
148
|
+
// without relations on a fresh install.
|
|
149
|
+
// -----------------------------------------------------------------------
|
|
150
|
+
const orphanCutoff = new Date(Date.now() - config.orphanAgeDays * 86_400_000).toISOString();
|
|
151
|
+
const orphanCountRes = await session.run(`MATCH (n)
|
|
152
|
+
WHERE n.accountId = $accountId
|
|
153
|
+
AND NOT (n)--()
|
|
154
|
+
AND coalesce(n.createdAt, n.updatedAt) < $cutoff
|
|
155
|
+
AND NOT (n:UserProfile OR n:Account OR n:LocalBusiness)
|
|
156
|
+
RETURN count(n) AS c`, { accountId, cutoff: orphanCutoff });
|
|
157
|
+
const orphanCount = Number(orphanCountRes.records[0]?.get("c") ?? 0);
|
|
158
|
+
if (orphanCount > 0) {
|
|
159
|
+
if (tripsCeiling(orphanCount, summary.totalNodes, config.safetyCeilingPercent)) {
|
|
160
|
+
logLine(`[graph:prune:ceiling-tripped] rule=orphan-delete wouldAffect=${orphanCount} totalNodes=${summary.totalNodes} ceiling=${config.safetyCeilingPercent}%`);
|
|
161
|
+
summary.ceilingTripped.push("orphan-delete");
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
logLine(`[graph:prune:rule-start] rule=orphan-delete count=${orphanCount}`);
|
|
165
|
+
if (!dryRun) {
|
|
166
|
+
// Audit each delete before the bulk DETACH DELETE — without per-node
|
|
167
|
+
// ids in the log, recovery from a misconfiguration is impossible.
|
|
168
|
+
const idsRes = await session.run(`MATCH (n)
|
|
169
|
+
WHERE n.accountId = $accountId
|
|
170
|
+
AND NOT (n)--()
|
|
171
|
+
AND coalesce(n.createdAt, n.updatedAt) < $cutoff
|
|
172
|
+
AND NOT (n:UserProfile OR n:Account OR n:LocalBusiness)
|
|
173
|
+
RETURN id(n) AS nid, labels(n) AS labels LIMIT 1000`, { accountId, cutoff: orphanCutoff });
|
|
174
|
+
for (const r of idsRes.records) {
|
|
175
|
+
logLine(`[graph:prune:action] rule=orphan-delete op=delete nodeId=${r.get("nid")} labels=${r.get("labels").join(",")}`);
|
|
176
|
+
}
|
|
177
|
+
await session.run(`MATCH (n)
|
|
178
|
+
WHERE n.accountId = $accountId
|
|
179
|
+
AND NOT (n)--()
|
|
180
|
+
AND coalesce(n.createdAt, n.updatedAt) < $cutoff
|
|
181
|
+
AND NOT (n:UserProfile OR n:Account OR n:LocalBusiness)
|
|
182
|
+
DELETE n`, { accountId, cutoff: orphanCutoff });
|
|
183
|
+
}
|
|
184
|
+
summary.orphansDeleted = orphanCount;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
// Rule 2: deny-list null
|
|
189
|
+
//
|
|
190
|
+
// Scan every node for string properties whose value contains any deny-list
|
|
191
|
+
// entry (substring match). Null those properties, append an audit entry to
|
|
192
|
+
// a per-node `_denylistAudit` JSON-encoded property. Excludes the audit
|
|
193
|
+
// property itself from the scan to avoid runaway re-matching on stored
|
|
194
|
+
// history.
|
|
195
|
+
// -----------------------------------------------------------------------
|
|
196
|
+
if (denylist.values.length > 0) {
|
|
197
|
+
const matchingRes = await session.run(`MATCH (n)
|
|
198
|
+
WHERE n.accountId = $accountId
|
|
199
|
+
WITH n, [k IN keys(n) WHERE k <> '_denylistAudit'
|
|
200
|
+
AND k <> 'embedding'
|
|
201
|
+
AND apoc.meta.cypher.isType(n[k], 'STRING')
|
|
202
|
+
AND any(deny IN $denylist WHERE n[k] CONTAINS deny)] AS hits
|
|
203
|
+
WHERE size(hits) > 0
|
|
204
|
+
RETURN id(n) AS nid, labels(n) AS labels, hits LIMIT 5000`, { accountId, denylist: denylist.values }).catch(async (apocErr) => {
|
|
205
|
+
// APOC may be unavailable on minimal Neo4j installs; fall back to a
|
|
206
|
+
// pure-Cypher type check that is slower but functional.
|
|
207
|
+
process.stderr.write(`[graph:prune:denylist] APOC unavailable (${apocErr instanceof Error ? apocErr.message : String(apocErr)}); using pure-Cypher fallback\n`);
|
|
208
|
+
return session.run(`MATCH (n)
|
|
209
|
+
WHERE n.accountId = $accountId
|
|
210
|
+
WITH n, [k IN keys(n) WHERE k <> '_denylistAudit'
|
|
211
|
+
AND k <> 'embedding'
|
|
212
|
+
AND any(deny IN $denylist WHERE
|
|
213
|
+
CASE
|
|
214
|
+
WHEN n[k] IS NULL THEN false
|
|
215
|
+
WHEN toString(n[k]) = n[k] THEN n[k] CONTAINS deny
|
|
216
|
+
ELSE false
|
|
217
|
+
END)] AS hits
|
|
218
|
+
WHERE size(hits) > 0
|
|
219
|
+
RETURN id(n) AS nid, labels(n) AS labels, hits LIMIT 5000`, { accountId, denylist: denylist.values });
|
|
220
|
+
});
|
|
221
|
+
const matchCount = matchingRes.records.length;
|
|
222
|
+
if (matchCount > 0) {
|
|
223
|
+
if (tripsCeiling(matchCount, summary.totalNodes, config.safetyCeilingPercent)) {
|
|
224
|
+
logLine(`[graph:prune:ceiling-tripped] rule=denylist-null wouldAffect=${matchCount} totalNodes=${summary.totalNodes} ceiling=${config.safetyCeilingPercent}%`);
|
|
225
|
+
summary.ceilingTripped.push("denylist-null");
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
logLine(`[graph:prune:rule-start] rule=denylist-null nodes=${matchCount}`);
|
|
229
|
+
for (const r of matchingRes.records) {
|
|
230
|
+
const nid = Number(r.get("nid"));
|
|
231
|
+
const labels = r.get("labels").join(",");
|
|
232
|
+
const hits = r.get("hits");
|
|
233
|
+
for (const propKey of hits) {
|
|
234
|
+
logLine(`[graph:prune:action] rule=denylist-null op=null nodeId=${nid} labels=${labels} property=${propKey}`);
|
|
235
|
+
}
|
|
236
|
+
if (!dryRun) {
|
|
237
|
+
const auditEntry = JSON.stringify({
|
|
238
|
+
ts: new Date().toISOString(),
|
|
239
|
+
properties: hits,
|
|
240
|
+
denylistMatches: denylist.values,
|
|
241
|
+
});
|
|
242
|
+
const setClauses = hits.map((k) => `n.\`${k.replace(/`/g, "")}\` = null`).join(", ");
|
|
243
|
+
await session.run(`MATCH (n) WHERE id(n) = $nid
|
|
244
|
+
SET ${setClauses},
|
|
245
|
+
n._denylistAudit = coalesce(n._denylistAudit, '') + $entry + '\n'`, { nid, entry: auditEntry });
|
|
246
|
+
summary.denylistNulled += hits.length;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
summary.denylistNulled += hits.length;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// -----------------------------------------------------------------------
|
|
256
|
+
// Rule 3: conversation-memory deny-list null
|
|
257
|
+
//
|
|
258
|
+
// Pollution surface: :Memory nodes tied to :Conversation. Same scan as
|
|
259
|
+
// rule 2 but scoped to that label so the cost stays bounded even when
|
|
260
|
+
// the deny-list grows.
|
|
261
|
+
// -----------------------------------------------------------------------
|
|
262
|
+
if (denylist.values.length > 0) {
|
|
263
|
+
const memRes = await session.run(`MATCH (m:Memory)-[:PART_OF|:ABOUT]->(c:Conversation)
|
|
264
|
+
WHERE m.accountId = $accountId
|
|
265
|
+
WITH m, [k IN keys(m) WHERE k <> '_denylistAudit'
|
|
266
|
+
AND k <> 'embedding'
|
|
267
|
+
AND any(deny IN $denylist WHERE
|
|
268
|
+
CASE
|
|
269
|
+
WHEN m[k] IS NULL THEN false
|
|
270
|
+
WHEN toString(m[k]) = m[k] THEN m[k] CONTAINS deny
|
|
271
|
+
ELSE false
|
|
272
|
+
END)] AS hits
|
|
273
|
+
WHERE size(hits) > 0
|
|
274
|
+
RETURN id(m) AS nid, hits LIMIT 5000`, { accountId, denylist: denylist.values });
|
|
275
|
+
const memCount = memRes.records.length;
|
|
276
|
+
if (memCount > 0) {
|
|
277
|
+
if (tripsCeiling(memCount, summary.totalNodes, config.safetyCeilingPercent)) {
|
|
278
|
+
logLine(`[graph:prune:ceiling-tripped] rule=conversation-memory-null wouldAffect=${memCount} totalNodes=${summary.totalNodes} ceiling=${config.safetyCeilingPercent}%`);
|
|
279
|
+
summary.ceilingTripped.push("conversation-memory-null");
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
logLine(`[graph:prune:rule-start] rule=conversation-memory-null nodes=${memCount}`);
|
|
283
|
+
for (const r of memRes.records) {
|
|
284
|
+
const nid = Number(r.get("nid"));
|
|
285
|
+
const hits = r.get("hits");
|
|
286
|
+
for (const k of hits) {
|
|
287
|
+
logLine(`[graph:prune:action] rule=conversation-memory-null op=null nodeId=${nid} property=${k}`);
|
|
288
|
+
}
|
|
289
|
+
if (!dryRun) {
|
|
290
|
+
const setClauses = hits.map((k) => `m.\`${k.replace(/`/g, "")}\` = null`).join(", ");
|
|
291
|
+
const auditEntry = JSON.stringify({
|
|
292
|
+
ts: new Date().toISOString(),
|
|
293
|
+
properties: hits,
|
|
294
|
+
denylistMatches: denylist.values,
|
|
295
|
+
});
|
|
296
|
+
await session.run(`MATCH (m) WHERE id(m) = $nid
|
|
297
|
+
SET ${setClauses},
|
|
298
|
+
m._denylistAudit = coalesce(m._denylistAudit, '') + $entry + '\n'`, { nid, entry: auditEntry });
|
|
299
|
+
}
|
|
300
|
+
summary.conversationMemoryNulled += hits.length;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// -----------------------------------------------------------------------
|
|
306
|
+
// Rule 4: candidate-merge flagging (per the 2026-04-19 user decision —
|
|
307
|
+
// never auto-merge; admin agent reviews next turn).
|
|
308
|
+
//
|
|
309
|
+
// For each canonical-key spec, find pairs of nodes sharing the key value.
|
|
310
|
+
// Emit a flag line per pair; stored as :PruneFlag nodes so the
|
|
311
|
+
// review-detector pattern (graph:prune:candidate-merge) can surface them.
|
|
312
|
+
// -----------------------------------------------------------------------
|
|
313
|
+
for (const spec of CANONICAL_KEYS) {
|
|
314
|
+
for (const tuple of spec.keyTuples) {
|
|
315
|
+
const matchProps = tuple.map((k) => `a.\`${k}\` IS NOT NULL AND a.\`${k}\` = b.\`${k}\``).join(" AND ");
|
|
316
|
+
const returnProps = tuple.map((k) => `a.\`${k}\` AS \`${k}\``).join(", ");
|
|
317
|
+
const dupRes = await session.run(`MATCH (a:${spec.label}), (b:${spec.label})
|
|
318
|
+
WHERE a.accountId = $accountId AND b.accountId = $accountId
|
|
319
|
+
AND id(a) < id(b)
|
|
320
|
+
AND ${matchProps}
|
|
321
|
+
RETURN id(a) AS aid, id(b) AS bid, ${returnProps} LIMIT 500`, { accountId });
|
|
322
|
+
for (const r of dupRes.records) {
|
|
323
|
+
const aid = Number(r.get("aid"));
|
|
324
|
+
const bid = Number(r.get("bid"));
|
|
325
|
+
const keyValues = tuple.map((k) => `${k}=${r.get(k)}`).join(",");
|
|
326
|
+
logLine(`[graph:prune:candidate-merge] label=${spec.label} keys=${keyValues} sourceId=${aid} targetId=${bid}`);
|
|
327
|
+
summary.candidateMergesFlagged += 1;
|
|
328
|
+
if (!dryRun) {
|
|
329
|
+
await session.run(`MERGE (f:PruneFlag {accountId: $accountId, kind: 'candidate-merge', sourceId: $aid, targetId: $bid})
|
|
330
|
+
ON CREATE SET f.label = $label, f.keys = $keys, f.createdAt = datetime()`, { accountId, aid, bid, label: spec.label, keys: keyValues });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// -----------------------------------------------------------------------
|
|
336
|
+
// Rule 5: contradiction detection
|
|
337
|
+
//
|
|
338
|
+
// Same node, same atomic property, multiple recent writes with diverging
|
|
339
|
+
// values. Detected via the audit trail Task 343 lays down on ToolCall
|
|
340
|
+
// records — but those are append-only, so the simpler signal here is
|
|
341
|
+
// Preference nodes whose status indicates contradiction (set by
|
|
342
|
+
// profile-update mode=contradict).
|
|
343
|
+
// -----------------------------------------------------------------------
|
|
344
|
+
const contraRes = await session.run(`MATCH (p:Preference)
|
|
345
|
+
WHERE p.accountId = $accountId AND p.status = 'contradicted'
|
|
346
|
+
AND p.flaggedByPrune IS NULL
|
|
347
|
+
RETURN id(p) AS pid, p.category AS category, p.key AS key LIMIT 200`, { accountId });
|
|
348
|
+
for (const r of contraRes.records) {
|
|
349
|
+
const pid = Number(r.get("pid"));
|
|
350
|
+
logLine(`[graph:prune:contradiction] nodeId=${pid} category=${r.get("category")} key=${r.get("key")}`);
|
|
351
|
+
summary.contradictionsFlagged += 1;
|
|
352
|
+
if (!dryRun) {
|
|
353
|
+
await session.run(`MATCH (p) WHERE id(p) = $pid SET p.flaggedByPrune = datetime()`, { pid });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
summary.elapsedMs = Date.now() - t0;
|
|
357
|
+
logLine(`[graph:prune:summary] account=${accountId} deleted=${summary.orphansDeleted} nulled=${summary.denylistNulled} convNulled=${summary.conversationMemoryNulled} flagged=${summary.candidateMergesFlagged} contradictions=${summary.contradictionsFlagged} ceilingTripped=${summary.ceilingTripped.join(",") || "none"} elapsed=${summary.elapsedMs}ms dryRun=${dryRun}`);
|
|
358
|
+
return summary;
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
await session.close();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export async function expungeConversationMemory(driver, accountId, conversationId, pattern) {
|
|
365
|
+
const session = driver.session();
|
|
366
|
+
try {
|
|
367
|
+
const matchRes = await session.run(`MATCH (m:Memory)-[:PART_OF|:ABOUT]->(c:Conversation {conversationId: $conversationId})
|
|
368
|
+
WHERE m.accountId = $accountId
|
|
369
|
+
WITH m, [k IN keys(m) WHERE k <> '_denylistAudit'
|
|
370
|
+
AND k <> 'embedding'
|
|
371
|
+
AND CASE
|
|
372
|
+
WHEN m[k] IS NULL THEN false
|
|
373
|
+
WHEN toString(m[k]) = m[k] THEN m[k] CONTAINS $pattern
|
|
374
|
+
ELSE false
|
|
375
|
+
END] AS hits
|
|
376
|
+
RETURN id(m) AS nid, hits, size(collect(m)) AS scanned`, { accountId, conversationId, pattern });
|
|
377
|
+
let propertiesNulled = 0;
|
|
378
|
+
const affectedNodeIds = [];
|
|
379
|
+
let nodesScanned = 0;
|
|
380
|
+
for (const r of matchRes.records) {
|
|
381
|
+
nodesScanned += 1;
|
|
382
|
+
const nid = Number(r.get("nid"));
|
|
383
|
+
const hits = r.get("hits");
|
|
384
|
+
if (hits.length === 0)
|
|
385
|
+
continue;
|
|
386
|
+
affectedNodeIds.push(nid);
|
|
387
|
+
const setClauses = hits.map((k) => `m.\`${k.replace(/`/g, "")}\` = null`).join(", ");
|
|
388
|
+
const auditEntry = JSON.stringify({
|
|
389
|
+
ts: new Date().toISOString(),
|
|
390
|
+
properties: hits,
|
|
391
|
+
manualPattern: pattern,
|
|
392
|
+
conversationId,
|
|
393
|
+
});
|
|
394
|
+
await session.run(`MATCH (m) WHERE id(m) = $nid
|
|
395
|
+
SET ${setClauses},
|
|
396
|
+
m._denylistAudit = coalesce(m._denylistAudit, '') + $entry + '\n'`, { nid, entry: auditEntry });
|
|
397
|
+
propertiesNulled += hits.length;
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
conversationId,
|
|
401
|
+
pattern,
|
|
402
|
+
nodesScanned,
|
|
403
|
+
propertiesNulled,
|
|
404
|
+
affectedNodeIds,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
await session.close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
//# sourceMappingURL=graph-prune.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune.js","sourceRoot":"","sources":["../../src/lib/graph-prune.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron script: graph-prune
|
|
3
|
+
*
|
|
4
|
+
* Daily Neo4j hygiene runner. Triggered by crontab at the configured local
|
|
5
|
+
* hour (default 03:00). Reads ${configDir}/graph-prune.config.json for the
|
|
6
|
+
* disable switch; reads ${configDir}/graph-prune-denylist.json for the
|
|
7
|
+
* operator-curated bad-string list. All actual rule logic lives in
|
|
8
|
+
* lib/graph-prune.ts so the on-demand graph-prune-run MCP tool exercises the
|
|
9
|
+
* same code path.
|
|
10
|
+
*
|
|
11
|
+
* Exit codes are advisory only — cron does not surface them. The script logs
|
|
12
|
+
* its outcome via [graph:prune:*] lines on stderr; logs-read ${configDir}/logs/graph-prune.log
|
|
13
|
+
* is the canonical reader.
|
|
14
|
+
*
|
|
15
|
+
* PLATFORM_ROOT=... ACCOUNT_ID=... node dist/scripts/graph-prune.js
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=graph-prune.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune.d.ts","sourceRoot":"","sources":["../../src/scripts/graph-prune.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron script: graph-prune
|
|
3
|
+
*
|
|
4
|
+
* Daily Neo4j hygiene runner. Triggered by crontab at the configured local
|
|
5
|
+
* hour (default 03:00). Reads ${configDir}/graph-prune.config.json for the
|
|
6
|
+
* disable switch; reads ${configDir}/graph-prune-denylist.json for the
|
|
7
|
+
* operator-curated bad-string list. All actual rule logic lives in
|
|
8
|
+
* lib/graph-prune.ts so the on-demand graph-prune-run MCP tool exercises the
|
|
9
|
+
* same code path.
|
|
10
|
+
*
|
|
11
|
+
* Exit codes are advisory only — cron does not surface them. The script logs
|
|
12
|
+
* its outcome via [graph:prune:*] lines on stderr; logs-read ${configDir}/logs/graph-prune.log
|
|
13
|
+
* is the canonical reader.
|
|
14
|
+
*
|
|
15
|
+
* PLATFORM_ROOT=... ACCOUNT_ID=... node dist/scripts/graph-prune.js
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
import { closeDriver, getDriver } from "../lib/neo4j.js";
|
|
20
|
+
import { loadConfig, loadDenylist, resolveConfigDir, runPrune, } from "../lib/graph-prune.js";
|
|
21
|
+
const CONFIG_DIR = resolveConfigDir();
|
|
22
|
+
const LOCK_FILE = resolve(CONFIG_DIR, "graph-prune.lock");
|
|
23
|
+
function acquireLock() {
|
|
24
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
if (existsSync(LOCK_FILE)) {
|
|
26
|
+
const pid = readFileSync(LOCK_FILE, "utf-8").trim();
|
|
27
|
+
try {
|
|
28
|
+
process.kill(Number(pid), 0);
|
|
29
|
+
return false; // a previous run is still alive
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
unlinkSync(LOCK_FILE); // stale lock from a crashed run
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
writeFileSync(LOCK_FILE, String(process.pid));
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function releaseLock() {
|
|
39
|
+
try {
|
|
40
|
+
if (existsSync(LOCK_FILE)) {
|
|
41
|
+
const pid = readFileSync(LOCK_FILE, "utf-8").trim();
|
|
42
|
+
if (pid === String(process.pid))
|
|
43
|
+
unlinkSync(LOCK_FILE);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* best-effort */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function main() {
|
|
51
|
+
const accountId = process.env.ACCOUNT_ID;
|
|
52
|
+
if (!accountId) {
|
|
53
|
+
process.stderr.write("[graph:prune:error] ACCOUNT_ID environment variable is required\n");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
if (!acquireLock()) {
|
|
57
|
+
process.stderr.write("[graph:prune:skip] another run holds the lock — exiting\n");
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const config = loadConfig(CONFIG_DIR);
|
|
62
|
+
if (!config.enabled) {
|
|
63
|
+
process.stderr.write(`[graph:prune:disabled] account=${accountId} — graph-prune.config.json has enabled=false\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const denylist = loadDenylist(CONFIG_DIR);
|
|
67
|
+
const driver = getDriver();
|
|
68
|
+
await runPrune(driver, accountId, config, denylist, { dryRun: false });
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
process.stderr.write(`[graph:prune:error] ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
releaseLock();
|
|
76
|
+
await closeDriver();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
main();
|
|
80
|
+
//# sourceMappingURL=graph-prune.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune.js","sourceRoot":"","sources":["../../src/scripts/graph-prune.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ExpungeResult } from "../lib/graph-prune.js";
|
|
2
|
+
export interface ConversationMemoryExpungeParams {
|
|
3
|
+
accountId: string;
|
|
4
|
+
conversationId: string;
|
|
5
|
+
pattern: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function conversationMemoryExpunge(params: ConversationMemoryExpungeParams): Promise<ExpungeResult>;
|
|
8
|
+
//# sourceMappingURL=conversation-memory-expunge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-memory-expunge.d.ts","sourceRoot":"","sources":["../../src/tools/conversation-memory-expunge.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtF,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,aAAa,CAAC,CAGxB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getDriver } from "../lib/neo4j.js";
|
|
2
|
+
import { expungeConversationMemory } from "../lib/graph-prune.js";
|
|
3
|
+
export async function conversationMemoryExpunge(params) {
|
|
4
|
+
const driver = getDriver();
|
|
5
|
+
return expungeConversationMemory(driver, params.accountId, params.conversationId, params.pattern);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=conversation-memory-expunge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-memory-expunge.js","sourceRoot":"","sources":["../../src/tools/conversation-memory-expunge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAsB,MAAM,uBAAuB,CAAC;AAQtF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAuC;IAEvC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AACpG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune-denylist-add.d.ts","sourceRoot":"","sources":["../../src/tools/graph-prune-denylist-add.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,iBAAiB,CA0BzE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { loadDenylist, resolveConfigDir, saveDenylist } from "../lib/graph-prune.js";
|
|
2
|
+
export function graphPruneDenylistAdd(values) {
|
|
3
|
+
const configDir = resolveConfigDir();
|
|
4
|
+
const list = loadDenylist(configDir);
|
|
5
|
+
const before = new Set(list.values);
|
|
6
|
+
const added = [];
|
|
7
|
+
const skipped = [];
|
|
8
|
+
for (const raw of values) {
|
|
9
|
+
const v = raw.trim();
|
|
10
|
+
if (!v)
|
|
11
|
+
continue;
|
|
12
|
+
if (before.has(v)) {
|
|
13
|
+
skipped.push(v);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
before.add(v);
|
|
17
|
+
added.push(v);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (added.length > 0) {
|
|
21
|
+
saveDenylist(configDir, {
|
|
22
|
+
values: [...before].sort(),
|
|
23
|
+
updatedAt: new Date().toISOString(),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return { added, skipped, totalAfter: before.size };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=graph-prune-denylist-add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune-denylist-add.js","sourceRoot":"","sources":["../../src/tools/graph-prune-denylist-add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQrF,MAAM,UAAU,qBAAqB,CAAC,MAAgB;IACpD,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,YAAY,CAAC,SAAS,EAAE;YACtB,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE;YAC1B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune-denylist-list.d.ts","sourceRoot":"","sources":["../../src/tools/graph-prune-denylist-list.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,sBAAsB,IAAI,kBAAkB,CAI3D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { loadDenylist, resolveConfigDir } from "../lib/graph-prune.js";
|
|
2
|
+
export function graphPruneDenylistList() {
|
|
3
|
+
const configDir = resolveConfigDir();
|
|
4
|
+
const list = loadDenylist(configDir);
|
|
5
|
+
return { values: list.values, updatedAt: list.updatedAt, count: list.values.length };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=graph-prune-denylist-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-prune-denylist-list.js","sourceRoot":"","sources":["../../src/tools/graph-prune-denylist-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAQvE,MAAM,UAAU,sBAAsB;IACpC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;AACvF,CAAC"}
|