@rubytech/create-realagent 1.0.693 → 1.0.695

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-search/dist/index.d.ts +127 -0
  3. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/graph-search/dist/index.js +393 -0
  5. package/payload/platform/lib/graph-search/dist/index.js.map +1 -0
  6. package/payload/platform/lib/graph-search/src/__tests__/bm25-only.test.ts +129 -0
  7. package/payload/platform/lib/graph-search/src/__tests__/escape-and-normalise.test.ts +53 -0
  8. package/payload/platform/lib/graph-search/src/__tests__/hybrid.test.ts +190 -0
  9. package/payload/platform/lib/graph-search/src/index.ts +498 -0
  10. package/payload/platform/lib/graph-search/tsconfig.json +9 -0
  11. package/payload/platform/lib/graph-search/vitest.config.ts +9 -0
  12. package/payload/platform/lib/graph-write/dist/index.d.ts +61 -0
  13. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -0
  14. package/payload/platform/lib/graph-write/dist/index.js +97 -0
  15. package/payload/platform/lib/graph-write/dist/index.js.map +1 -0
  16. package/payload/platform/lib/graph-write/src/index.ts +167 -0
  17. package/payload/platform/lib/graph-write/tsconfig.json +8 -0
  18. package/payload/platform/package.json +2 -2
  19. package/payload/platform/plugins/admin/mcp/dist/index.js +19 -8
  20. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/contacts/mcp/dist/index.js +27 -3
  22. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  23. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +4 -0
  24. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
  25. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +10 -6
  26. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  27. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts +2 -0
  28. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts.map +1 -1
  29. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js +43 -36
  30. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js.map +1 -1
  31. package/payload/platform/plugins/docs/references/memory-guide.md +6 -0
  32. package/payload/platform/plugins/memory/mcp/dist/index.js +44 -3
  33. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  34. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +3 -32
  35. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  36. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +18 -381
  37. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +9 -5
  39. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  40. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +10 -23
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  42. package/payload/platform/plugins/memory/references/graph-primitives.md +1 -1
  43. package/payload/platform/plugins/scheduling/mcp/dist/index.js +8 -1
  44. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  45. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +2 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  47. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +24 -10
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  49. package/payload/platform/plugins/tasks/mcp/dist/index.js +8 -2
  50. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  51. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +2 -0
  52. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -1
  53. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +45 -18
  54. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -1
  55. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +12 -2
  56. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
  57. package/payload/server/chunk-IAIGB5WN.js +11406 -0
  58. package/payload/server/maxy-edge.js +1 -1
  59. 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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -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
- // Link to Conversation node when conversationId is available
2504
- const conversationLink = opts.conversationId
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
- })${conversationLink}`, {
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) {