@rubytech/create-realagent 1.0.705 → 1.0.706
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/__tests__/apt-resolve.test.js +179 -0
- package/dist/apt-resolve.js +73 -0
- package/dist/index.js +48 -46
- package/package.json +3 -3
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +89 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +42 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +87 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +99 -0
- package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +84 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +30 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +46 -5
- package/payload/platform/plugins/memory/PLUGIN.md +3 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +56 -6
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +92 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +51 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +222 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +16 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +38 -11
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +136 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +180 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +11 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +6 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +44 -22
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +94 -57
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +7 -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 +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +4 -0
- package/payload/server/chunk-PE76FPYP.js +12040 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{Checkbox-B2Lk8F4X.js → Checkbox-CjbS9JcG.js} +1 -1
- package/payload/server/public/assets/{admin-agtgi48Q.js → admin-Ce9DbUuu.js} +1 -1
- package/payload/server/public/assets/{data-B7nsyBTV.js → data-C-SxjLC9.js} +1 -1
- package/payload/server/public/assets/{file-DHWTu8LP.js → file-D4cbAAuo.js} +1 -1
- package/payload/server/public/assets/{graph-ChDwqqhJ.js → graph-D-Rqh0Md.js} +1 -1
- package/payload/server/public/assets/{house-CfjnRPO6.js → house-CYsVygEQ.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-81wg0w0Q.css → jsx-runtime-DPXE45W9.css} +1 -1
- package/payload/server/public/assets/{public-CE1kyVnz.js → public-BTOF98iO.js} +1 -1
- package/payload/server/public/assets/{share-2-CAd1beVT.js → share-2-B-sbkB36.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-LSAU68Eo.js → useVoiceRecorder-DLVFx3ms.js} +1 -1
- package/payload/server/public/assets/{x-B0xK3Aoq.js → x-BNidzSAn.js} +1 -1
- package/payload/server/public/data.html +6 -6
- package/payload/server/public/graph.html +7 -7
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +1 -1
- /package/payload/server/public/assets/{jsx-runtime-DhzH26q8.js → jsx-runtime-BUs3sHtV.js} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared parser for `platform/neo4j/schema.cypher`.
|
|
4
|
+
*
|
|
5
|
+
* Two consumers today:
|
|
6
|
+
* 1. The admin SCHEMA prompt block (`platform/ui/app/lib/admin-schema-block.ts`,
|
|
7
|
+
* Task 703) renders labels declared in schema.cypher into the agent's
|
|
8
|
+
* system prompt.
|
|
9
|
+
* 2. The memory-plugin schema validator (Task 736) treats schema.cypher
|
|
10
|
+
* declarations as the "promise" half of the recognised-label set —
|
|
11
|
+
* labels that exist as constraints/indexes but may not yet have a node
|
|
12
|
+
* in the graph (fresh-install bootstrap).
|
|
13
|
+
*
|
|
14
|
+
* The previous home was admin-schema-block.ts; that owner was wrong as
|
|
15
|
+
* soon as a second consumer arrived. Lifting here keeps a single source of
|
|
16
|
+
* truth and removes the cross-package shape coupling.
|
|
17
|
+
*
|
|
18
|
+
* Also exports a compact `levenshtein` for "did you mean?" suggestions on
|
|
19
|
+
* unknown labels — used by the memory-plugin validator. The graph-mcp
|
|
20
|
+
* cypher validator has its own copy in schema-cache.ts (private to the
|
|
21
|
+
* stale-miss heuristic); the duplication is intentional, since this
|
|
22
|
+
* exported one targets human-readable error UX, not refresh debouncing.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.parseLabelsFromSchemaCypher = parseLabelsFromSchemaCypher;
|
|
26
|
+
exports.levenshtein = levenshtein;
|
|
27
|
+
exports.nearestLabel = nearestLabel;
|
|
28
|
+
const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
|
|
29
|
+
/**
|
|
30
|
+
* Extract every Neo4j label declared in a `schema.cypher` file by scanning
|
|
31
|
+
* `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
|
|
32
|
+
* list. Pure function — no I/O.
|
|
33
|
+
*/
|
|
34
|
+
function parseLabelsFromSchemaCypher(schemaCypher) {
|
|
35
|
+
const found = new Set();
|
|
36
|
+
for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
|
|
37
|
+
found.add(match[1]);
|
|
38
|
+
}
|
|
39
|
+
return [...found].sort();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Standard iterative-DP Levenshtein. Used to suggest a near-match when an
|
|
43
|
+
* agent submits an unknown label — small alphabets, short strings, runs in
|
|
44
|
+
* microseconds. Identical algorithm to the private copy in schema-cache.ts;
|
|
45
|
+
* exported here so callers outside graph-mcp don't have to roll their own.
|
|
46
|
+
*/
|
|
47
|
+
function levenshtein(a, b) {
|
|
48
|
+
if (a === b)
|
|
49
|
+
return 0;
|
|
50
|
+
if (a.length === 0)
|
|
51
|
+
return b.length;
|
|
52
|
+
if (b.length === 0)
|
|
53
|
+
return a.length;
|
|
54
|
+
let prev = new Array(b.length + 1);
|
|
55
|
+
let curr = new Array(b.length + 1);
|
|
56
|
+
for (let j = 0; j <= b.length; j++)
|
|
57
|
+
prev[j] = j;
|
|
58
|
+
for (let i = 1; i <= a.length; i++) {
|
|
59
|
+
curr[0] = i;
|
|
60
|
+
for (let j = 1; j <= b.length; j++) {
|
|
61
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
62
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
63
|
+
}
|
|
64
|
+
[prev, curr] = [curr, prev];
|
|
65
|
+
}
|
|
66
|
+
return prev[b.length];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Find the closest label in `candidates` to `unknown` by edit distance.
|
|
70
|
+
* Returns null when the closest match is further than `maxDistance` (default
|
|
71
|
+
* 3) — beyond that a suggestion is more confusing than helpful.
|
|
72
|
+
*/
|
|
73
|
+
function nearestLabel(unknown, candidates, maxDistance = 3) {
|
|
74
|
+
let best = null;
|
|
75
|
+
let bestDist = Infinity;
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
const d = levenshtein(unknown, candidate);
|
|
78
|
+
if (d < bestDist) {
|
|
79
|
+
bestDist = d;
|
|
80
|
+
best = candidate;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (best === null || bestDist > maxDistance)
|
|
84
|
+
return null;
|
|
85
|
+
return best;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=schema-cypher-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-cypher-parser.js","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AASH,kEAMC;AAQD,kCAgBC;AAOD,oCAgBC;AA5DD,MAAM,aAAa,GAAG,kCAAkC,CAAC;AAEzD;;;;GAIG;AACH,SAAgB,2BAA2B,CAAC,YAAoB;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA4B,EAC5B,WAAW,GAAG,CAAC;IAEf,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACjB,QAAQ,GAAG,CAAC,CAAC;YACb,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,WAAW;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
parseLabelsFromSchemaCypher,
|
|
5
|
+
levenshtein,
|
|
6
|
+
nearestLabel,
|
|
7
|
+
} from "../schema-cypher-parser.js";
|
|
8
|
+
|
|
9
|
+
test("parseLabelsFromSchemaCypher: extracts labels from constraint and index forms", () => {
|
|
10
|
+
const text = `
|
|
11
|
+
CREATE CONSTRAINT person_email IF NOT EXISTS
|
|
12
|
+
FOR (p:Person) REQUIRE p.email IS UNIQUE;
|
|
13
|
+
|
|
14
|
+
CREATE CONSTRAINT business_account IF NOT EXISTS
|
|
15
|
+
FOR (b:LocalBusiness) REQUIRE b.accountId IS UNIQUE;
|
|
16
|
+
|
|
17
|
+
CREATE INDEX admin_user_account IF NOT EXISTS
|
|
18
|
+
FOR (au:AdminUser) ON (au.accountId);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX knowledge_doc IF NOT EXISTS
|
|
21
|
+
FOR (k:KnowledgeDocument) ON (k.embedding) OPTIONS { ... };
|
|
22
|
+
`;
|
|
23
|
+
const labels = parseLabelsFromSchemaCypher(text);
|
|
24
|
+
assert.deepEqual(labels, [
|
|
25
|
+
"AdminUser",
|
|
26
|
+
"KnowledgeDocument",
|
|
27
|
+
"LocalBusiness",
|
|
28
|
+
"Person",
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("parseLabelsFromSchemaCypher: deduplicates labels declared multiple times", () => {
|
|
33
|
+
const text = `
|
|
34
|
+
FOR (p:Person) REQUIRE p.email IS UNIQUE;
|
|
35
|
+
FOR (p:Person) REQUIRE p.telephone IS UNIQUE;
|
|
36
|
+
FOR (p:Person) ON (p.status);
|
|
37
|
+
`;
|
|
38
|
+
assert.deepEqual(parseLabelsFromSchemaCypher(text), ["Person"]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("parseLabelsFromSchemaCypher: returns sorted output", () => {
|
|
42
|
+
const text = `FOR (z:Zebra) ON (z.x); FOR (a:Aardvark) ON (a.x); FOR (m:Mongoose) ON (m.x);`;
|
|
43
|
+
assert.deepEqual(parseLabelsFromSchemaCypher(text), [
|
|
44
|
+
"Aardvark",
|
|
45
|
+
"Mongoose",
|
|
46
|
+
"Zebra",
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("parseLabelsFromSchemaCypher: ignores non-constraint cypher", () => {
|
|
51
|
+
const text = `
|
|
52
|
+
MATCH (n:Person) RETURN n;
|
|
53
|
+
CREATE (b:Business { name: 'X' });
|
|
54
|
+
FOR (p:Person) REQUIRE p.email IS UNIQUE;
|
|
55
|
+
`;
|
|
56
|
+
// MATCH/CREATE patterns don't include FOR, so they're ignored.
|
|
57
|
+
assert.deepEqual(parseLabelsFromSchemaCypher(text), ["Person"]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("parseLabelsFromSchemaCypher: empty input returns empty array", () => {
|
|
61
|
+
assert.deepEqual(parseLabelsFromSchemaCypher(""), []);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("levenshtein: identical strings", () => {
|
|
65
|
+
assert.equal(levenshtein("Person", "Person"), 0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("levenshtein: substitution distance", () => {
|
|
69
|
+
// LocaIBusiness vs LocalBusiness — capital-I-as-l typo from the incident.
|
|
70
|
+
assert.equal(levenshtein("LocaIBusiness", "LocalBusiness"), 1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("levenshtein: insertion + deletion", () => {
|
|
74
|
+
assert.equal(levenshtein("Person", "Persons"), 1);
|
|
75
|
+
assert.equal(levenshtein("Persons", "Person"), 1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("levenshtein: handles empty strings", () => {
|
|
79
|
+
assert.equal(levenshtein("", "abc"), 3);
|
|
80
|
+
assert.equal(levenshtein("abc", ""), 3);
|
|
81
|
+
assert.equal(levenshtein("", ""), 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("nearestLabel: finds the closest match", () => {
|
|
85
|
+
const candidates = ["Person", "LocalBusiness", "AdminUser"];
|
|
86
|
+
assert.equal(nearestLabel("LocaIBusiness", candidates), "LocalBusiness");
|
|
87
|
+
assert.equal(nearestLabel("AdminUsr", candidates), "AdminUser");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("nearestLabel: returns null when no match within maxDistance", () => {
|
|
91
|
+
const candidates = ["Person", "LocalBusiness"];
|
|
92
|
+
assert.equal(nearestLabel("Quokka", candidates), null);
|
|
93
|
+
// Default maxDistance is 3; "abcde" is too far from any candidate.
|
|
94
|
+
assert.equal(nearestLabel("abcde", candidates, 1), null);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("nearestLabel: empty candidates returns null", () => {
|
|
98
|
+
assert.equal(nearestLabel("Person", []), null);
|
|
99
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parser for `platform/neo4j/schema.cypher`.
|
|
3
|
+
*
|
|
4
|
+
* Two consumers today:
|
|
5
|
+
* 1. The admin SCHEMA prompt block (`platform/ui/app/lib/admin-schema-block.ts`,
|
|
6
|
+
* Task 703) renders labels declared in schema.cypher into the agent's
|
|
7
|
+
* system prompt.
|
|
8
|
+
* 2. The memory-plugin schema validator (Task 736) treats schema.cypher
|
|
9
|
+
* declarations as the "promise" half of the recognised-label set —
|
|
10
|
+
* labels that exist as constraints/indexes but may not yet have a node
|
|
11
|
+
* in the graph (fresh-install bootstrap).
|
|
12
|
+
*
|
|
13
|
+
* The previous home was admin-schema-block.ts; that owner was wrong as
|
|
14
|
+
* soon as a second consumer arrived. Lifting here keeps a single source of
|
|
15
|
+
* truth and removes the cross-package shape coupling.
|
|
16
|
+
*
|
|
17
|
+
* Also exports a compact `levenshtein` for "did you mean?" suggestions on
|
|
18
|
+
* unknown labels — used by the memory-plugin validator. The graph-mcp
|
|
19
|
+
* cypher validator has its own copy in schema-cache.ts (private to the
|
|
20
|
+
* stale-miss heuristic); the duplication is intentional, since this
|
|
21
|
+
* exported one targets human-readable error UX, not refresh debouncing.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract every Neo4j label declared in a `schema.cypher` file by scanning
|
|
28
|
+
* `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
|
|
29
|
+
* list. Pure function — no I/O.
|
|
30
|
+
*/
|
|
31
|
+
export function parseLabelsFromSchemaCypher(schemaCypher: string): string[] {
|
|
32
|
+
const found = new Set<string>();
|
|
33
|
+
for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
|
|
34
|
+
found.add(match[1]);
|
|
35
|
+
}
|
|
36
|
+
return [...found].sort();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Standard iterative-DP Levenshtein. Used to suggest a near-match when an
|
|
41
|
+
* agent submits an unknown label — small alphabets, short strings, runs in
|
|
42
|
+
* microseconds. Identical algorithm to the private copy in schema-cache.ts;
|
|
43
|
+
* exported here so callers outside graph-mcp don't have to roll their own.
|
|
44
|
+
*/
|
|
45
|
+
export function levenshtein(a: string, b: string): number {
|
|
46
|
+
if (a === b) return 0;
|
|
47
|
+
if (a.length === 0) return b.length;
|
|
48
|
+
if (b.length === 0) return a.length;
|
|
49
|
+
let prev = new Array<number>(b.length + 1);
|
|
50
|
+
let curr = new Array<number>(b.length + 1);
|
|
51
|
+
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
|
52
|
+
for (let i = 1; i <= a.length; i++) {
|
|
53
|
+
curr[0] = i;
|
|
54
|
+
for (let j = 1; j <= b.length; j++) {
|
|
55
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
56
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
57
|
+
}
|
|
58
|
+
[prev, curr] = [curr, prev];
|
|
59
|
+
}
|
|
60
|
+
return prev[b.length];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Find the closest label in `candidates` to `unknown` by edit distance.
|
|
65
|
+
* Returns null when the closest match is further than `maxDistance` (default
|
|
66
|
+
* 3) — beyond that a suggestion is more confusing than helpful.
|
|
67
|
+
*/
|
|
68
|
+
export function nearestLabel(
|
|
69
|
+
unknown: string,
|
|
70
|
+
candidates: Iterable<string>,
|
|
71
|
+
maxDistance = 3,
|
|
72
|
+
): string | null {
|
|
73
|
+
let best: string | null = null;
|
|
74
|
+
let bestDist = Infinity;
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const d = levenshtein(unknown, candidate);
|
|
77
|
+
if (d < bestDist) {
|
|
78
|
+
bestDist = d;
|
|
79
|
+
best = candidate;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (best === null || bestDist > maxDistance) return null;
|
|
83
|
+
return best;
|
|
84
|
+
}
|
|
@@ -1709,6 +1709,36 @@ server.tool("onboarding-complete-step", "Mark an onboarding step as completed. S
|
|
|
1709
1709
|
};
|
|
1710
1710
|
}
|
|
1711
1711
|
});
|
|
1712
|
+
// Task 704: pin the operator's persona (personal/business-owner/employee) at
|
|
1713
|
+
// step 9 BEFORE any graph write happens. The agent calls this immediately on
|
|
1714
|
+
// the user's single-select submission. The tool's job is twofold:
|
|
1715
|
+
// 1. Emit a deterministic [onboarding-step9-mode] log line for the
|
|
1716
|
+
// diagnostic-grep path on the Pi (matches [onboarding-step-complete]
|
|
1717
|
+
// format — same accountId-prefix slicing, same surrounding code).
|
|
1718
|
+
// 2. Return the next-action prose so the agent's branching is anchored to
|
|
1719
|
+
// the tool surface, not free-text inference.
|
|
1720
|
+
// No persistence — the graph state (LocalBusiness exists vs Person with
|
|
1721
|
+
// role=admin-personal exists) remains the source of truth for gate
|
|
1722
|
+
// satisfaction. Re-calling this tool with a different mode on the same
|
|
1723
|
+
// session is allowed; the most recent log line wins for diagnostic purposes.
|
|
1724
|
+
server.tool("onboarding-step9-mode", "Step 9 onboarding fork: record the operator's persona (personal, business-owner, " +
|
|
1725
|
+
"or employee) before any graph write. Logs the choice for diagnostic grep and " +
|
|
1726
|
+
"returns the deterministic next-action prose for the agent.", {
|
|
1727
|
+
mode: z
|
|
1728
|
+
.enum(["personal", "business-owner", "employee"])
|
|
1729
|
+
.describe("The operator's persona: 'personal' for an individual user with no business, 'business-owner' for someone setting up Maxy as the operations agent for their company, or 'employee' for an employee whose employer is NOT being registered."),
|
|
1730
|
+
}, async ({ mode }) => {
|
|
1731
|
+
console.log(`[onboarding-step9-mode] accountId=${ACCOUNT_ID.slice(0, 8)}… mode=${mode}`);
|
|
1732
|
+
const nextAction = mode === "business-owner"
|
|
1733
|
+
? "Invoke the `business-profile` skill to create AdminUser + LocalBusiness and capture the business identity. Mark step 9 complete only after both nodes exist in the graph."
|
|
1734
|
+
: "Ask the user for their email (one sentence). Use their name from `admin-identity` in the system prompt; split into givenName + familyName. Call `memory-write` to create the AdminUser node, then call `memory-write` to create a Person node with `role: \"admin-personal\"`, the givenName, familyName, email, and an OWNS edge from the AdminUser. Then call `onboarding-complete-step` with step 9. Do not invoke the `business-profile` skill — personal/employee mode does not register a LocalBusiness.";
|
|
1735
|
+
return {
|
|
1736
|
+
content: [{
|
|
1737
|
+
type: "text",
|
|
1738
|
+
text: JSON.stringify({ mode, nextAction }, null, 2),
|
|
1739
|
+
}],
|
|
1740
|
+
};
|
|
1741
|
+
});
|
|
1712
1742
|
// ===================================================================
|
|
1713
1743
|
// Utility tools
|
|
1714
1744
|
// ===================================================================
|