@rubytech/create-maxy-code 0.1.161 → 0.1.164

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 (87) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/edge-annotations.json +4 -0
  3. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  4. package/payload/platform/plugins/admin/PLUGIN.md +1 -6
  5. package/payload/platform/plugins/admin/hooks/lib/admin-graph-pass-common.sh +12 -15
  6. package/payload/platform/plugins/admin/mcp/dist/index.js +0 -41
  7. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/docs/references/platform.md +1 -1
  9. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/email/PLUGIN.md +8 -12
  11. package/payload/platform/plugins/email/mcp/dist/index.js +8 -34
  12. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/email/mcp/dist/lib/attachment-archive.d.ts +39 -0
  14. package/payload/platform/plugins/email/mcp/dist/lib/attachment-archive.d.ts.map +1 -0
  15. package/payload/platform/plugins/email/mcp/dist/lib/attachment-archive.js +0 -0
  16. package/payload/platform/plugins/email/mcp/dist/lib/attachment-archive.js.map +1 -0
  17. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.d.ts +23 -1
  18. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.d.ts.map +1 -1
  19. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.js +131 -3
  20. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.js.map +1 -1
  21. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-lookup.d.ts +19 -0
  22. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-lookup.d.ts.map +1 -0
  23. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-lookup.js +51 -0
  24. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-lookup.js.map +1 -0
  25. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +0 -18
  26. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/lib/graph.js +0 -12
  28. package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -1
  29. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +88 -0
  30. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  31. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +189 -1
  32. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  33. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.d.ts.map +1 -1
  34. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js +6 -1
  35. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts +7 -15
  37. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts.map +1 -1
  38. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js +206 -86
  39. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js.map +1 -1
  40. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts.map +1 -1
  41. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js +104 -4
  42. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js.map +1 -1
  43. package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -1
  44. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +18 -12
  45. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -1
  46. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
  47. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +5 -4
  48. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
  49. package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +13 -12
  51. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -1
  52. package/payload/platform/plugins/email/references/email-reference.md +8 -8
  53. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  54. package/payload/platform/plugins/memory/PLUGIN.md +2 -2
  55. package/payload/platform/plugins/memory/mcp/dist/index.js +4 -3
  56. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js +1 -13
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js.map +1 -1
  59. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.d.ts.map +1 -1
  60. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.js +3 -5
  61. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.js.map +1 -1
  62. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts +1 -1
  63. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts.map +1 -1
  64. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js +10 -4
  65. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js.map +1 -1
  66. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.d.ts.map +1 -1
  67. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js +5 -6
  68. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +21 -4
  70. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +6 -8
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  74. package/payload/platform/plugins/memory/references/schema-base.md +2 -1
  75. package/payload/platform/plugins/memory/references/transcript-formats/circleback.md +49 -0
  76. package/payload/platform/plugins/memory/references/transcript-formats/granola.md +50 -0
  77. package/payload/platform/plugins/memory/references/transcript-formats/otter.md +50 -0
  78. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +1 -0
  79. package/payload/platform/plugins/memory/skills/conversation-archive-mcp/SKILL.md +170 -0
  80. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  81. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -4
  82. package/payload/platform/scripts/seed-neo4j.sh +1 -3
  83. package/payload/platform/templates/specialists/agents/librarian.md +1 -0
  84. package/payload/platform/plugins/admin/hooks/__tests__/per-turn-graph-pass-gate.test.sh +0 -361
  85. package/payload/platform/plugins/admin/hooks/__tests__/pre-turn-graph-pass.test.sh +0 -441
  86. package/payload/platform/plugins/admin/hooks/per-turn-graph-pass-gate.sh +0 -106
  87. package/payload/platform/plugins/admin/hooks/pre-turn-graph-pass.sh +0 -171
@@ -2,109 +2,182 @@ import neo4j from "neo4j-driver";
2
2
  import { getSession } from "../lib/neo4j.js";
3
3
  import { computeEmbedding } from "../lib/embedding.js";
4
4
  /**
5
- * Query email-source :KnowledgeDocument threads stored in the graph.
5
+ * Query stored email-thread `:ConversationArchive` rows in the graph.
6
6
  *
7
7
  * Two modes:
8
- * - List (no query): Cypher MATCH with optional filters, sorted by lastReceivedAt DESC
9
- * - Search (query provided): embed via Ollama, vector similarity on KD embedding index
8
+ * - List (no query): MATCH archives with optional sender + date filters,
9
+ * sorted by lastMessageAt DESC.
10
+ * - Search (query provided): embed via Ollama, run vector similarity over
11
+ * `:Section` chunks, dedup to parent archives, rank by best section.
10
12
  *
11
- * Always scoped to accountId. The display surface is per-thread, not
12
- * per-message the KD body holds the verbatim chronological transcript.
13
+ * Always scoped to (accountId, source='email'). The display surface is
14
+ * per-archive; per-message envelopes are not stored Task 445.
13
15
  */
