@rubytech/create-realagent-code 0.1.384 → 0.1.387
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__/cron-heartbeat-registration.test.js +5 -0
- package/dist/cron-registration.js +1 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +36 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +73 -2
- package/payload/platform/plugins/docs/references/admin-ui.md +12 -1
- package/payload/platform/plugins/docs/references/graph.md +11 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
- package/payload/platform/plugins/docs/references/sweep-guide.md +43 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +34 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +17 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +101 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +54 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +117 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js +6 -3
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +88 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +77 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +59 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +131 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +62 -15
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +110 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +35 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +81 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +7 -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 +21 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +32 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
- package/payload/platform/plugins/sweep/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/sweep/PLUGIN.md +15 -0
- package/payload/platform/plugins/sweep/skills/sweep-workflow/SKILL.md +113 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +19 -4
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -4
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
- package/payload/platform/scripts/seed-neo4j.sh +38 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +5 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/premium-plugins/real-agent/agents/negotiator.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-sales/PLUGIN.md +41 -8
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/index.d.ts +2 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/index.js +36 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/index.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/chain-graph.d.ts +131 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/chain-graph.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/chain-graph.js +379 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/chain-graph.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/neo4j.js +40 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-read.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-read.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-read.js +45 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-read.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-upsert.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-upsert.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-upsert.js +94 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-chain-upsert.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-milestone-set.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-milestone-set.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-milestone-set.js +40 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-milestone-set.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-party-lookup.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-party-lookup.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-party-lookup.js +45 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-party-lookup.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-pipeline-list.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-pipeline-list.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-pipeline-list.js +38 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/dist/tools/progression-pipeline-list.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/package-lock.json +2587 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/package.json +21 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/__tests__/round-trip.test.ts +251 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/index.ts +43 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/lib/chain-graph.ts +555 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/lib/neo4j.ts +45 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/tools/progression-chain-read.ts +63 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/tools/progression-chain-upsert.ts +125 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/tools/progression-milestone-set.ts +56 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/tools/progression-party-lookup.ts +53 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/tools/progression-pipeline-list.ts +49 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/tsconfig.json +20 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/skills/negotiation/references/offer-presentation.md +2 -2
- package/payload/premium-plugins/real-agent/plugins/estate-sales/skills/progression-inbox/SKILL.md +35 -0
- package/payload/premium-plugins/real-agent/plugins/estate-sales/skills/sales-progression/SKILL.md +17 -6
- package/payload/premium-plugins/real-agent/plugins/estate-sales/skills/sales-progression/references/transaction-tracking.md +10 -6
- package/payload/premium-plugins/real-agent/plugins/leads/skills/chain-progression-tracker/SKILL.md +10 -1
- package/payload/server/public/assets/{AdminLoginScreens-C_h8V0Xs.js → AdminLoginScreens-BejIjbmU.js} +1 -1
- package/payload/server/public/assets/{AdminShell-Dqnxpn7U.js → AdminShell-D2-uBSB5.js} +1 -1
- package/payload/server/public/assets/{Checkbox-Bl1WRVGb.js → Checkbox-1F1tzqca.js} +1 -1
- package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
- package/payload/server/public/assets/{admin-ByoqNaSf.js → admin-DqQARkjy.js} +1 -1
- package/payload/server/public/assets/{browser-BXY1sNxv.js → browser-nDtEK6RC.js} +1 -1
- package/payload/server/public/assets/{calendar-CObRTPpU.js → calendar-CO4Bwmho.js} +1 -1
- package/payload/server/public/assets/chat-DeIge_bC.js +1 -0
- package/payload/server/public/assets/chevron-left-DhVdq0aN.js +1 -0
- package/payload/server/public/assets/data-B1GIdzHk.js +1 -0
- package/payload/server/public/assets/{graph-BCL0fVDH.js → graph-DFyicKd7.js} +1 -1
- package/payload/server/public/assets/{graph-labels-C0nn9nQi.js → graph-labels-D3BQdcpD.js} +1 -1
- package/payload/server/public/assets/{operator-B_1qYO7m.js → operator-BxrjWdT9.js} +1 -1
- package/payload/server/public/assets/{page-BQEl7aZO.js → page-ByDLq_o1.js} +1 -1
- package/payload/server/public/assets/page-D-Ep4bXd.js +32 -0
- package/payload/server/public/assets/{public-BzvwPHM0.js → public-DbdqfdYq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-CHep9vl5.js → rotate-ccw-BkX8HODe.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-B1Kf32uG.js → useSubAccountSwitcher-DMbRhLPv.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-CfI3J2IX.css → useSubAccountSwitcher-DS0iqSkP.css} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +8 -7
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +11 -10
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public.html +8 -8
- package/payload/server/server.js +556 -273
- package/payload/server/public/assets/Transcript-C93Lifnb.js +0 -1
- package/payload/server/public/assets/chat-Dq-VHp7a.js +0 -1
- package/payload/server/public/assets/chevron-left-DjbK_wdQ.js +0 -1
- package/payload/server/public/assets/data-BXOoK8Ci.js +0 -1
- package/payload/server/public/assets/page-Dck23z9U.js +0 -32
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maxy/estate-sales",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"test": "vitest run"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
14
|
+
"neo4j-driver": "^5.28.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.7.0",
|
|
18
|
+
"@types/node": "^22.0.0",
|
|
19
|
+
"vitest": "^4.1.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/payload/premium-plugins/real-agent/plugins/estate-sales/mcp/src/__tests__/round-trip.test.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Ephemeral round-trip test for the chain-graph I/O lib. Runs only when a
|
|
2
|
+
// Neo4j is reachable (NEO4J_URI + NEO4J_PASSWORD set) — the sprint drives it
|
|
3
|
+
// against a throwaway container; there is no CI Neo4j so it self-skips there.
|
|
4
|
+
//
|
|
5
|
+
// Proves: Loop's full chain model survives upsert -> read byte-identical;
|
|
6
|
+
// milestone-set advances one date and only that date; pipeline flags a stalled
|
|
7
|
+
// deal and not a fresh one; party-lookup resolves an inbound email to its role;
|
|
8
|
+
// a priority gap is flagged at read time.
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
11
|
+
import neo4j, { Driver, Session } from "neo4j-driver";
|
|
12
|
+
import {
|
|
13
|
+
upsertChain,
|
|
14
|
+
readChain,
|
|
15
|
+
setMilestone,
|
|
16
|
+
listPipeline,
|
|
17
|
+
lookupParty,
|
|
18
|
+
type ChainUpsertInput,
|
|
19
|
+
} from "../lib/chain-graph.js";
|
|
20
|
+
|
|
21
|
+
const URI = process.env.NEO4J_URI;
|
|
22
|
+
const PASSWORD = process.env.NEO4J_PASSWORD;
|
|
23
|
+
const USER = process.env.NEO4J_USER ?? "neo4j";
|
|
24
|
+
const ACCOUNT = "test-1372-progression";
|
|
25
|
+
const NOW = Date.parse("2026-07-03T00:00:00Z");
|
|
26
|
+
|
|
27
|
+
const run = URI && PASSWORD ? describe : describe.skip;
|
|
28
|
+
|
|
29
|
+
run("chain-graph round trip", () => {
|
|
30
|
+
let driver: Driver;
|
|
31
|
+
let session: Session;
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
driver = neo4j.driver(URI as string, neo4j.auth.basic(USER, PASSWORD as string));
|
|
35
|
+
session = driver.session();
|
|
36
|
+
// Clean any prior run and seed the LocalBusiness the chain's OWNED_BY needs.
|
|
37
|
+
await session.run(`MATCH (n {accountId: $a}) DETACH DELETE n`, { a: ACCOUNT });
|
|
38
|
+
await session.run(`MERGE (b:LocalBusiness {accountId: $a}) SET b.name = 'Test Agency'`, { a: ACCOUNT });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterAll(async () => {
|
|
42
|
+
await session.run(`MATCH (n {accountId: $a}) DETACH DELETE n`, { a: ACCOUNT });
|
|
43
|
+
await session.close();
|
|
44
|
+
await driver.close();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const twoLinkChain: ChainUpsertInput = {
|
|
48
|
+
chainId: "chain-A",
|
|
49
|
+
sale: {
|
|
50
|
+
sourceSystem: "loop-crm",
|
|
51
|
+
sourceId: "sale-1",
|
|
52
|
+
salePrice: 42500000,
|
|
53
|
+
status: "sale-agreed",
|
|
54
|
+
sstcDate: "2026-06-01",
|
|
55
|
+
isCompleteChain: true,
|
|
56
|
+
},
|
|
57
|
+
chain: {
|
|
58
|
+
anticipatedExchangeDate: "2026-08-15",
|
|
59
|
+
desiredCompletionDate: "2026-09-01",
|
|
60
|
+
createdDate: "2026-06-01",
|
|
61
|
+
owningTeam: "sales",
|
|
62
|
+
owningAgent: "Alex",
|
|
63
|
+
isComplete: false,
|
|
64
|
+
},
|
|
65
|
+
links: [
|
|
66
|
+
{
|
|
67
|
+
priority: 1,
|
|
68
|
+
linkType: "loop-property",
|
|
69
|
+
address: "14 Garth Road",
|
|
70
|
+
salePrice: 42500000,
|
|
71
|
+
generalNotes: "subject property",
|
|
72
|
+
proceedabilityNotes: "buyer proceedable",
|
|
73
|
+
milestones: {
|
|
74
|
+
"mortgage-valuation": "2026-06-05",
|
|
75
|
+
survey: "2026-06-10",
|
|
76
|
+
"draft-contract-issued": "2026-06-12",
|
|
77
|
+
"searches-requested": "2026-06-14",
|
|
78
|
+
"searches-received": "2026-06-20",
|
|
79
|
+
"mortgage-offer-received": "2026-06-22",
|
|
80
|
+
"seller-signed": "2026-06-25",
|
|
81
|
+
"buyer-signed": "2026-06-26",
|
|
82
|
+
exchanged: "2026-06-28",
|
|
83
|
+
completed: "2026-06-30",
|
|
84
|
+
},
|
|
85
|
+
parties: {
|
|
86
|
+
seller: { name: "Jane Seller", email: "jane@example.com", phone: "0700000001" },
|
|
87
|
+
"seller-solicitor": { name: "Seller LLP", email: "ssol@firm.co", phone: "0700000002" },
|
|
88
|
+
buyer: { name: "Bob Buyer", email: "bob@example.com", phone: "0700000003" },
|
|
89
|
+
"buyer-solicitor": { name: "Buyer LLP", email: "bsol@firm.co", phone: "0700000004" },
|
|
90
|
+
agent: { name: "Alex Agent", email: "alex@agency.co", phone: "0700000005" },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
priority: 2,
|
|
95
|
+
linkType: "external",
|
|
96
|
+
address: "9 Onward Street",
|
|
97
|
+
salePrice: 55000000,
|
|
98
|
+
generalNotes: "onward purchase",
|
|
99
|
+
proceedabilityNotes: "chain above unknown",
|
|
100
|
+
milestones: {
|
|
101
|
+
"mortgage-valuation": "2026-06-07",
|
|
102
|
+
survey: "2026-06-11",
|
|
103
|
+
"draft-contract-issued": "2026-06-13",
|
|
104
|
+
"searches-requested": "2026-06-15",
|
|
105
|
+
"searches-received": "2026-06-21",
|
|
106
|
+
"mortgage-offer-received": "2026-06-23",
|
|
107
|
+
"seller-signed": "2026-06-24",
|
|
108
|
+
"buyer-signed": "2026-06-27",
|
|
109
|
+
exchanged: "2026-06-29",
|
|
110
|
+
completed: "2026-07-01",
|
|
111
|
+
},
|
|
112
|
+
parties: {
|
|
113
|
+
seller: { name: "Sam Vendor", email: "sam@example.com", phone: "0700000006" },
|
|
114
|
+
"buyer-solicitor": { name: "Buyer LLP", email: "bsol@firm.co", phone: "0700000004" },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
it("upsert -> read returns every field byte-identical", async () => {
|
|
121
|
+
const res = await upsertChain(session, ACCOUNT, twoLinkChain);
|
|
122
|
+
expect(res.linkCount).toBe(2);
|
|
123
|
+
expect(res.partyCount).toBe(7);
|
|
124
|
+
|
|
125
|
+
const state = await readChain(session, ACCOUNT, { chainId: "chain-A" }, NOW);
|
|
126
|
+
expect(state).not.toBeNull();
|
|
127
|
+
expect(state!.links.map((l) => l.priority)).toEqual([1, 2]); // priority-ordered
|
|
128
|
+
expect(state!.sale!.salePrice).toBe(42500000); // int round-trip, not float
|
|
129
|
+
expect(state!.sale!.isCompleteChain).toBe(true);
|
|
130
|
+
expect(state!.chain!.anticipatedExchangeDate).toBe("2026-08-15");
|
|
131
|
+
expect(state!.chain!.owningAgent).toBe("Alex");
|
|
132
|
+
|
|
133
|
+
const link1 = state!.links[0];
|
|
134
|
+
expect(link1.milestones).toEqual(twoLinkChain.links[0].milestones);
|
|
135
|
+
expect(link1.salePrice).toBe(42500000);
|
|
136
|
+
expect(link1.address).toBe("14 Garth Road");
|
|
137
|
+
// five parties, each with contact on partyEmail/partyPhone.
|
|
138
|
+
const seller = link1.parties.find((p) => p.role === "seller");
|
|
139
|
+
expect(seller).toMatchObject({ name: "Jane Seller", email: "jane@example.com", phone: "0700000001" });
|
|
140
|
+
expect(link1.parties.length).toBe(5);
|
|
141
|
+
|
|
142
|
+
const link2 = state!.links[1];
|
|
143
|
+
expect(link2.milestones).toEqual(twoLinkChain.links[1].milestones);
|
|
144
|
+
expect(link2.parties.length).toBe(2);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("re-upsert is idempotent (no duplicate nodes)", async () => {
|
|
148
|
+
await upsertChain(session, ACCOUNT, twoLinkChain);
|
|
149
|
+
const counts = await session.run(
|
|
150
|
+
`MATCH (s:Sale {accountId: $a, chainId: 'chain-A'}) WITH count(s) AS sales
|
|
151
|
+
MATCH (cl:ChainLink {accountId: $a, chainId: 'chain-A'}) WITH sales, count(cl) AS links
|
|
152
|
+
MATCH (per:Person {accountId: $a, chainId: 'chain-A'}) RETURN sales, links, count(per) AS parties`,
|
|
153
|
+
{ a: ACCOUNT },
|
|
154
|
+
);
|
|
155
|
+
expect(counts.records[0].get("sales").toNumber()).toBe(1);
|
|
156
|
+
expect(counts.records[0].get("links").toNumber()).toBe(2);
|
|
157
|
+
expect(counts.records[0].get("parties").toNumber()).toBe(7);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("milestone-set advances one date and only that date", async () => {
|
|
161
|
+
const before = await readChain(session, ACCOUNT, { chainId: "chain-A" }, NOW);
|
|
162
|
+
const beforeLink1 = before!.links[0].milestones;
|
|
163
|
+
await setMilestone(session, ACCOUNT, "chain-A", 1, "exchanged", "2026-08-01", "email-123");
|
|
164
|
+
const after = await readChain(session, ACCOUNT, { chainId: "chain-A" }, NOW);
|
|
165
|
+
const afterLink1 = after!.links[0].milestones;
|
|
166
|
+
expect(afterLink1.exchanged).toBe("2026-08-01");
|
|
167
|
+
for (const stage of Object.keys(beforeLink1)) {
|
|
168
|
+
if (stage === "exchanged") continue;
|
|
169
|
+
expect(afterLink1[stage]).toBe(beforeLink1[stage]);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("party-lookup resolves an inbound email to its role", async () => {
|
|
174
|
+
const matches = await lookupParty(session, ACCOUNT, { email: "bsol@firm.co" });
|
|
175
|
+
// buyer-solicitor on both links -> two matches.
|
|
176
|
+
expect(matches.length).toBe(2);
|
|
177
|
+
expect(matches.every((m) => m.partyRole === "buyer-solicitor")).toBe(true);
|
|
178
|
+
expect(await lookupParty(session, ACCOUNT, { email: "nobody@nowhere.co" })).toEqual([]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("pipeline flags a stalled deal and not a fresh one", async () => {
|
|
182
|
+
// chain-A milestones are all in June/Aug 2026; relative to NOW (2026-07-03)
|
|
183
|
+
// its newest date (the milestone-set exchanged 2026-08-01) is in the future,
|
|
184
|
+
// so build a genuinely stale and a genuinely fresh sale for the assertion.
|
|
185
|
+
const stale: ChainUpsertInput = {
|
|
186
|
+
chainId: "chain-STALE",
|
|
187
|
+
sale: { sourceSystem: "loop-crm", sourceId: "sale-stale", status: "sale-agreed", sstcDate: "2026-05-01" },
|
|
188
|
+
links: [{ priority: 1, linkType: "external", address: "Stale House", milestones: { "searches-received": "2026-05-10" } }],
|
|
189
|
+
};
|
|
190
|
+
const fresh: ChainUpsertInput = {
|
|
191
|
+
chainId: "chain-FRESH",
|
|
192
|
+
sale: { sourceSystem: "loop-crm", sourceId: "sale-fresh", status: "sale-agreed", sstcDate: "2026-07-01" },
|
|
193
|
+
links: [{ priority: 1, linkType: "external", address: "Fresh House", milestones: { "searches-received": "2026-07-02" } }],
|
|
194
|
+
};
|
|
195
|
+
await upsertChain(session, ACCOUNT, stale);
|
|
196
|
+
await upsertChain(session, ACCOUNT, fresh);
|
|
197
|
+
const rows = await listPipeline(session, ACCOUNT, NOW, 7);
|
|
198
|
+
const staleRow = rows.find((r) => r.chainId === "chain-STALE");
|
|
199
|
+
const freshRow = rows.find((r) => r.chainId === "chain-FRESH");
|
|
200
|
+
expect(staleRow?.stalled).toBe(true);
|
|
201
|
+
expect(freshRow?.stalled).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("rejects (rolls back) when the account has no LocalBusiness to anchor the chain", async () => {
|
|
205
|
+
const orphanAcct = "test-1372-no-business";
|
|
206
|
+
await session.run(`MATCH (n {accountId: $a}) DETACH DELETE n`, { a: orphanAcct });
|
|
207
|
+
await expect(
|
|
208
|
+
upsertChain(session, orphanAcct, {
|
|
209
|
+
chainId: "no-biz",
|
|
210
|
+
sale: { sourceSystem: "loop-crm", sourceId: "s", status: "sale-agreed", sstcDate: "2026-07-01" },
|
|
211
|
+
links: [{ priority: 1, linkType: "external", address: "Nowhere" }],
|
|
212
|
+
}),
|
|
213
|
+
).rejects.toThrow(/LocalBusiness/);
|
|
214
|
+
// The whole transaction rolled back — no orphaned Chain was written.
|
|
215
|
+
const c = await session.run(`MATCH (c:Chain {accountId: $a}) RETURN count(c) AS n`, { a: orphanAcct });
|
|
216
|
+
expect(c.records[0].get("n").toNumber()).toBe(0);
|
|
217
|
+
await session.run(`MATCH (n {accountId: $a}) DETACH DELETE n`, { a: orphanAcct });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("orders a mixed date-only / datetime milestone set correctly (numeric, not lexical)", async () => {
|
|
221
|
+
// "2026-06-30" (date-only) is lexically AFTER "2026-06-29T23:59:59Z" but
|
|
222
|
+
// chronologically BEFORE it. The stall clock must use the datetime.
|
|
223
|
+
const mixed: ChainUpsertInput = {
|
|
224
|
+
chainId: "chain-MIXED",
|
|
225
|
+
sale: { sourceSystem: "loop-crm", sourceId: "sale-mixed", status: "sale-agreed", sstcDate: "2026-06-01" },
|
|
226
|
+
links: [{
|
|
227
|
+
priority: 1, linkType: "external", address: "Mixed",
|
|
228
|
+
milestones: { "searches-received": "2026-06-30", exchanged: "2026-06-29T23:59:59Z" },
|
|
229
|
+
}],
|
|
230
|
+
};
|
|
231
|
+
await upsertChain(session, ACCOUNT, mixed);
|
|
232
|
+
// NOW = 2026-07-03; newest real movement is 2026-06-30 (3 days) -> not stalled at 7d.
|
|
233
|
+
const state = await readChain(session, ACCOUNT, { chainId: "chain-MIXED" }, NOW, 7);
|
|
234
|
+
expect(state!.stalled).toBe(false);
|
|
235
|
+
await session.run(`MATCH (n {accountId: $a, chainId: 'chain-MIXED'}) DETACH DELETE n`, { a: ACCOUNT });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("flags a priority gap at read time", async () => {
|
|
239
|
+
const gapped: ChainUpsertInput = {
|
|
240
|
+
chainId: "chain-GAP",
|
|
241
|
+
sale: { sourceSystem: "loop-crm", sourceId: "sale-gap", status: "sale-agreed", sstcDate: "2026-07-01" },
|
|
242
|
+
links: [
|
|
243
|
+
{ priority: 1, linkType: "external", address: "First" },
|
|
244
|
+
{ priority: 3, linkType: "external", address: "Third" },
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
await upsertChain(session, ACCOUNT, gapped);
|
|
248
|
+
const state = await readChain(session, ACCOUNT, { chainId: "chain-GAP" }, NOW);
|
|
249
|
+
expect(state!.structuralFlags.some((f) => f.includes("gap"))).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// estate-sales MCP server — sales-progression chain tools (Task 1372).
|
|
2
|
+
//
|
|
3
|
+
// Five tools, all admin + negotiator side (none exposed on the public agent):
|
|
4
|
+
// progression-chain-upsert write (Sale + Chain + ordered links + parties)
|
|
5
|
+
// progression-milestone-set write (one milestone date on one link)
|
|
6
|
+
// progression-chain-read query (full state-of-chain for one deal)
|
|
7
|
+
// progression-pipeline-list query (active sale-agreed pipeline + stall flags)
|
|
8
|
+
// progression-party-lookup query (inbound contact -> chain + link + role)
|
|
9
|
+
//
|
|
10
|
+
// Writes use raw parameterised cypher via a local neo4j driver, account-scoped
|
|
11
|
+
// with the parent edge attached in the same statement (buyers/mcp pattern).
|
|
12
|
+
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
|
|
16
|
+
import { closeDriver } from "./lib/neo4j.js";
|
|
17
|
+
import { registerChainUpsert } from "./tools/progression-chain-upsert.js";
|
|
18
|
+
import { registerMilestoneSet } from "./tools/progression-milestone-set.js";
|
|
19
|
+
import { registerChainRead } from "./tools/progression-chain-read.js";
|
|
20
|
+
import { registerPipelineList } from "./tools/progression-pipeline-list.js";
|
|
21
|
+
import { registerPartyLookup } from "./tools/progression-party-lookup.js";
|
|
22
|
+
|
|
23
|
+
const accountId = process.env.ACCOUNT_ID ?? "local";
|
|
24
|
+
|
|
25
|
+
const server = new McpServer({ name: "maxy-estate-sales", version: "0.1.0" });
|
|
26
|
+
|
|
27
|
+
console.error(`[estate-sales] server started, account=${accountId}`);
|
|
28
|
+
|
|
29
|
+
registerChainUpsert(server, accountId);
|
|
30
|
+
registerMilestoneSet(server, accountId);
|
|
31
|
+
registerChainRead(server, accountId);
|
|
32
|
+
registerPipelineList(server, accountId);
|
|
33
|
+
registerPartyLookup(server, accountId);
|
|
34
|
+
|
|
35
|
+
const transport = new StdioServerTransport();
|
|
36
|
+
await server.connect(transport);
|
|
37
|
+
|
|
38
|
+
async function shutdown(): Promise<void> {
|
|
39
|
+
await closeDriver();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
process.on("SIGINT", shutdown);
|
|
43
|
+
process.on("SIGTERM", shutdown);
|