@rubytech/create-realagent 1.0.693 → 1.0.695
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/package.json +1 -1
- package/payload/platform/lib/graph-search/dist/index.d.ts +127 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/index.js +393 -0
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-search/src/__tests__/bm25-only.test.ts +129 -0
- package/payload/platform/lib/graph-search/src/__tests__/escape-and-normalise.test.ts +53 -0
- package/payload/platform/lib/graph-search/src/__tests__/hybrid.test.ts +190 -0
- package/payload/platform/lib/graph-search/src/index.ts +498 -0
- package/payload/platform/lib/graph-search/tsconfig.json +9 -0
- package/payload/platform/lib/graph-search/vitest.config.ts +9 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts +61 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.js +97 -0
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-write/src/index.ts +167 -0
- package/payload/platform/lib/graph-write/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +19 -8
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/index.js +27 -3
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +4 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +10 -6
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js +43 -36
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js.map +1 -1
- package/payload/platform/plugins/docs/references/memory-guide.md +6 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +44 -3
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +3 -32
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +18 -381
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +9 -5
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +10 -23
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/references/graph-primitives.md +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +8 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +24 -10
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/index.js +8 -2
- package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +2 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +45 -18
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +12 -2
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
- package/payload/server/chunk-IAIGB5WN.js +11406 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +656 -21
|
@@ -1,405 +1,42 @@
|
|
|
1
|
-
import { int } from "neo4j-driver";
|
|
2
1
|
import { getSession } from "../lib/neo4j.js";
|
|
3
2
|
import { embed } from "../lib/embeddings.js";
|
|
4
|
-
import {
|
|
3
|
+
import { hybrid } from "../../../../../lib/graph-search/dist/index.js";
|
|
5
4
|
// ---------------------------------------------------------------------------
|
|
6
|
-
//
|
|
5
|
+
// Memory search — thin adapter over the shared `graph-search` lib (Task 675).
|
|
6
|
+
// The hybrid pipeline, Lucene escape, BM25 normalisation, and graph expand
|
|
7
|
+
// all live in platform/lib/graph-search; this file threads the MCP tool
|
|
8
|
+
// parameters through and applies the trash-census observability log.
|
|
7
9
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// Merge: normalise BM25 scores to [0,1] via min-max, then combine:
|
|
13
|
-
// combined = 0.7 * vector_score + 0.3 * normalised_bm25_score
|
|
14
|
-
// Deduplicate by nodeId, taking the higher combined score.
|
|
15
|
-
// Fall back to vector-only if the full-text index doesn't exist.
|
|
16
|
-
//
|
|
17
|
-
// Trashed nodes (Task 576 :Trashed label) and legacy soft-deleted nodes
|
|
18
|
-
// (pre-Task-576 deletedAt property) are excluded from ALL query paths via
|
|
19
|
-
// the shared notTrashed() helper: vector search, BM25, property keyword
|
|
20
|
-
// search, and graph expand. The filter is unconditional — no parameter
|
|
21
|
-
// controls it. When the filter drops results, [trash:read-filter-skipped]
|
|
22
|
-
// is emitted so silent over-filtering is observable.
|
|
10
|
+
// Trash filter visibility: when the account holds `:Trashed` nodes we emit
|
|
11
|
+
// `[trash:read-filter-skipped]` so silent over-filtering stays observable.
|
|
12
|
+
// The census lives here (not in the shared lib) because it is MCP-tool-
|
|
13
|
+
// specific context the admin route doesn't share.
|
|
23
14
|
// ---------------------------------------------------------------------------
|
|
24
|
-
const VECTOR_WEIGHT = 0.7;
|
|
25
|
-
const BM25_WEIGHT = 0.3;
|
|
26
|
-
const FULLTEXT_INDEX_NAME = "knowledge_fulltext";
|
|
27
|
-
// Cached vector index map — discovered from Neo4j at first query.
|
|
28
|
-
let indexCache = null;
|
|
29
|
-
/**
|
|
30
|
-
* Discover all vector indexes from Neo4j.
|
|
31
|
-
* Returns a map of label → index name.
|
|
32
|
-
*/
|
|
33
|
-
async function discoverIndexes() {
|
|
34
|
-
if (indexCache)
|
|
35
|
-
return indexCache;
|
|
36
|
-
const session = getSession();
|
|
37
|
-
try {
|
|
38
|
-
const result = await session.run(`SHOW INDEXES YIELD name, labelsOrTypes, type WHERE type = 'VECTOR' RETURN name, labelsOrTypes`);
|
|
39
|
-
indexCache = new Map();
|
|
40
|
-
for (const record of result.records) {
|
|
41
|
-
const name = record.get("name");
|
|
42
|
-
const labels = record.get("labelsOrTypes");
|
|
43
|
-
for (const label of labels) {
|
|
44
|
-
indexCache.set(label, name);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return indexCache;
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
await session.close();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** Clear the index cache — call after schema changes. */
|
|
54
|
-
export function clearIndexCache() {
|
|
55
|
-
indexCache = null;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Run BM25 full-text search against the knowledge_fulltext index.
|
|
59
|
-
* Returns empty array if the index doesn't exist (graceful fallback).
|
|
60
|
-
*/
|
|
61
|
-
async function bm25Search(queryText, accountId, limit, allowedScopes, keywordFilter, agentSlug) {
|
|
62
|
-
const session = getSession();
|
|
63
|
-
try {
|
|
64
|
-
const scopeClause = allowedScopes
|
|
65
|
-
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
66
|
-
: "";
|
|
67
|
-
const agentClause = agentSlug
|
|
68
|
-
? "AND node.agents IS NOT NULL AND $agentSlug IN node.agents"
|
|
69
|
-
: "";
|
|
70
|
-
const kwClause = keywordFilter?.clause ?? "";
|
|
71
|
-
// Escape Lucene special characters in the query
|
|
72
|
-
const escaped = queryText.replace(/[+\-&|!(){}[\]^"~*?:\\/]/g, "\\$&");
|
|
73
|
-
const result = await session.run(`CALL db.index.fulltext.queryNodes($indexName, $query)
|
|
74
|
-
YIELD node, score
|
|
75
|
-
WHERE node.accountId = $accountId
|
|
76
|
-
${scopeClause}
|
|
77
|
-
${agentClause}
|
|
78
|
-
AND ${notTrashed("node")}
|
|
79
|
-
${kwClause}
|
|
80
|
-
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
81
|
-
ORDER BY score DESC
|
|
82
|
-
LIMIT $limit`, {
|
|
83
|
-
indexName: FULLTEXT_INDEX_NAME,
|
|
84
|
-
query: escaped,
|
|
85
|
-
accountId,
|
|
86
|
-
limit: int(limit),
|
|
87
|
-
...(allowedScopes ? { allowedScopes } : {}),
|
|
88
|
-
...(agentSlug ? { agentSlug } : {}),
|
|
89
|
-
...(keywordFilter?.params ?? {}),
|
|
90
|
-
});
|
|
91
|
-
return result.records.map((r) => ({
|
|
92
|
-
nodeId: r.get("nodeId"),
|
|
93
|
-
labels: r.get("nodeLabels"),
|
|
94
|
-
properties: nodeToPlain(r.get("node").properties),
|
|
95
|
-
score: typeof r.get("score") === "number" ? r.get("score") : Number(r.get("score")),
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
// Full-text index may not exist yet — fall back to vector-only
|
|
100
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
-
if (msg.includes("index") || msg.includes("fulltext") || msg.includes("not found")) {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
throw err;
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await session.close();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Normalise BM25 scores to [0, 1] using min-max within the result set.
|
|
112
|
-
* If all scores are equal (or single result), returns 1.0 for all.
|
|
113
|
-
*/
|
|
114
|
-
function normaliseBm25Scores(scores) {
|
|
115
|
-
if (scores.length === 0)
|
|
116
|
-
return [];
|
|
117
|
-
const min = Math.min(...scores);
|
|
118
|
-
const max = Math.max(...scores);
|
|
119
|
-
const range = max - min;
|
|
120
|
-
if (range === 0)
|
|
121
|
-
return scores.map(() => 1.0);
|
|
122
|
-
return scores.map((s) => (s - min) / range);
|
|
123
|
-
}
|
|
124
15
|
export async function memorySearch(params) {
|
|
125
|
-
const { query: queryText, labels, accountId, limit = 10, expandHops = 1, allowedScopes, keywords, keywordMatch = "any", agentSlug, keywordSubscriptions, } = params;
|
|
126
|
-
const queryEmbedding = await embed(queryText);
|
|
127
|
-
const labelToIndex = await discoverIndexes();
|
|
128
16
|
const session = getSession();
|
|
129
|
-
|
|
130
|
-
// When keywords are provided, nodes WITH a `keywords` property must match;
|
|
131
|
-
// nodes WITHOUT the property (e.g. Email) pass through unfiltered.
|
|
132
|
-
const keywordFn = keywordMatch === "all" ? "ALL" : "ANY";
|
|
133
|
-
const keywordClause = keywords && keywords.length > 0
|
|
134
|
-
? `AND (node.keywords IS NULL OR ${keywordFn}(kw IN $keywords WHERE kw IN node.keywords))`
|
|
135
|
-
: "";
|
|
136
|
-
const keywordParams = keywords && keywords.length > 0
|
|
137
|
-
? { keywords: keywords.map((k) => k.toLowerCase().trim()) }
|
|
138
|
-
: {};
|
|
17
|
+
const limit = params.limit ?? 10;
|
|
139
18
|
try {
|
|
140
|
-
|
|
141
|
-
let indexesToQuery;
|
|
142
|
-
if (labels && labels.length > 0) {
|
|
143
|
-
indexesToQuery = labels
|
|
144
|
-
.map((l) => labelToIndex.get(l))
|
|
145
|
-
.filter((idx) => idx !== undefined);
|
|
146
|
-
if (indexesToQuery.length === 0) {
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
indexesToQuery = [...new Set(labelToIndex.values())];
|
|
152
|
-
}
|
|
153
|
-
const scoredMap = new Map();
|
|
154
|
-
// Build scope clause — omit for admin (unrestricted), filter for scoped agents.
|
|
155
|
-
// IS NULL handles legacy nodes that predate the scope migration.
|
|
156
|
-
const scopeClause = allowedScopes
|
|
157
|
-
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
158
|
-
: "";
|
|
159
|
-
const scopeParams = allowedScopes ? { allowedScopes } : {};
|
|
160
|
-
// Build per-agent tag clause — when set, only nodes explicitly tagged for this agent.
|
|
161
|
-
// Both agentSlug AND allowedScopes must pass (defense in depth).
|
|
162
|
-
const agentClause = agentSlug
|
|
163
|
-
? "AND node.agents IS NOT NULL AND $agentSlug IN node.agents"
|
|
164
|
-
: "";
|
|
165
|
-
const agentParams = agentSlug ? { agentSlug } : {};
|
|
166
|
-
// Unconditional soft-delete filter — :Trashed nodes (Task 576) and legacy
|
|
167
|
-
// deletedAt-marked nodes are never returned. notTrashed() centralises the
|
|
168
|
-
// predicate so a future migration drops the legacy arm in one place.
|
|
169
|
-
for (const indexName of indexesToQuery) {
|
|
170
|
-
const vectorQuery = `
|
|
171
|
-
CALL db.index.vector.queryNodes($indexName, $limit, $embedding)
|
|
172
|
-
YIELD node, score
|
|
173
|
-
WHERE node.accountId = $accountId
|
|
174
|
-
${scopeClause}
|
|
175
|
-
${agentClause}
|
|
176
|
-
AND ${notTrashed("node")}
|
|
177
|
-
${keywordClause}
|
|
178
|
-
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
179
|
-
ORDER BY score DESC
|
|
180
|
-
LIMIT $limit
|
|
181
|
-
`;
|
|
182
|
-
const vectorResult = await session.run(vectorQuery, {
|
|
183
|
-
indexName,
|
|
184
|
-
embedding: queryEmbedding,
|
|
185
|
-
limit: int(limit),
|
|
186
|
-
accountId,
|
|
187
|
-
...scopeParams,
|
|
188
|
-
...agentParams,
|
|
189
|
-
...keywordParams,
|
|
190
|
-
});
|
|
191
|
-
for (const record of vectorResult.records) {
|
|
192
|
-
const nodeId = record.get("nodeId");
|
|
193
|
-
const score = typeof record.get("score") === "number"
|
|
194
|
-
? record.get("score")
|
|
195
|
-
: Number(record.get("score"));
|
|
196
|
-
const existing = scoredMap.get(nodeId);
|
|
197
|
-
if (existing) {
|
|
198
|
-
existing.vectorScore = Math.max(existing.vectorScore, score);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
scoredMap.set(nodeId, {
|
|
202
|
-
nodeId,
|
|
203
|
-
labels: record.get("nodeLabels"),
|
|
204
|
-
properties: nodeToPlain(record.get("node").properties),
|
|
205
|
-
vectorScore: score,
|
|
206
|
-
bm25Score: 0,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// --- BM25 search (knowledge documents only, graceful fallback) ---
|
|
212
|
-
const bm25KeywordFilter = keywordClause
|
|
213
|
-
? { clause: keywordClause, params: keywordParams }
|
|
214
|
-
: undefined;
|
|
215
|
-
const bm25Results = await bm25Search(queryText, accountId, limit, allowedScopes, bm25KeywordFilter, agentSlug);
|
|
216
|
-
if (bm25Results.length > 0) {
|
|
217
|
-
const rawScores = bm25Results.map((r) => r.score);
|
|
218
|
-
const normalised = normaliseBm25Scores(rawScores);
|
|
219
|
-
for (let i = 0; i < bm25Results.length; i++) {
|
|
220
|
-
const r = bm25Results[i];
|
|
221
|
-
const existing = scoredMap.get(r.nodeId);
|
|
222
|
-
if (existing) {
|
|
223
|
-
existing.bm25Score = Math.max(existing.bm25Score, normalised[i]);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
scoredMap.set(r.nodeId, {
|
|
227
|
-
nodeId: r.nodeId,
|
|
228
|
-
labels: r.labels,
|
|
229
|
-
properties: r.properties,
|
|
230
|
-
vectorScore: 0,
|
|
231
|
-
bm25Score: normalised[i],
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// --- Keyword subscription queries (per-agent reactive knowledge) ---
|
|
237
|
-
// When keywordSubscriptions are configured, two complementary searches run:
|
|
238
|
-
// 1. BM25 full-text search per keyword (catches content matches)
|
|
239
|
-
// 2. Property-based search on the node `keywords` array (catches tagged nodes)
|
|
240
|
-
// Both run WITHOUT the agentSlug clause — keyword matches are scope-inclusive
|
|
241
|
-
// by design. ALLOWED_SCOPES still applies (defense in depth).
|
|
242
|
-
//
|
|
243
|
-
// QUERY --> BM25 per keyword ────────────────--> ┐
|
|
244
|
-
// └--> Property match (node.keywords) ──--> ├--> scoredMap (union)
|
|
245
|
-
// ┘
|
|
246
|
-
if (keywordSubscriptions && keywordSubscriptions.length > 0) {
|
|
247
|
-
// 1. BM25 full-text search per keyword (case-insensitive via analyzer)
|
|
248
|
-
for (const kw of keywordSubscriptions) {
|
|
249
|
-
const kwResults = await bm25Search(kw, accountId, limit, allowedScopes);
|
|
250
|
-
if (kwResults.length > 0) {
|
|
251
|
-
const rawScores = kwResults.map((r) => r.score);
|
|
252
|
-
const normalised = normaliseBm25Scores(rawScores);
|
|
253
|
-
for (let i = 0; i < kwResults.length; i++) {
|
|
254
|
-
const r = kwResults[i];
|
|
255
|
-
const existing = scoredMap.get(r.nodeId);
|
|
256
|
-
if (existing) {
|
|
257
|
-
existing.bm25Score = Math.max(existing.bm25Score, normalised[i]);
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
scoredMap.set(r.nodeId, {
|
|
261
|
-
nodeId: r.nodeId,
|
|
262
|
-
labels: r.labels,
|
|
263
|
-
properties: r.properties,
|
|
264
|
-
vectorScore: 0,
|
|
265
|
-
bm25Score: normalised[i],
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// 2. Property-based search: find nodes whose `keywords` array contains
|
|
272
|
-
// any of the subscription keywords. Runs against ALL node labels (not
|
|
273
|
-
// just KnowledgeDocument) since knowledge may be stored as DefinedTerm,
|
|
274
|
-
// Question, Service, etc. Uses toLower() on both sides as a transitional
|
|
275
|
-
// measure for existing mixed-case data.
|
|
276
|
-
const propScopeClause = allowedScopes
|
|
277
|
-
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
278
|
-
: "";
|
|
279
|
-
const propResult = await session.run(`MATCH (node)
|
|
280
|
-
WHERE node.accountId = $accountId
|
|
281
|
-
AND ${notTrashed("node")}
|
|
282
|
-
AND node.keywords IS NOT NULL
|
|
283
|
-
AND ANY(kw IN $kwSubs WHERE ANY(nk IN node.keywords WHERE toLower(nk) = kw))
|
|
284
|
-
${propScopeClause}
|
|
285
|
-
RETURN node, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
286
|
-
LIMIT $limit`, {
|
|
287
|
-
accountId,
|
|
288
|
-
kwSubs: keywordSubscriptions,
|
|
289
|
-
limit: int(limit),
|
|
290
|
-
...(allowedScopes ? { allowedScopes } : {}),
|
|
291
|
-
});
|
|
292
|
-
for (const record of propResult.records) {
|
|
293
|
-
const nodeId = record.get("nodeId");
|
|
294
|
-
const existing = scoredMap.get(nodeId);
|
|
295
|
-
if (existing) {
|
|
296
|
-
// Property match is exact — boost bm25Score to maximum
|
|
297
|
-
existing.bm25Score = Math.max(existing.bm25Score, 1.0);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
scoredMap.set(nodeId, {
|
|
301
|
-
nodeId,
|
|
302
|
-
labels: record.get("nodeLabels"),
|
|
303
|
-
properties: nodeToPlain(record.get("node").properties),
|
|
304
|
-
vectorScore: 0,
|
|
305
|
-
bm25Score: 1.0,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
// --- Merge: combine vector + BM25 scores ---
|
|
311
|
-
const merged = [...scoredMap.values()].map((node) => ({
|
|
312
|
-
...node,
|
|
313
|
-
combinedScore: VECTOR_WEIGHT * node.vectorScore + BM25_WEIGHT * node.bm25Score,
|
|
314
|
-
}));
|
|
315
|
-
merged.sort((a, b) => b.combinedScore - a.combinedScore);
|
|
316
|
-
const topResults = merged.slice(0, limit);
|
|
317
|
-
// --- Graph expand ---
|
|
318
|
-
const allResults = [];
|
|
319
|
-
for (const node of topResults) {
|
|
320
|
-
const result = {
|
|
321
|
-
nodeId: node.nodeId,
|
|
322
|
-
labels: node.labels,
|
|
323
|
-
properties: node.properties,
|
|
324
|
-
score: node.combinedScore,
|
|
325
|
-
related: [],
|
|
326
|
-
};
|
|
327
|
-
if (expandHops > 0) {
|
|
328
|
-
const expandScopeClause = allowedScopes
|
|
329
|
-
? "AND (related.scope IS NULL OR related.scope IN $allowedScopes)"
|
|
330
|
-
: "";
|
|
331
|
-
// Per-agent expand filter: related nodes must be untagged or tagged for this agent.
|
|
332
|
-
// Prevents cross-agent content leakage via graph traversal.
|
|
333
|
-
const expandAgentClause = agentSlug
|
|
334
|
-
? "AND (related.agents IS NULL OR $agentSlug IN related.agents)"
|
|
335
|
-
: "";
|
|
336
|
-
const expandQuery = `
|
|
337
|
-
MATCH (n)-[r]-(related)
|
|
338
|
-
WHERE elementId(n) = $nodeId
|
|
339
|
-
AND ${notTrashed("related")}
|
|
340
|
-
${expandScopeClause}
|
|
341
|
-
${expandAgentClause}
|
|
342
|
-
RETURN type(r) AS relType,
|
|
343
|
-
CASE WHEN startNode(r) = n THEN 'outgoing' ELSE 'incoming' END AS direction,
|
|
344
|
-
labels(related) AS relatedLabels,
|
|
345
|
-
related
|
|
346
|
-
LIMIT 20
|
|
347
|
-
`;
|
|
348
|
-
const expandResult = await session.run(expandQuery, {
|
|
349
|
-
nodeId: node.nodeId,
|
|
350
|
-
...scopeParams,
|
|
351
|
-
...agentParams,
|
|
352
|
-
});
|
|
353
|
-
for (const relRecord of expandResult.records) {
|
|
354
|
-
result.related.push({
|
|
355
|
-
relationship: relRecord.get("relType"),
|
|
356
|
-
direction: relRecord.get("direction"),
|
|
357
|
-
labels: relRecord.get("relatedLabels"),
|
|
358
|
-
properties: nodeToPlain(relRecord.get("related").properties),
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
allResults.push(result);
|
|
363
|
-
}
|
|
364
|
-
// Trash filter visibility: emit when the account holds any :Trashed nodes,
|
|
365
|
-
// so silent over-filtering is observable. Cheap single-row aggregate;
|
|
366
|
-
// accountId-as-unique-key labels (LocalBusiness/OnboardingState) have
|
|
367
|
-
// their accountId nulled and are missed here — acceptable, those labels
|
|
368
|
-
// are 1-per-account and rare in trash.
|
|
19
|
+
const res = await hybrid(session, embed, { ...params, limit });
|
|
369
20
|
try {
|
|
370
|
-
const trashCensus = await session.run(`MATCH (n:Trashed) WHERE n.accountId = $accountId RETURN count(n) AS c`, { accountId });
|
|
21
|
+
const trashCensus = await session.run(`MATCH (n:Trashed) WHERE n.accountId = $accountId RETURN count(n) AS c`, { accountId: params.accountId });
|
|
371
22
|
const skipped = trashCensus.records[0]?.get("c");
|
|
372
23
|
const skippedCount = typeof skipped === "number"
|
|
373
24
|
? skipped
|
|
374
25
|
: skipped?.toNumber?.() ?? 0;
|
|
375
26
|
if (skippedCount > 0) {
|
|
376
|
-
const fingerprint = (labels && labels.length > 0)
|
|
377
|
-
? `labels=${labels.join(",")}`
|
|
378
|
-
: `query=${
|
|
379
|
-
process.stderr.write(`[trash:read-filter-skipped] accountId=${accountId} ${fingerprint} skippedCount=${skippedCount}\n`);
|
|
27
|
+
const fingerprint = (params.labels && params.labels.length > 0)
|
|
28
|
+
? `labels=${params.labels.join(",")}`
|
|
29
|
+
: `query=${params.query.slice(0, 40).replace(/\s+/g, " ")}`;
|
|
30
|
+
process.stderr.write(`[trash:read-filter-skipped] accountId=${params.accountId} ${fingerprint} skippedCount=${skippedCount}\n`);
|
|
380
31
|
}
|
|
381
32
|
}
|
|
382
33
|
catch {
|
|
383
|
-
//
|
|
34
|
+
// observability-only; never break a search on census failure.
|
|
384
35
|
}
|
|
385
|
-
return
|
|
36
|
+
return res.results;
|
|
386
37
|
}
|
|
387
38
|
finally {
|
|
388
39
|
await session.close();
|
|
389
40
|
}
|
|
390
41
|
}
|
|
391
|
-
function nodeToPlain(properties) {
|
|
392
|
-
const plain = {};
|
|
393
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
394
|
-
if (key === "embedding")
|
|
395
|
-
continue;
|
|
396
|
-
if (value && typeof value === "object" && "toNumber" in value) {
|
|
397
|
-
plain[key] = value.toNumber();
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
plain[key] = value;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return plain;
|
|
404
|
-
}
|
|
405
42
|
//# sourceMappingURL=memory-search.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-search.js","sourceRoot":"","sources":["../../src/tools/memory-search.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-search.js","sourceRoot":"","sources":["../../src/tools/memory-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAwC,MAAM,+CAA+C,CAAC;AAE7G,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,wEAAwE;AACxE,qEAAqE;AACrE,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,wEAAwE;AACxE,kDAAkD;AAClD,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAwD;IAExD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAE/D,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,uEAAuE,EACvE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAChC,CAAC;YACF,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAGlC,CAAC;YACd,MAAM,YAAY,GAAG,OAAO,OAAO,KAAK,QAAQ;gBAC9C,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAC;YAC/B,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;oBAC7D,CAAC,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;oBACrC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yCAAyC,MAAM,CAAC,SAAS,IAAI,WAAW,iBAAiB,YAAY,IAAI,CAC1G,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;QAED,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { LoadedSchema } from "../lib/schema-loader.js";
|
|
2
|
+
import { type GraphRelationship, type CreatedBy } from "../../../../../lib/graph-write/dist/index.js";
|
|
2
3
|
interface WriteParams {
|
|
3
4
|
labels: string[];
|
|
4
5
|
properties: Record<string, unknown>;
|
|
5
6
|
accountId?: string;
|
|
6
7
|
/** Visibility scope for the node (e.g. "public", "shared", "admin", "user:phone:+44..."). Required — caller must specify. */
|
|
7
8
|
scope: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/**
|
|
10
|
+
* At least one relationship required — enforced by writeNodeWithEdges.
|
|
11
|
+
* The doctrine: a node without at least one adjacency is noise, not
|
|
12
|
+
* knowledge. See .docs/neo4j.md (Write doctrine).
|
|
13
|
+
*/
|
|
14
|
+
relationships: GraphRelationship[];
|
|
15
|
+
/** Provenance stamp — sourced from the MCP server env vars (agent/session). */
|
|
16
|
+
createdBy: CreatedBy;
|
|
13
17
|
/**
|
|
14
18
|
* Loaded schema used to validate the write before any Neo4j or embedding
|
|
15
19
|
* work is done. Required — the caller (index.ts) loads this once at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-write.d.ts","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"memory-write.d.ts","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACf,MAAM,8CAA8C,CAAC;AAEtD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6HAA6H;IAC7H,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,+EAA+E;IAC/E,SAAS,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAkH3E;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKhD;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EAAE,EAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAUR"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getSession } from "../lib/neo4j.js";
|
|
2
2
|
import { embed } from "../lib/embeddings.js";
|
|
3
3
|
import { validateWrite } from "../lib/schema-validator.js";
|
|
4
|
+
import { writeNodeWithEdges, } from "../../../../../lib/graph-write/dist/index.js";
|
|
4
5
|
export async function memoryWrite(params) {
|
|
5
|
-
const { labels, properties, accountId, scope, relationships, schema } = params;
|
|
6
|
+
const { labels, properties, accountId, scope, relationships, createdBy, schema } = params;
|
|
6
7
|
if (!scope) {
|
|
7
8
|
throw new Error("scope is required — valid values: 'public', 'shared', 'admin', 'user:{identifier}'");
|
|
8
9
|
}
|
|
@@ -20,7 +21,6 @@ export async function memoryWrite(params) {
|
|
|
20
21
|
const session = getSession();
|
|
21
22
|
const labelStr = labels.map((l) => `\`${l}\``).join(":");
|
|
22
23
|
try {
|
|
23
|
-
// Compute embedding from a text representation of the node
|
|
24
24
|
const textForEmbedding = buildTextRepresentation(labels, properties);
|
|
25
25
|
const embedding = await embed(textForEmbedding);
|
|
26
26
|
const nodeProps = {
|
|
@@ -33,27 +33,14 @@ export async function memoryWrite(params) {
|
|
|
33
33
|
if (!properties.createdOn && !properties.createdAt) {
|
|
34
34
|
nodeProps.createdAt = new Date().toISOString();
|
|
35
35
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Create relationships if specified
|
|
45
|
-
if (relationships && relationships.length > 0) {
|
|
46
|
-
for (const rel of relationships) {
|
|
47
|
-
const relQuery = rel.direction === "outgoing"
|
|
48
|
-
? `MATCH (a), (b) WHERE elementId(a) = $fromId AND elementId(b) = $toId CREATE (a)-[:${rel.type}]->(b)`
|
|
49
|
-
: `MATCH (a), (b) WHERE elementId(a) = $fromId AND elementId(b) = $toId CREATE (b)-[:${rel.type}]->(a)`;
|
|
50
|
-
await session.run(relQuery, {
|
|
51
|
-
fromId: nodeId,
|
|
52
|
-
toId: rel.targetNodeId,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return { nodeId, labels: nodeLabels, created: true };
|
|
36
|
+
const result = await writeNodeWithEdges({
|
|
37
|
+
session,
|
|
38
|
+
labels,
|
|
39
|
+
props: nodeProps,
|
|
40
|
+
relationships,
|
|
41
|
+
createdBy,
|
|
42
|
+
});
|
|
43
|
+
return { nodeId: result.nodeId, labels: result.labels, created: true };
|
|
57
44
|
}
|
|
58
45
|
catch (error) {
|
|
59
46
|
// Constraint violations include an unusable internal ID (e.g. "Node(219)").
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-write.js","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"memory-write.js","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,7 +57,7 @@ graph as a single action.
|
|
|
57
57
|
| User intent | Tool | When to pick this, not the next row |
|
|
58
58
|
|---|---|---|
|
|
59
59
|
| "remove admin X", "take away their admin access", "revoke admin" | `mcp__admin__admin-remove` | Device-level admin offboarding — detaches the `(AdminUser)-[:ADMIN_OF]->(LocalBusiness)` edge and updates `platform/config/users.json`. Not for scrubbing personal data — use the GDPR contact-erase tools for that. |
|
|
60
|
-
| "delete this specific node", "remove this entry by its id", "get rid of node X", "remove orphans", "prune", "clean up" | `mcp__memory__memory-delete` | Soft-deletes a single node by `elementId` (adds `:Trashed`, preserves relationships, snapshots unique keys, invisible to `memory-search` and any `notTrashed`-filtered read). For KnowledgeDocument, cascades to Sections and Chunks. Enumerate candidates first with a Cypher read, confirm with the user, then delete each by `elementId`. There is no bulk "delete everything without relationships" tool —
|
|
60
|
+
| "delete this specific node", "remove this entry by its id", "get rid of node X", "remove orphans", "prune", "clean up" | `mcp__memory__memory-delete` | Soft-deletes a single node by `elementId` (adds `:Trashed`, preserves relationships, snapshots unique keys, invisible to `memory-search` and any `notTrashed`-filtered read). For KnowledgeDocument, cascades to Sections and Chunks. Enumerate candidates first with a Cypher read, confirm with the user, then delete each by `elementId`. There is no bulk "delete everything without relationships" tool. Per the Task 673 write doctrine (`.docs/neo4j.md` § Write doctrine), **new** orphans should be impossible — every writer now rejects zero-edge calls at the MCP tool-surface. Existing orphans from pre-Task-673 data are legitimate backfill candidates (see Task 677) but are not produced by any live writer; if you see a freshly-created orphan, it is a bug in a writer that bypassed `writeNodeWithEdges`, not expected state. |
|
|
61
61
|
| "undo that delete", "I deleted X by mistake, bring it back", "restore the contact I just trashed" | `mcp__memory__memory-restore` | Removes `:Trashed`, restores snapshotted unique-key properties. Fails loudly when an active node already holds the unique slot (e.g. a fresh `OnboardingState` was created while the old one was in trash) — the error names the conflicting `elementId` so you can ask the user how to resolve. |
|
|
62
62
|
| "empty the trash", "purge old soft-deletes", "what's been sitting in trash and ready to clear" | `mcp__memory__memory-empty-trash` | Hard-deletes trashed nodes whose `trashedAt` is older than `graceDays` (default 30); `dryRun=true` lists candidates without mutating. Use `dryRun` first, show the list to the user, then run for real. For KnowledgeDocument candidates, the on-disk attachment directory is removed too. |
|
|
63
63
|
| "scrub this conversation's memory", "remove this claim the bot picked up", "forget what we said in that thread" | `mcp__memory__conversation-memory-expunge` | Removes deny-listable content from `:Memory` nodes scoped to one conversation. Requires an explicit pattern and conversationId. Not for deleting individual nodes — use `memory-delete`. |
|
|
@@ -24,6 +24,8 @@ const accountId = process.env.ACCOUNT_ID;
|
|
|
24
24
|
if (!accountId) {
|
|
25
25
|
throw new Error("ACCOUNT_ID environment variable is required");
|
|
26
26
|
}
|
|
27
|
+
const sessionId = process.env.SESSION_ID || undefined;
|
|
28
|
+
const agentSlug = process.env.AGENT_SLUG || undefined;
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
28
30
|
// Shared schema fragments
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
@@ -52,7 +54,12 @@ server.tool("schedule-event", "Create a scheduled event or appointment. For one-
|
|
|
52
54
|
try {
|
|
53
55
|
const timezone = await getUserTimezone(accountId);
|
|
54
56
|
console.error(`[schedule-event] formatted for timezone: ${timezone}`);
|
|
55
|
-
const result = await scheduleEvent({
|
|
57
|
+
const result = await scheduleEvent({
|
|
58
|
+
...params,
|
|
59
|
+
accountId,
|
|
60
|
+
sessionKey: params.sessionKey ?? sessionId,
|
|
61
|
+
createdBy: { agent: agentSlug, session: sessionId, tool: "schedule-event" },
|
|
62
|
+
});
|
|
56
63
|
const parts = [`Event created: "${params.name}" (ID: ${result.eventId})`];
|
|
57
64
|
parts.push(`starts: ${formatCombined(params.startDate, timezone)}`);
|
|
58
65
|
if (params.recurrence)
|