14
16
  export async function emailGraphQuery(params) {
15
17
  const limit = Math.min(Math.max(1, params.limit ?? 20), 50);
16
18
  const offset = Math.max(0, params.offset ?? 0);
17
19
  const query = params.query?.trim();
18
- const threads = query && query.length > 0
20
+ const archives = query && query.length > 0
19
21
  ? await searchMode(query, params, limit)
20
22
  : await listMode(params, limit, offset);
21
- if (threads.length === 0) {
23
+ if (archives.length === 0) {
22
24
  return formatEmptyResult(params, query);
23
25
  }
24
- const lines = threads
25
- .map((t) => {
26
- const sender = t.rootFromName
27
- ? `${t.rootFromName} <${t.rootFromAddress ?? ""}>`
28
- : (t.rootFromAddress ?? "(unknown)");
29
- let entry = `Thread: ${t.attachmentId.slice(0, 16)}…`;
30
- entry += `\nSubject: ${t.subject}`;
31
- entry += `\nFirst sender: ${sender}`;
32
- entry += `\nMessages: ${t.messageIds.length}`;
33
- entry += `\nFirst: ${t.firstReceivedAt}`;
34
- entry += `\nLast: ${t.lastReceivedAt}`;
35
- if (t.direction === "outbound")
36
- entry += `\nDirection: outbound`;
37
- if (t.score !== undefined)
38
- entry += `\nScore: ${t.score.toFixed(3)}`;
39
- // Truncate body to a preview so list responses stay readable.
40
- const preview = t.body.length > 800 ? `${t.body.slice(0, 800)}\n…(truncated)` : t.body;
41
- entry += `\n\n${preview}`;
26
+ const lines = archives
27
+ .map((a) => {
28
+ const participants = a.participantNames.length > 0
29
+ ? a.participantNames.join(", ")
30
+ : "(unknown)";
31
+ let entry = `Archive: ${a.elementId.slice(0, 16)}…`;
32
+ entry += `\nTitle: ${a.title || "(untitled)"}`;
33
+ entry += `\nParticipants: ${participants}`;
34
+ entry += `\nSections: ${a.sectionCount}`;
35
+ entry += `\nFirst: ${a.firstMessageAt}`;
36
+ entry += `\nLast: ${a.lastMessageAt}`;
37
+ if (a.score !== undefined)
38
+ entry += `\nScore: ${a.score.toFixed(3)}`;
39
+ const preview = a.summary.length > 800 ? `${a.summary.slice(0, 800)}\n…(truncated)` : a.summary;
40
+ if (preview)
41
+ entry += `\n\n${preview}`;
42
42
  return entry + "\n---";
43
43
  })
44
44
  .join("\n\n");
45
- if (!query && threads.length === limit) {
45
+ if (!query && archives.length === limit) {
46
46
  return `${lines}\n\nPagination: next offset=${offset + limit} (pass as offset to get more results)`;
47
47
  }
48
48
  return lines;
49
49
  }
50
50
  // ---------------------------------------------------------------------------
51
- // List mode — Cypher MATCH with optional filters
51
+ // List mode — Cypher MATCH on :ConversationArchive with optional filters
52
52
  // ---------------------------------------------------------------------------
53
53
  async function listMode(params, limit, offset) {
54
- // Exclude discarded threads (spam) from list results. Threads without a
55
- // screening property are included.
56
- const conditions = [
57
- "kd.source = 'email'",
58
- "kd.accountId = $accountId",
59
- "(kd.screening <> 'discarded' OR kd.screening IS NULL)",
54
+ const archiveWhere = [
55
+ "a.source = 'email'",
56
+ "a.accountId = $accountId",
60
57
  ];
61
58
  const cyParams = {
62
59
  accountId: params.accountId,
63
60
  limit: neo4j.int(limit),
64
61
  offset: neo4j.int(offset),
65
62
  };
63
+ // Date filters apply at the section level (firstMessageAt/lastMessageAt
64
+ // span the archive). Carry the archive only when at least one section
65
+ // overlaps the requested window.
66
+ let sectionDateFilter = "";
67
+ const sectionDateConds = [];
66
68
  if (params.dateFrom) {
67
- conditions.push("kd.lastReceivedAt >= $dateFrom");
69
+ sectionDateConds.push("s.lastMessageAt >= $dateFrom");
68
70
  cyParams.dateFrom = params.dateFrom;
69
71
  }
70
72
  if (params.dateTo) {
71
- conditions.push("kd.firstReceivedAt <= $dateTo");
73
+ sectionDateConds.push("s.firstMessageAt <= $dateTo");
72
74
  cyParams.dateTo = params.dateTo;
73
75
  }
74
- if (params.direction === "outbound") {
75
- conditions.push("kd.direction = 'outbound'");
76
+ if (sectionDateConds.length > 0) {
77
+ sectionDateFilter = `WHERE ${sectionDateConds.join(" AND ")}`;
76
78
  }
77
- else if (params.direction === "inbound") {
78
- conditions.push("kd.direction IS NULL");
79
- }
80
- const senderClause = params.sender
81
- ? `, (kd)-[:FROM]->(sender:Person)
82
- WHERE toLower(sender.email) CONTAINS toLower($sender)`
83
- : `OPTIONAL MATCH (kd)-[:FROM]->(sender:Person)`;
79
+ // Sender filter joins through :PARTICIPANT_IN; matches Person OR AdminUser
80
+ // on case-insensitive email substring.
81
+ let senderFilter = "";
84
82
  if (params.sender) {
83
+ senderFilter = `
84
+ WITH a
85
+ MATCH (p)-[:PARTICIPANT_IN]->(a)
86
+ WHERE (p:Person OR p:AdminUser)
87
+ AND p.email IS NOT NULL
88
+ AND toLower(p.email) CONTAINS toLower($sender)
89
+ `;
85
90
  cyParams.sender = params.sender;
86
91
  }
87
92
  const cypher = `
88
- MATCH (kd:KnowledgeDocument)
89
- ${senderClause}
90
- WHERE ${conditions.join(" AND ")}
91
- WITH kd, head(collect(sender)) AS rootSender
92
- RETURN kd, rootSender
93
- ORDER BY kd.lastReceivedAt DESC
93
+ MATCH (a:ConversationArchive)
94
+ WHERE ${archiveWhere.join(" AND ")}
95
+ ${senderFilter}
96
+ WITH DISTINCT a
97
+ OPTIONAL MATCH (a)-[:HAS_SECTION]->(s:Section)
98
+ ${sectionDateFilter}
99
+ WITH a, collect(s) AS matchedSections
100
+ WHERE size(matchedSections) > 0 OR ($dateFrom IS NULL AND $dateTo IS NULL)
101
+ OPTIONAL MATCH (a)-[:HAS_SECTION]->(allS:Section)
102
+ WITH a, matchedSections, allS
103
+ ORDER BY coalesce(allS.position, 0) ASC
104
+ WITH a,
105
+ matchedSections,
106
+ collect(allS) AS allSections
107
+ WITH a,
108
+ allSections,
109
+ [s IN allSections | coalesce(s.summary, '')] AS summaries,
110
+ [s IN allSections WHERE s.firstMessageAt IS NOT NULL | s.firstMessageAt] AS firsts,
111
+ [s IN allSections WHERE s.lastMessageAt IS NOT NULL | s.lastMessageAt] AS lasts
112
+ OPTIONAL MATCH (p)-[:PARTICIPANT_IN]->(a)
113
+ WHERE p:Person OR p:AdminUser
114
+ WITH a,
115
+ allSections,
116
+ summaries,
117
+ firsts,
118
+ lasts,
119
+ collect(DISTINCT coalesce(p.displayName, p.name, p.email, '')) AS participantNames
120
+ RETURN elementId(a) AS elementId,
121
+ coalesce(a.title, '') AS title,
122
+ [x IN summaries WHERE x <> ''] AS summaryParts,
123
+ CASE WHEN size(firsts) > 0 THEN apoc.coll.min(firsts) ELSE coalesce(a.createdAt, '') END AS firstMessageAt,
124
+ CASE WHEN size(lasts) > 0 THEN apoc.coll.max(lasts) ELSE coalesce(a.lastIngestedMessageAt, a.updatedAt, '') END AS lastMessageAt,
125
+ size(allSections) AS sectionCount,
126
+ [n IN participantNames WHERE n <> ''] AS participantNames
127
+ ORDER BY lastMessageAt DESC
128
+ SKIP $offset
129
+ LIMIT $limit
130
+ `;
131
+ // Fallback when APOC isn't available: compute min/max in JS. Strip the
132
+ // apoc.* calls and pull raw arrays instead.
133
+ const cypherNoApoc = `
134
+ MATCH (a:ConversationArchive)
135
+ WHERE ${archiveWhere.join(" AND ")}
136
+ ${senderFilter}
137
+ WITH DISTINCT a
138
+ OPTIONAL MATCH (a)-[:HAS_SECTION]->(s:Section)
139
+ ${sectionDateFilter}
140
+ WITH a, collect(s) AS matchedSections
141
+ WHERE size(matchedSections) > 0 OR ($dateFrom IS NULL AND $dateTo IS NULL)
142
+ OPTIONAL MATCH (a)-[:HAS_SECTION]->(allS:Section)
143
+ WITH a, allS
144
+ ORDER BY coalesce(allS.position, 0) ASC
145
+ WITH a, collect(allS) AS allSections
146
+ OPTIONAL MATCH (p)-[:PARTICIPANT_IN]->(a)
147
+ WHERE p:Person OR p:AdminUser
148
+ WITH a,
149
+ allSections,
150
+ collect(DISTINCT coalesce(p.displayName, p.name, p.email, '')) AS participantNames
151
+ RETURN elementId(a) AS elementId,
152
+ coalesce(a.title, '') AS title,
153
+ [x IN allSections | coalesce(x.summary, '')] AS summaryParts,
154
+ [x IN allSections WHERE x.firstMessageAt IS NOT NULL | x.firstMessageAt] AS firsts,
155
+ [x IN allSections WHERE x.lastMessageAt IS NOT NULL | x.lastMessageAt] AS lasts,
156
+ coalesce(a.createdAt, '') AS archiveCreatedAt,
157
+ coalesce(a.lastIngestedMessageAt, a.updatedAt, '') AS archiveLastAt,
158
+ size(allSections) AS sectionCount,
159
+ [n IN participantNames WHERE n <> ''] AS participantNames
160
+ ORDER BY archiveLastAt DESC
94
161
  SKIP $offset
95
162
  LIMIT $limit
96
163
  `;
164
+ // dateFrom/dateTo must always be bound (Cypher requires named params even
165
+ // for the no-filter branch above where the WHERE references them).
166
+ if (cyParams.dateFrom === undefined)
167
+ cyParams.dateFrom = null;
168
+ if (cyParams.dateTo === undefined)
169
+ cyParams.dateTo = null;
97
170
  const session = getSession();
98
171
  try {
99
- const result = await session.run(cypher, cyParams);
100
- return result.records.map((r) => nodeToRecord(r.get("kd"), r.get("rootSender")));
172
+ const result = await session.run(cypherNoApoc, cyParams);
173
+ return result.records.map(recordToArchive);
101
174
  }
102
175
  finally {
103
176
  await session.close();
104
177
  }
105
178
  }
106
179
  // ---------------------------------------------------------------------------
107
- // Search mode — vector similarity on KD embedding index
180
+ // Search mode — vector similarity over :Section, dedup to parent archive
108
181
  // ---------------------------------------------------------------------------
109
182
  async function searchMode(query, params, limit) {
110
183
  const embedding = await computeEmbedding(query);
@@ -112,26 +185,62 @@ async function searchMode(query, params, limit) {
112
185
  throw new Error("Semantic search unavailable — the embedding service is not responding. " +
113
186
  "Try again without a query to list email threads instead.");
114
187
  }
115
- const topK = Math.min(limit * 3, 150);
188
+ const topK = Math.min(limit * 6, 300);
189
+ const cyParams = {
190
+ topK: neo4j.int(topK),
191
+ embedding,
192
+ accountId: params.accountId,
193
+ limit: neo4j.int(limit),
194
+ sender: params.sender ?? null,
195
+ dateFrom: params.dateFrom ?? null,
196
+ dateTo: params.dateTo ?? null,
197
+ };
198
+ // Section-level vector search → restrict to email archives → keep best
199
+ // section per archive → carry that score as the archive's rank. Sender
200
+ // and date filters are applied post-vector (cheaper than pre-filtering
201
+ // a billion-row index, and the topK over-fetch absorbs the loss).
202
+ const cypher = `
203
+ CALL db.index.vector.queryNodes('section_embedding', $topK, $embedding)
204
+ YIELD node, score
205
+ WITH node, score
206
+ MATCH (a:ConversationArchive { accountId: $accountId, source: 'email' })
207
+ -[:HAS_SECTION]->(node)
208
+ WHERE ($dateFrom IS NULL OR node.lastMessageAt >= $dateFrom)
209
+ AND ($dateTo IS NULL OR node.firstMessageAt <= $dateTo)
210
+ WITH a, score
211
+ ORDER BY score DESC
212
+ WITH a, head(collect(score)) AS bestScore
213
+ OPTIONAL MATCH (p)-[:PARTICIPANT_IN]->(a)
214
+ WHERE p:Person OR p:AdminUser
215
+ WITH a, bestScore,
216
+ collect(DISTINCT coalesce(p.displayName, p.name, p.email, '')) AS participantNames,
217
+ collect(DISTINCT toLower(coalesce(p.email, ''))) AS participantEmails
218
+ WHERE $sender IS NULL
219
+ OR any(e IN participantEmails WHERE e CONTAINS toLower($sender))
220
+ OPTIONAL MATCH (a)-[:HAS_SECTION]->(allS:Section)
221
+ WITH a, bestScore, participantNames, allS
222
+ ORDER BY coalesce(allS.position, 0) ASC
223
+ WITH a, bestScore, participantNames, collect(allS) AS allSections
224
+ RETURN elementId(a) AS elementId,
225
+ coalesce(a.title, '') AS title,
226
+ [x IN allSections | coalesce(x.summary, '')] AS summaryParts,
227
+ [x IN allSections WHERE x.firstMessageAt IS NOT NULL | x.firstMessageAt] AS firsts,
228
+ [x IN allSections WHERE x.lastMessageAt IS NOT NULL | x.lastMessageAt] AS lasts,
229
+ coalesce(a.createdAt, '') AS archiveCreatedAt,
230
+ coalesce(a.lastIngestedMessageAt, a.updatedAt, '') AS archiveLastAt,
231
+ size(allSections) AS sectionCount,
232
+ [n IN participantNames WHERE n <> ''] AS participantNames,
233
+ bestScore AS score
234
+ ORDER BY score DESC
235
+ LIMIT $limit
236
+ `;
116
237
  const session = getSession();
117
238
  try {
118
- const result = await session.run(`CALL db.index.vector.queryNodes('knowledge_document_embedding', $topK, $embedding)
119
- YIELD node, score
120
- WHERE node.source = 'email' AND node.accountId = $accountId
121
- OPTIONAL MATCH (node)-[:FROM]->(sender:Person)
122
- WITH node, score, head(collect(sender)) AS rootSender
123
- RETURN node, score, rootSender
124
- ORDER BY score DESC
125
- LIMIT $limit`, {
126
- topK: neo4j.int(topK),
127
- embedding,
128
- accountId: params.accountId,
129
- limit: neo4j.int(limit),
130
- });
131
- return result.records.map((r) => {
132
- const record = nodeToRecord(r.get("node"), r.get("rootSender"));
133
- record.score = r.get("score");
134
- return record;
239
+ const result = await session.run(cypher, cyParams);
240
+ return result.records.map((rec) => {
241
+ const archive = recordToArchive(rec);
242
+ archive.score = rec.get("score");
243
+ return archive;
135
244
  });
136
245
  }
137
246
  finally {
@@ -141,19 +250,32 @@ async function searchMode(query, params, limit) {
141
250
  // ---------------------------------------------------------------------------
142
251
  // Helpers
143
252
  // ---------------------------------------------------------------------------
144
- function nodeToRecord(node, rootSender) {
145
- const p = node.properties;
146
- const sp = rootSender?.properties ?? null;
253
+ function toNumber(v) {
254
+ if (typeof v === "number")
255
+ return v;
256
+ if (typeof v?.toNumber === "function") {
257
+ return v.toNumber();
258
+ }
259
+ return Number(v ?? 0);
260
+ }
261
+ function recordToArchive(rec) {
262
+ const summaryParts = rec.get("summaryParts") ?? [];
263
+ const firsts = rec.get("firsts") ?? [];
264
+ const lasts = rec.get("lasts") ?? [];
265
+ const firstMessageAt = firsts.length > 0
266
+ ? firsts.slice().sort()[0]
267
+ : rec.get("archiveCreatedAt") ?? "";
268
+ const lastMessageAt = lasts.length > 0
269
+ ? lasts.slice().sort()[lasts.length - 1]
270
+ : rec.get("archiveLastAt") ?? "";
147
271
  return {
148
- attachmentId: p.attachmentId,
149
- subject: p.subject ?? "(no subject)",
150
- body: p.body ?? "",
151
- messageIds: p.messageIds ?? [],
152
- firstReceivedAt: p.firstReceivedAt ?? "",
153
- lastReceivedAt: p.lastReceivedAt ?? "",
154
- direction: p.direction ?? null,
155
- rootFromAddress: sp ? (sp.email ?? null) : null,
156
- rootFromName: sp ? (sp.displayName ?? null) : null,
272
+ elementId: rec.get("elementId"),
273
+ title: rec.get("title") ?? "",
274
+ summary: summaryParts.filter((s) => s).join("\n\n"),
275
+ firstMessageAt,
276
+ lastMessageAt,
277
+ sectionCount: toNumber(rec.get("sectionCount")),
278
+ participantNames: rec.get("participantNames") ?? [],
157
279
  };
158
280
  }
159
281
  function formatEmptyResult(params, query) {
@@ -166,8 +288,6 @@ function formatEmptyResult(params, query) {
166
288
  filters.push(`from: ${params.dateFrom}`);
167
289
  if (params.dateTo)
168
290
  filters.push(`to: ${params.dateTo}`);
169
- if (params.direction)
170
- filters.push(`direction: ${params.direction}`);
171
- return `No email threads found in the graph${filters.length > 0 ? ` matching ${filters.join(", ")}` : ""}.`;
291
+ return `No email archives found in the graph${filters.length > 0 ? ` matching ${filters.join(", ")}` : ""}.`;
172
292
  }
173
293
  //# sourceMappingURL=email-graph-query.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-graph-query.js","sourceRoot":"","sources":["../../src/tools/email-graph-query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-graph-query.js","sourceRoot":"","sources":["../../src/tools/email-graph-query.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"email-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/email-ingest.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAMD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAuFvE"}
1
+ {"version":3,"file":"email-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/email-ingest.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAMD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA4GvE"}
@@ -1,7 +1,9 @@
1
- import { updatePollState } from "../lib/credentials.js";
1
+ import { loadCredentials, updatePollState } from "../lib/credentials.js";
2
2
  import { stripAngleBrackets } from "../lib/graph.js";
3
- import { dispatchEmailMessagesToConversationArchive } from "../lib/conversation-archive-dispatch.js";
3
+ import { dispatchEmailMessagesToConversationArchive, } from "../lib/conversation-archive-dispatch.js";
4
4
  import { takeBatch, peekBatch } from "../lib/ingest-batch.js";
5
+ import { fetchAttachmentBytesForMessages } from "../lib/imap.js";
6
+ import { archiveAttachment, sha256OfBytes, syntheticAttachmentId, } from "../lib/attachment-archive.js";
5
7
  function normaliseId(id) {
6
8
  return stripAngleBrackets(id)?.toLowerCase() ?? null;
7
9
  }
@@ -72,15 +74,30 @@ export async function emailIngest(params) {
72
74
  }
73
75
  }
74
76
  toIngest.sort((a, b) => a.receivedAt.localeCompare(b.receivedAt));
75
- const { threadsDispatched, threadsSkipped, unresolvedAddresses } = await dispatchEmailMessagesToConversationArchive(toIngest);
77
+ // ─── Attachment byte fetch + on-disk archive (Task 402) ───────────────
78
+ //
79
+ // For every approved message, look up its attachment metadata in the
80
+ // cached batch, fetch the bytes from IMAP in one connection, archive
81
+ // them to disk, and build the per-attachment records the dispatcher
82
+ // needs to MERGE :DigitalDocument nodes.
83
+ const ingestedUids = new Set(toIngest.map((m) => m.uid));
84
+ const metaByUid = new Map();
85
+ for (const m of batch.messages) {
86
+ if (ingestedUids.has(m.uid) && m.attachments.length > 0) {
87
+ metaByUid.set(m.uid, m.attachments);
88
+ }
89
+ }
90
+ const attachmentRecords = await buildAttachmentRecords(params.accountId, batch.uidValidity, metaByUid);
91
+ const { threadsDispatched, threadsSkipped, unresolvedAddresses, attachmentsAttached, attachmentsFailed } = await dispatchEmailMessagesToConversationArchive(toIngest, attachmentRecords);
76
92
  // Advance the high-water mark so the next email-fetch starts after this batch.
77
93
  await updatePollState(params.accountId, batch.maxUid, batch.uidValidity);
78
94
  // Consume the cached batch only after a successful write + state advance.
79
95
  takeBatch(params.accountId);
80
- console.error(`[email-ingest] tag=email-ingest ingested=${toIngest.length} discarded=${discarded} threadsDispatched=${threadsDispatched} threadsSkipped=${threadsSkipped} unresolved=${unresolvedAddresses.length}`);
96
+ console.error(`[email-ingest] tag=email-ingest ingested=${toIngest.length} discarded=${discarded} threadsDispatched=${threadsDispatched} threadsSkipped=${threadsSkipped} unresolved=${unresolvedAddresses.length} attachmentsAttached=${attachmentsAttached} attachmentsFailed=${attachmentsFailed}`);
81
97
  const lines = [
82
98
  `Ingested ${toIngest.length} message(s); discarded ${discarded}.`,
83
99
  `Graph: ${threadsDispatched} thread(s) dispatched to conversation-archive, ${threadsSkipped} skipped.`,
100
+ `Attachments: ${attachmentsAttached} archived as :DigitalDocument, ${attachmentsFailed} failed.`,
84
101
  `Inbox high-water mark advanced to UID ${batch.maxUid}.`,
85
102
  ];
86
103
  if (unresolvedAddresses.length > 0) {
@@ -88,4 +105,87 @@ export async function emailIngest(params) {
88
105
  }
89
106
  return lines.join("\n");
90
107
  }
108
+ // ---------------------------------------------------------------------------
109
+ // Attachment byte fetch + on-disk archive (Task 402).
110
+ //
111
+ // metaByUid maps approved-message UID → the attachment metadata extracted at
112
+ // email-fetch time (filename, mimeType, sizeBytes, partKey). For each
113
+ // metadata entry we either fetch bytes and archive to disk, or skip bytes
114
+ // (oversize / fetch-error) and write a synthetic-id record so the
115
+ // :DigitalDocument still lands with metadata-only.
116
+ // ---------------------------------------------------------------------------
117
+ async function buildAttachmentRecords(accountId, uidValidity, metaByUid) {
118
+ if (metaByUid.size === 0)
119
+ return [];
120
+ const accountDir = process.env.ACCOUNT_DIR;
121
+ if (!accountDir) {
122
+ console.error("[email-attach] WARN: ACCOUNT_DIR env not set; attachment bytes will not be archived (metadata-only :DigitalDocument nodes will still land).");
123
+ }
124
+ const credResult = await loadCredentials(accountId);
125
+ if ("error" in credResult) {
126
+ console.error(`[email-attach] WARN: loadCredentials failed during attachment fetch: ${credResult.error}`);
127
+ return [];
128
+ }
129
+ const { config, password } = credResult.credentials;
130
+ // Flatten the (uid, partKey) requests across all approved messages.
131
+ const requests = [];
132
+ for (const [uid, metas] of metaByUid.entries()) {
133
+ for (const m of metas) {
134
+ requests.push({ uid, partKey: m.partKey, expectedSize: m.sizeBytes, encoding: m.encoding, meta: m });
135
+ }
136
+ }
137
+ const fetched = await fetchAttachmentBytesForMessages(config, password, requests.map(({ uid, partKey, expectedSize, encoding }) => ({ uid, partKey, expectedSize, encoding })));
138
+ // Index by (uid, partKey) for O(1) lookup.
139
+ const fetchedKey = (uid, partKey) => `${uid}:${partKey}`;
140
+ const fetchedByKey = new Map(fetched.map((f) => [fetchedKey(f.uid, f.partKey), f]));
141
+ const records = [];
142
+ for (const req of requests) {
143
+ const f = fetchedByKey.get(fetchedKey(req.uid, req.partKey));
144
+ if (!f)
145
+ continue;
146
+ if (f.bytes === null) {
147
+ // Bytes not available — write a metadata-only record with a
148
+ // synthetic content-free attachmentId so the graph node still lands.
149
+ console.error(`[email-attach] metadata-only uid=${req.uid} part=${req.partKey} filename=${req.meta.filename} reason=${f.failureReason}`);
150
+ records.push({
151
+ uid: req.uid,
152
+ partKey: req.partKey,
153
+ filename: req.meta.filename,
154
+ mimeType: req.meta.mimeType,
155
+ sizeBytes: req.meta.sizeBytes,
156
+ attachmentId: syntheticAttachmentId(uidValidity, req.uid, req.partKey, req.meta.filename, req.meta.sizeBytes),
157
+ archivePath: null,
158
+ archived: false,
159
+ });
160
+ continue;
161
+ }
162
+ // Bytes in hand — archive to disk (best-effort) and use sha256(bytes)
163
+ // as the content-addressed attachmentId regardless of archive outcome.
164
+ const attachmentId = sha256OfBytes(f.bytes);
165
+ let archivePath = null;
166
+ let archived = false;
167
+ if (accountDir) {
168
+ const result = archiveAttachment(accountDir, uidValidity, req.uid, req.meta.filename, f.bytes);
169
+ if (result) {
170
+ archivePath = result.archivePath;
171
+ archived = true;
172
+ console.error(`[email-attach] archived uid=${req.uid} part=${req.partKey} sha256=${attachmentId.slice(0, 12)} path=${archivePath}`);
173
+ }
174
+ else {
175
+ console.error(`[email-attach] archive-skipped uid=${req.uid} part=${req.partKey} (write failed; node will still land with archivePath=null)`);
176
+ }
177
+ }
178
+ records.push({
179
+ uid: req.uid,
180
+ partKey: req.partKey,
181
+ filename: req.meta.filename,
182
+ mimeType: req.meta.mimeType,
183
+ sizeBytes: req.meta.sizeBytes,
184
+ attachmentId,
185
+ archivePath,
186
+ archived,
187
+ });
188
+ }
189
+ return records;
190
+ }
91
191
  //# sourceMappingURL=email-ingest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-ingest.js","sourceRoot":"","sources":["../../src/tools/email-ingest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-ingest.js","sourceRoot":"","sources":["../../src/tools/email-ingest.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"email-read.d.ts","sourceRoot":"","sources":["../../src/tools/email-read.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoEnE"}
1
+ {"version":3,"file":"email-read.d.ts","sourceRoot":"","sources":["../../src/tools/email-read.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0EnE"}
@@ -1,6 +1,6 @@
1
1
  import { loadCredentials } from "../lib/credentials.js";
2
2
  import { fetchRecent, MAX_LIMIT } from "../lib/imap.js";
3
- import { getThreadInfo, stripAngleBrackets } from "../lib/graph.js";
3
+ import { getMessageArchiveHints } from "../lib/conversation-archive-lookup.js";
4
4
  export async function emailRead(params) {
5
5
  const result = await loadCredentials(params.accountId);
6
6
  if ("error" in result)
@@ -38,14 +38,21 @@ export async function emailRead(params) {
38
38
  filters.push(`folder: ${params.folder}`);
39
39
  return `No messages found${filters.length > 0 ? ` matching ${filters.join(", ")}` : ""}.`;
40
40
  }
41
- // Enrich with thread info from the graph (best-effort — failures return empty map)
42
- const messageIds = messages.map((m) => m.messageId).filter((id) => !!id);
43
- let threadMap = new Map();
41
+ // Per-archive enrichment from :ConversationArchive {source:'email'}.
42
+ // Best-effort failures and empty results degrade silently. Matches on
43
+ // (accountId, receivedAt within :Section.firstMessageAt..lastMessageAt)
44
+ // and surfaces the parent archive's title + the matching section's
45
+ // ordered position. Semantics shift from Task-321 per-message thread
46
+ // depth to per-archive ordering — see Task 445.
47
+ const hintInput = messages
48
+ .filter((m) => m.messageId)
49
+ .map((m) => ({ messageId: m.messageId, receivedAt: m.date }));
50
+ let hints = new Map();
44
51
  try {
45
- threadMap = await getThreadInfo(messageIds, params.accountId);
52
+ hints = await getMessageArchiveHints(hintInput, params.accountId);
46
53
  }
47
54
  catch {
48
- // Thread enrichment is optional — continue without it
55
+ // Enrichment is optional — continue without it
49
56
  }
50
57
  const lines = messages
51
58
  .map((m) => {
@@ -54,12 +61,11 @@ export async function emailRead(params) {
54
61
  entry += `\nMessage-ID: ${m.messageId}`;
55
62
  if (m.inReplyTo)
56
63
  entry += `\nIn-Reply-To: ${m.inReplyTo}`;
57
- const normalisedMid = stripAngleBrackets(m.messageId);
58
- if (normalisedMid) {
59
- const thread = threadMap.get(normalisedMid);
60
- if (thread) {
61
- entry += `\nThread-Depth: ${thread.threadDepth}`;
62
- entry += `\nThread-ID: ${thread.threadId}`;
64
+ if (m.messageId) {
65
+ const hint = hints.get(m.messageId);
66
+ if (hint) {
67
+ entry += `\nArchive: ${hint.archiveTitle}`;
68
+ entry += `\nArchive-Section: ${hint.sectionIndex}`;
63
69
  }
64
70
  }
65
71
  if (m.bodyPreview)