@rubytech/create-maxy 1.0.693 → 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 +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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write doctrine (Task 673): a node without at least one adjacency is noise,
|
|
3
|
+
* not knowledge. `writeNodeWithEdges` is the single primitive every graph
|
|
4
|
+
* writer should call; it rejects zero-relationship writes, verifies every
|
|
5
|
+
* relationship target resolves before the node is created, and commits the
|
|
6
|
+
* node + all edges in one managed transaction. Provenance is stamped on the
|
|
7
|
+
* node as flattened `createdBy*` properties (Neo4j does not persist nested
|
|
8
|
+
* maps, and flat props are queryable — `MATCH (n) WHERE n.createdBySession
|
|
9
|
+
* = $id RETURN n` is the forensic entry point).
|
|
10
|
+
*
|
|
11
|
+
* Rejection paths (every one emits a stderr log the admin server pipes to
|
|
12
|
+
* server.log, so orphan pressure is visible per-write not just in the
|
|
13
|
+
* hourly [graph-health] signal):
|
|
14
|
+
* - zero relationships → `[graph-write] reject reason=zero-relationships`
|
|
15
|
+
* - unresolved target id → `[graph-write] reject reason=unresolved-target`
|
|
16
|
+
*
|
|
17
|
+
* The `createdBy.agent` field is advisory, not authoritative — it is sourced
|
|
18
|
+
* from the MCP server's AGENT_SLUG env var, which is set by the trusted
|
|
19
|
+
* spawning code (admin server / workflow runner). A misconfigured spawner
|
|
20
|
+
* will write "unknown"; that's the signal something is wrong. Do not use
|
|
21
|
+
* this field for access control — it is forensic, not a security boundary.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Session } from "neo4j-driver";
|
|
25
|
+
|
|
26
|
+
export interface GraphRelationship {
|
|
27
|
+
type: string;
|
|
28
|
+
direction: "outgoing" | "incoming";
|
|
29
|
+
targetNodeId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CreatedBy {
|
|
33
|
+
/** Agent slug (e.g. "maxy-admin", "whatsapp-public") for LLM-tool writes. */
|
|
34
|
+
agent?: string;
|
|
35
|
+
/** Session correlation id — same string across every node written in a conversation turn. */
|
|
36
|
+
session?: string;
|
|
37
|
+
/** MCP tool name ("memory-write", "contact-create", ...) for LLM-tool writes. */
|
|
38
|
+
tool?: string;
|
|
39
|
+
/** Subsystem name ("workflow-execute", "persist-tool-call", ...) for system writes. */
|
|
40
|
+
source?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WriteNodeWithEdgesParams {
|
|
44
|
+
session: Session;
|
|
45
|
+
labels: string[];
|
|
46
|
+
props: Record<string, unknown>;
|
|
47
|
+
/** At least one relationship is required — zero-rel writes are rejected. */
|
|
48
|
+
relationships: GraphRelationship[];
|
|
49
|
+
createdBy: CreatedBy;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WriteNodeResult {
|
|
53
|
+
nodeId: string;
|
|
54
|
+
labels: string[];
|
|
55
|
+
edgesCreated: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Stamp flattened provenance into node properties. */
|
|
59
|
+
export function stampCreatedBy(
|
|
60
|
+
props: Record<string, unknown>,
|
|
61
|
+
createdBy: CreatedBy
|
|
62
|
+
): Record<string, unknown> {
|
|
63
|
+
return {
|
|
64
|
+
...props,
|
|
65
|
+
createdByAgent: createdBy.agent ?? "unknown",
|
|
66
|
+
createdBySession: createdBy.session ?? "unknown",
|
|
67
|
+
createdByTool: createdBy.tool ?? null,
|
|
68
|
+
createdBySource: createdBy.source ?? null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Enforce the write doctrine: node + ≥1 edge, transactional, provenance stamped.
|
|
74
|
+
* The label and relationship type strings are interpolated into Cypher — the
|
|
75
|
+
* caller must constrain them via Zod/schema before calling. Backticks are
|
|
76
|
+
* stripped to defeat the only Cypher escape char.
|
|
77
|
+
*/
|
|
78
|
+
export async function writeNodeWithEdges(
|
|
79
|
+
params: WriteNodeWithEdgesParams
|
|
80
|
+
): Promise<WriteNodeResult> {
|
|
81
|
+
const { session, labels, props, relationships, createdBy } = params;
|
|
82
|
+
|
|
83
|
+
const agentLabel = createdBy.agent ?? createdBy.source ?? "unknown";
|
|
84
|
+
const labelCsv = labels.join(",");
|
|
85
|
+
|
|
86
|
+
if (!relationships || relationships.length < 1) {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`[graph-write] reject reason=zero-relationships labels=${labelCsv} agent=${agentLabel}\n`
|
|
89
|
+
);
|
|
90
|
+
throw new Error(
|
|
91
|
+
"Write doctrine violated: a node must be created with at least one relationship. See .docs/neo4j.md (Write doctrine)."
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const labelStr = labels.map((l) => `\`${l.replace(/`/g, "")}\``).join(":");
|
|
96
|
+
const nodeProps = stampCreatedBy(props, createdBy);
|
|
97
|
+
|
|
98
|
+
return await session.executeWrite(async (tx) => {
|
|
99
|
+
const targetIds = relationships.map((r) => r.targetNodeId);
|
|
100
|
+
const check = await tx.run(
|
|
101
|
+
`UNWIND $ids AS id MATCH (t) WHERE elementId(t) = id RETURN count(DISTINCT t) AS found`,
|
|
102
|
+
{ ids: targetIds }
|
|
103
|
+
);
|
|
104
|
+
const found = check.records[0].get("found").toNumber();
|
|
105
|
+
const uniqueRequested = new Set(targetIds).size;
|
|
106
|
+
if (found !== uniqueRequested) {
|
|
107
|
+
process.stderr.write(
|
|
108
|
+
`[graph-write] reject reason=unresolved-target labels=${labelCsv} agent=${agentLabel} requested=${uniqueRequested} found=${found}\n`
|
|
109
|
+
);
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Write doctrine violated: ${uniqueRequested - found} of ${uniqueRequested} relationship target(s) did not resolve (elementId mismatch). No node created.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const nodeRes = await tx.run(
|
|
116
|
+
`CREATE (n:${labelStr} $props) RETURN elementId(n) AS nodeId, labels(n) AS nodeLabels`,
|
|
117
|
+
{ props: nodeProps }
|
|
118
|
+
);
|
|
119
|
+
const nodeId = nodeRes.records[0].get("nodeId") as string;
|
|
120
|
+
const nodeLabels = nodeRes.records[0].get("nodeLabels") as string[];
|
|
121
|
+
|
|
122
|
+
// Neo4j Community's default isolation is read-committed (not snapshot)
|
|
123
|
+
// — a target elementId that resolved during the pre-check can be
|
|
124
|
+
// deleted by a concurrent transaction before the per-edge CREATE below
|
|
125
|
+
// runs. If that happens, `MATCH (a), (b)` returns 0 rows and the CREATE
|
|
126
|
+
// quietly produces no edge. Per-edge counter inspection catches the
|
|
127
|
+
// race: any zero-result CREATE throws inside the transaction callback,
|
|
128
|
+
// so `executeWrite` rolls the node back — no silent-orphan path.
|
|
129
|
+
let edgesCreated = 0;
|
|
130
|
+
for (const rel of relationships) {
|
|
131
|
+
const type = rel.type.replace(/`/g, "");
|
|
132
|
+
const q =
|
|
133
|
+
rel.direction === "outgoing"
|
|
134
|
+
? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)`
|
|
135
|
+
: `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
|
|
136
|
+
const r = await tx.run(q, { from: nodeId, to: rel.targetNodeId });
|
|
137
|
+
const created = r.summary.counters.updates().relationshipsCreated;
|
|
138
|
+
if (created === 0) {
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
`[graph-write] reject reason=unresolved-target-on-create labels=${labelCsv} agent=${agentLabel} relType=${rel.type} targetId=${rel.targetNodeId}\n`
|
|
141
|
+
);
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Write doctrine violated: relationship CREATE to target ${rel.targetNodeId} produced 0 edges (target likely deleted concurrently after pre-check). Transaction rolled back.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
edgesCreated += created;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (edgesCreated !== relationships.length) {
|
|
150
|
+
// Defensive: should be unreachable given the per-edge check above.
|
|
151
|
+
// The rollback throws loudly so regression shows rather than a
|
|
152
|
+
// silent commit with edgesCreated < requested.
|
|
153
|
+
process.stderr.write(
|
|
154
|
+
`[graph-write] reject reason=edge-count-mismatch labels=${labelCsv} agent=${agentLabel} requested=${relationships.length} created=${edgesCreated}\n`
|
|
155
|
+
);
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Write doctrine violated: expected ${relationships.length} edges, created ${edgesCreated}. Transaction rolled back.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
process.stderr.write(
|
|
162
|
+
`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"}\n`
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return { nodeId, labels: nodeLabels, edgesCreated };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"plugins/*/mcp"
|
|
7
7
|
],
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
-
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json",
|
|
9
|
+
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
+
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json",
|
|
11
11
|
"build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
|
|
12
12
|
"build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
|
|
13
13
|
"build:telegram": "tsc -p plugins/telegram/mcp/tsconfig.json",
|
|
@@ -2494,17 +2494,22 @@ function cleanupPendingAction(actionId) {
|
|
|
2494
2494
|
}
|
|
2495
2495
|
}
|
|
2496
2496
|
async function persistApprovalToolCall(opts) {
|
|
2497
|
+
// Write doctrine (Task 673): a ToolCall without its owning Conversation is
|
|
2498
|
+
// noise — every admin tool call belongs to a chat. When conversationId is
|
|
2499
|
+
// missing or its Conversation cannot be matched, we log and skip the
|
|
2500
|
+
// persist rather than create an orphan ToolCall node. The legacy
|
|
2501
|
+
// OPTIONAL-MATCH + FOREACH pattern created orphans silently.
|
|
2502
|
+
if (!opts.conversationId) {
|
|
2503
|
+
console.error(`[approval] ToolCall skipped (no conversationId): tool=${opts.toolName} state=${opts.approvalState}`);
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2497
2506
|
const session = getSession();
|
|
2498
2507
|
try {
|
|
2499
2508
|
const optionalFields = [
|
|
2500
2509
|
opts.originalInput != null ? ", originalInput: $originalInput" : "",
|
|
2501
|
-
opts.conversationId != null ? ", conversationId: $conversationId" : "",
|
|
2502
2510
|
].join("");
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
? `\nWITH tc\nOPTIONAL MATCH (c:Conversation {conversationId: $conversationId})\nFOREACH (_ IN CASE WHEN c IS NOT NULL THEN [1] ELSE [] END | CREATE (c)-[:HAS_TOOL_CALL]->(tc))`
|
|
2506
|
-
: "";
|
|
2507
|
-
await session.run(`CREATE (tc:ToolCall {
|
|
2511
|
+
const res = await session.run(`MATCH (c:Conversation {conversationId: $conversationId})
|
|
2512
|
+
CREATE (c)-[:HAS_TOOL_CALL]->(tc:ToolCall {
|
|
2508
2513
|
callId: $callId,
|
|
2509
2514
|
toolName: $toolName,
|
|
2510
2515
|
pluginName: $pluginName,
|
|
@@ -2514,10 +2519,12 @@ async function persistApprovalToolCall(opts) {
|
|
|
2514
2519
|
agentType: 'admin',
|
|
2515
2520
|
accountId: $accountId,
|
|
2516
2521
|
approvalState: $approvalState,
|
|
2522
|
+
conversationId: $conversationId,
|
|
2523
|
+
createdBySource: 'admin-approval',
|
|
2517
2524
|
startedAt: datetime($startedAt),
|
|
2518
2525
|
completedAt: datetime($completedAt)
|
|
2519
2526
|
${optionalFields}
|
|
2520
|
-
})
|
|
2527
|
+
})`, {
|
|
2521
2528
|
callId: randomUUID(),
|
|
2522
2529
|
toolName: opts.toolName,
|
|
2523
2530
|
pluginName: opts.pluginName,
|
|
@@ -2526,11 +2533,15 @@ async function persistApprovalToolCall(opts) {
|
|
|
2526
2533
|
isError: opts.isError,
|
|
2527
2534
|
accountId: ACCOUNT_ID,
|
|
2528
2535
|
approvalState: opts.approvalState,
|
|
2536
|
+
conversationId: opts.conversationId,
|
|
2529
2537
|
startedAt: new Date().toISOString(),
|
|
2530
2538
|
completedAt: new Date().toISOString(),
|
|
2531
2539
|
...(opts.originalInput != null ? { originalInput: opts.originalInput.slice(0, 300) } : {}),
|
|
2532
|
-
...(opts.conversationId != null ? { conversationId: opts.conversationId } : {}),
|
|
2533
2540
|
});
|
|
2541
|
+
if (res.summary.counters.updates().nodesCreated === 0) {
|
|
2542
|
+
console.error(`[approval] ToolCall skipped (conversation ${opts.conversationId} not found): tool=${opts.toolName}`);
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2534
2545
|
console.error(`[approval] ToolCall persisted: ${opts.toolName} state=${opts.approvalState}`);
|
|
2535
2546
|
}
|
|
2536
2547
|
catch (err) {
|