@rubytech/create-maxy 1.0.692 → 1.0.694
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 +69 -15
- 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/platform/scripts/logs-read.sh +63 -9
- package/payload/platform/scripts/logs-read.test.sh +212 -0
- package/payload/server/chunk-IAIGB5WN.js +11406 -0
- package/payload/server/chunk-Q6NDXCM6.js +11448 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +656 -21
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared hybrid search primitive over the Neo4j knowledge graph.
|
|
3
|
+
*
|
|
4
|
+
* Pre-Task-675 there were two BM25 implementations over the same
|
|
5
|
+
* `knowledge_fulltext` index — one in the memory MCP tool, one in the admin
|
|
6
|
+
* Hono route — with divergent filter semantics (soft-delete primitive) and
|
|
7
|
+
* divergent ranking (agent ran hybrid vector+BM25, UI ran BM25-only). Task
|
|
8
|
+
* 675 collapses both into this lib; memory MCP + admin route now share
|
|
9
|
+
* a single code path so `/graph` UI ranking matches what the agent sees.
|
|
10
|
+
*
|
|
11
|
+
* QUERY --> EMBED --> VECTOR SEARCH (per index) --> ┐
|
|
12
|
+
* │ ├--> MERGE --> EXPAND --> RESULTS
|
|
13
|
+
* └--> BM25 FULL-TEXT SEARCH -------------------> ┘
|
|
14
|
+
*
|
|
15
|
+
* Hybrid merge: normalise BM25 scores to [0,1] via min-max, then combine
|
|
16
|
+
* combined = 0.7 * vector_score + 0.3 * normalised_bm25_score
|
|
17
|
+
* Dedup by nodeId, keeping the higher combined score.
|
|
18
|
+
*
|
|
19
|
+
* Trashed nodes (`:Trashed` label + legacy `deletedAt` property) are
|
|
20
|
+
* excluded from every path via `notTrashed()` from `graph-trash`.
|
|
21
|
+
*
|
|
22
|
+
* The lib is STATELESS. Callers pass a `Session` and, for hybrid, an
|
|
23
|
+
* `embed` function. This keeps the lib independent of Ollama config and
|
|
24
|
+
* free of circular imports against each caller's own getSession/embed
|
|
25
|
+
* (memory MCP and the Hono ui build each have their own copies for
|
|
26
|
+
* cross-build-boundary reasons — see neo4j-store.ts:68 comment).
|
|
27
|
+
*
|
|
28
|
+
* Log emission is the caller's responsibility — the lib throws or returns
|
|
29
|
+
* `mode` in the result so the caller can format its own `[graph-search]`
|
|
30
|
+
* line with the prefix and fields it wants.
|
|
31
|
+
*/
|
|
32
|
+
import { type Session } from "neo4j-driver";
|
|
33
|
+
export interface SearchHit {
|
|
34
|
+
nodeId: string;
|
|
35
|
+
labels: string[];
|
|
36
|
+
properties: Record<string, unknown>;
|
|
37
|
+
score: number;
|
|
38
|
+
}
|
|
39
|
+
export interface SearchResult extends SearchHit {
|
|
40
|
+
related: Array<{
|
|
41
|
+
relationship: string;
|
|
42
|
+
direction: string;
|
|
43
|
+
labels: string[];
|
|
44
|
+
properties: Record<string, unknown>;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export interface ScoredNode {
|
|
48
|
+
nodeId: string;
|
|
49
|
+
labels: string[];
|
|
50
|
+
properties: Record<string, unknown>;
|
|
51
|
+
vectorScore: number;
|
|
52
|
+
bm25Score: number;
|
|
53
|
+
}
|
|
54
|
+
export type SearchMode = "hybrid" | "bm25";
|
|
55
|
+
export interface Bm25OnlyParams {
|
|
56
|
+
query: string;
|
|
57
|
+
accountId?: string;
|
|
58
|
+
limit: number;
|
|
59
|
+
allowedScopes?: string[];
|
|
60
|
+
agentSlug?: string;
|
|
61
|
+
keywords?: string[];
|
|
62
|
+
keywordMatch?: "any" | "all";
|
|
63
|
+
}
|
|
64
|
+
export interface HybridParams extends Bm25OnlyParams {
|
|
65
|
+
labels?: string[];
|
|
66
|
+
expandHops?: number;
|
|
67
|
+
keywordSubscriptions?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* When true, a failing `embed()` does NOT throw — the lib falls back to
|
|
70
|
+
* `bm25Only()` and returns `mode: "bm25"`. Admin-route callers set this
|
|
71
|
+
* true so Ollama-down degrades to BM25-only; MCP tool callers leave it
|
|
72
|
+
* false so embed failure surfaces loudly.
|
|
73
|
+
*/
|
|
74
|
+
degradeOnEmbedFailure?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface HybridResponse {
|
|
77
|
+
mode: SearchMode;
|
|
78
|
+
results: SearchResult[];
|
|
79
|
+
/** Populated when degradeOnEmbedFailure fired. Caller logs it. */
|
|
80
|
+
embedError?: string;
|
|
81
|
+
}
|
|
82
|
+
export type EmbedFn = (text: string) => Promise<number[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Lucene-escape special characters per Neo4j's fulltext query grammar.
|
|
85
|
+
* `/[+\-&|!(){}[\]^"~*?:\\/]/g` — matches memory-search.ts:127 and
|
|
86
|
+
* neo4j-store.ts:2521 verbatim; consolidation preserves escape set.
|
|
87
|
+
*/
|
|
88
|
+
export declare function escapeLucene(query: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Normalise BM25 scores to [0, 1] using min-max within the result set.
|
|
91
|
+
* If all scores are equal (or single result), returns 1.0 for all —
|
|
92
|
+
* the range-zero branch prevents divide-by-zero and keeps the combined
|
|
93
|
+
* score meaningful.
|
|
94
|
+
*/
|
|
95
|
+
export declare function normaliseBm25Scores(scores: number[]): number[];
|
|
96
|
+
export declare function discoverIndexes(session: Session): Promise<Map<string, string>>;
|
|
97
|
+
/** Clear the index cache — call after schema changes. */
|
|
98
|
+
export declare function clearIndexCache(): void;
|
|
99
|
+
/**
|
|
100
|
+
* BM25 full-text search against the `knowledge_fulltext` index.
|
|
101
|
+
* Returns [] when the index doesn't exist — matches memory-search.ts
|
|
102
|
+
* graceful-fallback semantics so a fresh account with no documents
|
|
103
|
+
* doesn't 500 the caller.
|
|
104
|
+
*/
|
|
105
|
+
export declare function bm25Only(session: Session, params: Bm25OnlyParams): Promise<SearchHit[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Hybrid vector + BM25 search with graph expansion.
|
|
108
|
+
*
|
|
109
|
+
* Sequence:
|
|
110
|
+
* 1. embed(query) — may throw if Ollama is unreachable. When
|
|
111
|
+
* `degradeOnEmbedFailure=true`, caller receives bm25Only() result
|
|
112
|
+
* with `mode: "bm25"`; otherwise the throw propagates.
|
|
113
|
+
* 2. Vector search per label (one query per vector index discovered at
|
|
114
|
+
* boot). Nodes-by-label filter short-circuits when the requested
|
|
115
|
+
* labels have no index.
|
|
116
|
+
* 3. BM25 search on knowledge_fulltext — same filter semantics as
|
|
117
|
+
* vector half (scope/agent/keyword/trashed).
|
|
118
|
+
* 4. Keyword subscriptions (when set): BM25 per keyword + property
|
|
119
|
+
* lookup against `node.keywords` array. Both bypass the agentSlug
|
|
120
|
+
* filter — subscriptions are scope-inclusive by design (matches
|
|
121
|
+
* memory-search.ts comment L319).
|
|
122
|
+
* 5. Merge: 0.7*vector + 0.3*bm25_norm, dedup by nodeId, sort.
|
|
123
|
+
* 6. Graph expand (1 hop by default, 0 to skip) — notTrashed filter +
|
|
124
|
+
* scope + agent clauses mirrored.
|
|
125
|
+
*/
|
|
126
|
+
export declare function hybrid(session: Session, embed: EmbedFn, params: HybridParams): Promise<HybridResponse>;
|
|
127
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAO,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAOjD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,OAAO,EAAE,KAAK,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAO9D;AAMD,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAapF;AAED,yDAAyD;AACzD,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAmBD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,SAAS,EAAE,CAAC,CAoDtB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,MAAM,CAC1B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,cAAc,CAAC,CAuNzB"}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared hybrid search primitive over the Neo4j knowledge graph.
|
|
4
|
+
*
|
|
5
|
+
* Pre-Task-675 there were two BM25 implementations over the same
|
|
6
|
+
* `knowledge_fulltext` index — one in the memory MCP tool, one in the admin
|
|
7
|
+
* Hono route — with divergent filter semantics (soft-delete primitive) and
|
|
8
|
+
* divergent ranking (agent ran hybrid vector+BM25, UI ran BM25-only). Task
|
|
9
|
+
* 675 collapses both into this lib; memory MCP + admin route now share
|
|
10
|
+
* a single code path so `/graph` UI ranking matches what the agent sees.
|
|
11
|
+
*
|
|
12
|
+
* QUERY --> EMBED --> VECTOR SEARCH (per index) --> ┐
|
|
13
|
+
* │ ├--> MERGE --> EXPAND --> RESULTS
|
|
14
|
+
* └--> BM25 FULL-TEXT SEARCH -------------------> ┘
|
|
15
|
+
*
|
|
16
|
+
* Hybrid merge: normalise BM25 scores to [0,1] via min-max, then combine
|
|
17
|
+
* combined = 0.7 * vector_score + 0.3 * normalised_bm25_score
|
|
18
|
+
* Dedup by nodeId, keeping the higher combined score.
|
|
19
|
+
*
|
|
20
|
+
* Trashed nodes (`:Trashed` label + legacy `deletedAt` property) are
|
|
21
|
+
* excluded from every path via `notTrashed()` from `graph-trash`.
|
|
22
|
+
*
|
|
23
|
+
* The lib is STATELESS. Callers pass a `Session` and, for hybrid, an
|
|
24
|
+
* `embed` function. This keeps the lib independent of Ollama config and
|
|
25
|
+
* free of circular imports against each caller's own getSession/embed
|
|
26
|
+
* (memory MCP and the Hono ui build each have their own copies for
|
|
27
|
+
* cross-build-boundary reasons — see neo4j-store.ts:68 comment).
|
|
28
|
+
*
|
|
29
|
+
* Log emission is the caller's responsibility — the lib throws or returns
|
|
30
|
+
* `mode` in the result so the caller can format its own `[graph-search]`
|
|
31
|
+
* line with the prefix and fields it wants.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.escapeLucene = escapeLucene;
|
|
35
|
+
exports.normaliseBm25Scores = normaliseBm25Scores;
|
|
36
|
+
exports.discoverIndexes = discoverIndexes;
|
|
37
|
+
exports.clearIndexCache = clearIndexCache;
|
|
38
|
+
exports.bm25Only = bm25Only;
|
|
39
|
+
exports.hybrid = hybrid;
|
|
40
|
+
const neo4j_driver_1 = require("neo4j-driver");
|
|
41
|
+
const index_js_1 = require("../../graph-trash/dist/index.js");
|
|
42
|
+
const VECTOR_WEIGHT = 0.7;
|
|
43
|
+
const BM25_WEIGHT = 0.3;
|
|
44
|
+
const FULLTEXT_INDEX_NAME = "knowledge_fulltext";
|
|
45
|
+
/**
|
|
46
|
+
* Lucene-escape special characters per Neo4j's fulltext query grammar.
|
|
47
|
+
* `/[+\-&|!(){}[\]^"~*?:\\/]/g` — matches memory-search.ts:127 and
|
|
48
|
+
* neo4j-store.ts:2521 verbatim; consolidation preserves escape set.
|
|
49
|
+
*/
|
|
50
|
+
function escapeLucene(query) {
|
|
51
|
+
return query.replace(/[+\-&|!(){}[\]^"~*?:\\/]/g, "\\$&");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Normalise BM25 scores to [0, 1] using min-max within the result set.
|
|
55
|
+
* If all scores are equal (or single result), returns 1.0 for all —
|
|
56
|
+
* the range-zero branch prevents divide-by-zero and keeps the combined
|
|
57
|
+
* score meaningful.
|
|
58
|
+
*/
|
|
59
|
+
function normaliseBm25Scores(scores) {
|
|
60
|
+
if (scores.length === 0)
|
|
61
|
+
return [];
|
|
62
|
+
const min = Math.min(...scores);
|
|
63
|
+
const max = Math.max(...scores);
|
|
64
|
+
const range = max - min;
|
|
65
|
+
if (range === 0)
|
|
66
|
+
return scores.map(() => 1.0);
|
|
67
|
+
return scores.map((s) => (s - min) / range);
|
|
68
|
+
}
|
|
69
|
+
// Module-scope vector-index cache — discovered from Neo4j at first query.
|
|
70
|
+
// Per-process singleton matches memory-search.ts behaviour.
|
|
71
|
+
let indexCache = null;
|
|
72
|
+
async function discoverIndexes(session) {
|
|
73
|
+
if (indexCache)
|
|
74
|
+
return indexCache;
|
|
75
|
+
const result = await session.run(`SHOW INDEXES YIELD name, labelsOrTypes, type WHERE type = 'VECTOR' RETURN name, labelsOrTypes`);
|
|
76
|
+
const fresh = new Map();
|
|
77
|
+
for (const record of result.records) {
|
|
78
|
+
const name = record.get("name");
|
|
79
|
+
const labels = record.get("labelsOrTypes");
|
|
80
|
+
for (const label of labels)
|
|
81
|
+
fresh.set(label, name);
|
|
82
|
+
}
|
|
83
|
+
indexCache = fresh;
|
|
84
|
+
return fresh;
|
|
85
|
+
}
|
|
86
|
+
/** Clear the index cache — call after schema changes. */
|
|
87
|
+
function clearIndexCache() {
|
|
88
|
+
indexCache = null;
|
|
89
|
+
}
|
|
90
|
+
function buildKeywordFilter(keywords, keywordMatch = "any") {
|
|
91
|
+
if (!keywords || keywords.length === 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
const fn = keywordMatch === "all" ? "ALL" : "ANY";
|
|
94
|
+
return {
|
|
95
|
+
clause: `AND (node.keywords IS NULL OR ${fn}(kw IN $keywords WHERE kw IN node.keywords))`,
|
|
96
|
+
params: { keywords: keywords.map((k) => k.toLowerCase().trim()) },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* BM25 full-text search against the `knowledge_fulltext` index.
|
|
101
|
+
* Returns [] when the index doesn't exist — matches memory-search.ts
|
|
102
|
+
* graceful-fallback semantics so a fresh account with no documents
|
|
103
|
+
* doesn't 500 the caller.
|
|
104
|
+
*/
|
|
105
|
+
async function bm25Only(session, params) {
|
|
106
|
+
const { query, accountId, limit, allowedScopes, agentSlug, keywords, keywordMatch } = params;
|
|
107
|
+
const scopeClause = allowedScopes
|
|
108
|
+
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
109
|
+
: "";
|
|
110
|
+
const agentClause = agentSlug
|
|
111
|
+
? "AND node.agents IS NOT NULL AND $agentSlug IN node.agents"
|
|
112
|
+
: "";
|
|
113
|
+
const keywordFilter = buildKeywordFilter(keywords, keywordMatch);
|
|
114
|
+
const kwClause = keywordFilter?.clause ?? "";
|
|
115
|
+
const escaped = escapeLucene(query);
|
|
116
|
+
try {
|
|
117
|
+
const result = await session.run(`CALL db.index.fulltext.queryNodes($indexName, $query)
|
|
118
|
+
YIELD node, score
|
|
119
|
+
WHERE node.accountId = $accountId
|
|
120
|
+
${scopeClause}
|
|
121
|
+
${agentClause}
|
|
122
|
+
AND ${(0, index_js_1.notTrashed)("node")}
|
|
123
|
+
${kwClause}
|
|
124
|
+
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
125
|
+
ORDER BY score DESC
|
|
126
|
+
LIMIT $limit`, {
|
|
127
|
+
indexName: FULLTEXT_INDEX_NAME,
|
|
128
|
+
query: escaped,
|
|
129
|
+
accountId,
|
|
130
|
+
limit: (0, neo4j_driver_1.int)(limit),
|
|
131
|
+
...(allowedScopes ? { allowedScopes } : {}),
|
|
132
|
+
...(agentSlug ? { agentSlug } : {}),
|
|
133
|
+
...(keywordFilter?.params ?? {}),
|
|
134
|
+
});
|
|
135
|
+
return result.records.map((r) => {
|
|
136
|
+
const scoreRaw = r.get("score");
|
|
137
|
+
const score = typeof scoreRaw === "number" ? scoreRaw : Number(scoreRaw);
|
|
138
|
+
const node = r.get("node");
|
|
139
|
+
return {
|
|
140
|
+
nodeId: r.get("nodeId"),
|
|
141
|
+
labels: r.get("nodeLabels"),
|
|
142
|
+
properties: plainProperties(node.properties),
|
|
143
|
+
score,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
if (msg.includes("index") || msg.includes("fulltext") || msg.includes("not found")) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Hybrid vector + BM25 search with graph expansion.
|
|
157
|
+
*
|
|
158
|
+
* Sequence:
|
|
159
|
+
* 1. embed(query) — may throw if Ollama is unreachable. When
|
|
160
|
+
* `degradeOnEmbedFailure=true`, caller receives bm25Only() result
|
|
161
|
+
* with `mode: "bm25"`; otherwise the throw propagates.
|
|
162
|
+
* 2. Vector search per label (one query per vector index discovered at
|
|
163
|
+
* boot). Nodes-by-label filter short-circuits when the requested
|
|
164
|
+
* labels have no index.
|
|
165
|
+
* 3. BM25 search on knowledge_fulltext — same filter semantics as
|
|
166
|
+
* vector half (scope/agent/keyword/trashed).
|
|
167
|
+
* 4. Keyword subscriptions (when set): BM25 per keyword + property
|
|
168
|
+
* lookup against `node.keywords` array. Both bypass the agentSlug
|
|
169
|
+
* filter — subscriptions are scope-inclusive by design (matches
|
|
170
|
+
* memory-search.ts comment L319).
|
|
171
|
+
* 5. Merge: 0.7*vector + 0.3*bm25_norm, dedup by nodeId, sort.
|
|
172
|
+
* 6. Graph expand (1 hop by default, 0 to skip) — notTrashed filter +
|
|
173
|
+
* scope + agent clauses mirrored.
|
|
174
|
+
*/
|
|
175
|
+
async function hybrid(session, embed, params) {
|
|
176
|
+
const { query, labels, accountId, limit, allowedScopes, keywords, keywordMatch = "any", agentSlug, keywordSubscriptions, expandHops = 1, degradeOnEmbedFailure = false, } = params;
|
|
177
|
+
let queryEmbedding;
|
|
178
|
+
try {
|
|
179
|
+
queryEmbedding = await embed(query);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
if (!degradeOnEmbedFailure)
|
|
183
|
+
throw err;
|
|
184
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
185
|
+
const bm25Hits = await bm25Only(session, params);
|
|
186
|
+
const results = bm25Hits.map((h) => ({ ...h, related: [] }));
|
|
187
|
+
return { mode: "bm25", results, embedError: msg };
|
|
188
|
+
}
|
|
189
|
+
const labelToIndex = await discoverIndexes(session);
|
|
190
|
+
const keywordFilter = buildKeywordFilter(keywords, keywordMatch);
|
|
191
|
+
const keywordClause = keywordFilter?.clause ?? "";
|
|
192
|
+
const keywordParams = keywordFilter?.params ?? {};
|
|
193
|
+
const scoreMap = new Map();
|
|
194
|
+
// --- Vector search (per label) ---
|
|
195
|
+
let indexesToQuery;
|
|
196
|
+
if (labels && labels.length > 0) {
|
|
197
|
+
indexesToQuery = labels
|
|
198
|
+
.map((l) => labelToIndex.get(l))
|
|
199
|
+
.filter((idx) => idx !== undefined);
|
|
200
|
+
if (indexesToQuery.length === 0) {
|
|
201
|
+
return { mode: "hybrid", results: [] };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
indexesToQuery = [...new Set(labelToIndex.values())];
|
|
206
|
+
}
|
|
207
|
+
const scopeClause = allowedScopes
|
|
208
|
+
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
209
|
+
: "";
|
|
210
|
+
const scopeParams = allowedScopes ? { allowedScopes } : {};
|
|
211
|
+
const agentClause = agentSlug
|
|
212
|
+
? "AND node.agents IS NOT NULL AND $agentSlug IN node.agents"
|
|
213
|
+
: "";
|
|
214
|
+
const agentParams = agentSlug ? { agentSlug } : {};
|
|
215
|
+
for (const indexName of indexesToQuery) {
|
|
216
|
+
const vectorResult = await session.run(`CALL db.index.vector.queryNodes($indexName, $limit, $embedding)
|
|
217
|
+
YIELD node, score
|
|
218
|
+
WHERE node.accountId = $accountId
|
|
219
|
+
${scopeClause}
|
|
220
|
+
${agentClause}
|
|
221
|
+
AND ${(0, index_js_1.notTrashed)("node")}
|
|
222
|
+
${keywordClause}
|
|
223
|
+
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
224
|
+
ORDER BY score DESC
|
|
225
|
+
LIMIT $limit`, {
|
|
226
|
+
indexName,
|
|
227
|
+
embedding: queryEmbedding,
|
|
228
|
+
limit: (0, neo4j_driver_1.int)(limit),
|
|
229
|
+
accountId,
|
|
230
|
+
...scopeParams,
|
|
231
|
+
...agentParams,
|
|
232
|
+
...keywordParams,
|
|
233
|
+
});
|
|
234
|
+
for (const record of vectorResult.records) {
|
|
235
|
+
const nodeId = record.get("nodeId");
|
|
236
|
+
const scoreRaw = record.get("score");
|
|
237
|
+
const score = typeof scoreRaw === "number" ? scoreRaw : Number(scoreRaw);
|
|
238
|
+
const existing = scoreMap.get(nodeId);
|
|
239
|
+
if (existing) {
|
|
240
|
+
existing.vectorScore = Math.max(existing.vectorScore, score);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const node = record.get("node");
|
|
244
|
+
scoreMap.set(nodeId, {
|
|
245
|
+
nodeId,
|
|
246
|
+
labels: record.get("nodeLabels"),
|
|
247
|
+
properties: plainProperties(node.properties),
|
|
248
|
+
vectorScore: score,
|
|
249
|
+
bm25Score: 0,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// --- BM25 half ---
|
|
255
|
+
const bm25Hits = await bm25Only(session, params);
|
|
256
|
+
if (bm25Hits.length > 0) {
|
|
257
|
+
const rawScores = bm25Hits.map((h) => h.score);
|
|
258
|
+
const normalised = normaliseBm25Scores(rawScores);
|
|
259
|
+
for (let i = 0; i < bm25Hits.length; i++) {
|
|
260
|
+
mergeBm25Hit(scoreMap, bm25Hits[i], normalised[i]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// --- Keyword subscriptions (scope-inclusive, agentSlug-bypassing) ---
|
|
264
|
+
if (keywordSubscriptions && keywordSubscriptions.length > 0) {
|
|
265
|
+
for (const kw of keywordSubscriptions) {
|
|
266
|
+
const kwHits = await bm25Only(session, {
|
|
267
|
+
query: kw,
|
|
268
|
+
accountId,
|
|
269
|
+
limit,
|
|
270
|
+
allowedScopes,
|
|
271
|
+
});
|
|
272
|
+
if (kwHits.length === 0)
|
|
273
|
+
continue;
|
|
274
|
+
const rawScores = kwHits.map((h) => h.score);
|
|
275
|
+
const normalised = normaliseBm25Scores(rawScores);
|
|
276
|
+
for (let i = 0; i < kwHits.length; i++) {
|
|
277
|
+
mergeBm25Hit(scoreMap, kwHits[i], normalised[i]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const propScopeClause = allowedScopes
|
|
281
|
+
? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)"
|
|
282
|
+
: "";
|
|
283
|
+
const propResult = await session.run(`MATCH (node)
|
|
284
|
+
WHERE node.accountId = $accountId
|
|
285
|
+
AND ${(0, index_js_1.notTrashed)("node")}
|
|
286
|
+
AND node.keywords IS NOT NULL
|
|
287
|
+
AND ANY(kw IN $kwSubs WHERE ANY(nk IN node.keywords WHERE toLower(nk) = kw))
|
|
288
|
+
${propScopeClause}
|
|
289
|
+
RETURN node, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
290
|
+
LIMIT $limit`, {
|
|
291
|
+
accountId,
|
|
292
|
+
kwSubs: keywordSubscriptions,
|
|
293
|
+
limit: (0, neo4j_driver_1.int)(limit),
|
|
294
|
+
...(allowedScopes ? { allowedScopes } : {}),
|
|
295
|
+
});
|
|
296
|
+
for (const record of propResult.records) {
|
|
297
|
+
const nodeId = record.get("nodeId");
|
|
298
|
+
const existing = scoreMap.get(nodeId);
|
|
299
|
+
if (existing) {
|
|
300
|
+
existing.bm25Score = Math.max(existing.bm25Score, 1.0);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const node = record.get("node");
|
|
304
|
+
scoreMap.set(nodeId, {
|
|
305
|
+
nodeId,
|
|
306
|
+
labels: record.get("nodeLabels"),
|
|
307
|
+
properties: plainProperties(node.properties),
|
|
308
|
+
vectorScore: 0,
|
|
309
|
+
bm25Score: 1.0,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// --- Merge & rank ---
|
|
315
|
+
const merged = [...scoreMap.values()]
|
|
316
|
+
.map((node) => ({
|
|
317
|
+
...node,
|
|
318
|
+
combinedScore: VECTOR_WEIGHT * node.vectorScore + BM25_WEIGHT * node.bm25Score,
|
|
319
|
+
}))
|
|
320
|
+
.sort((a, b) => b.combinedScore - a.combinedScore)
|
|
321
|
+
.slice(0, limit);
|
|
322
|
+
// --- Graph expand ---
|
|
323
|
+
const results = [];
|
|
324
|
+
for (const node of merged) {
|
|
325
|
+
const result = {
|
|
326
|
+
nodeId: node.nodeId,
|
|
327
|
+
labels: node.labels,
|
|
328
|
+
properties: node.properties,
|
|
329
|
+
score: node.combinedScore,
|
|
330
|
+
related: [],
|
|
331
|
+
};
|
|
332
|
+
if (expandHops > 0) {
|
|
333
|
+
const expandScopeClause = allowedScopes
|
|
334
|
+
? "AND (related.scope IS NULL OR related.scope IN $allowedScopes)"
|
|
335
|
+
: "";
|
|
336
|
+
const expandAgentClause = agentSlug
|
|
337
|
+
? "AND (related.agents IS NULL OR $agentSlug IN related.agents)"
|
|
338
|
+
: "";
|
|
339
|
+
const expandResult = await session.run(`MATCH (n)-[r]-(related)
|
|
340
|
+
WHERE elementId(n) = $nodeId
|
|
341
|
+
AND ${(0, index_js_1.notTrashed)("related")}
|
|
342
|
+
${expandScopeClause}
|
|
343
|
+
${expandAgentClause}
|
|
344
|
+
RETURN type(r) AS relType,
|
|
345
|
+
CASE WHEN startNode(r) = n THEN 'outgoing' ELSE 'incoming' END AS direction,
|
|
346
|
+
labels(related) AS relatedLabels,
|
|
347
|
+
related
|
|
348
|
+
LIMIT 20`, { nodeId: node.nodeId, ...scopeParams, ...agentParams });
|
|
349
|
+
for (const rec of expandResult.records) {
|
|
350
|
+
const related = rec.get("related");
|
|
351
|
+
result.related.push({
|
|
352
|
+
relationship: rec.get("relType"),
|
|
353
|
+
direction: rec.get("direction"),
|
|
354
|
+
labels: rec.get("relatedLabels"),
|
|
355
|
+
properties: plainProperties(related.properties),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
results.push(result);
|
|
360
|
+
}
|
|
361
|
+
return { mode: "hybrid", results };
|
|
362
|
+
}
|
|
363
|
+
function mergeBm25Hit(map, hit, normalisedScore) {
|
|
364
|
+
const existing = map.get(hit.nodeId);
|
|
365
|
+
if (existing) {
|
|
366
|
+
existing.bm25Score = Math.max(existing.bm25Score, normalisedScore);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
map.set(hit.nodeId, {
|
|
370
|
+
nodeId: hit.nodeId,
|
|
371
|
+
labels: hit.labels,
|
|
372
|
+
properties: hit.properties,
|
|
373
|
+
vectorScore: 0,
|
|
374
|
+
bm25Score: normalisedScore,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/** Strip `embedding` and unwrap Neo4j Integers into JS numbers. */
|
|
379
|
+
function plainProperties(properties) {
|
|
380
|
+
const plain = {};
|
|
381
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
382
|
+
if (key === "embedding")
|
|
383
|
+
continue;
|
|
384
|
+
if (value && typeof value === "object" && "toNumber" in value) {
|
|
385
|
+
plain[key] = value.toNumber();
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
plain[key] = value;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return plain;
|
|
392
|
+
}
|
|
393
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|