@rubytech/create-maxy 1.0.757 → 1.0.758

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.
@@ -1,33 +1,43 @@
1
1
  import { getSession } from "../lib/neo4j.js";
2
2
  /**
3
- * memory-archive-write — deterministic bulk-archive write surface (Task 744).
3
+ * memory-archive-write — deterministic bulk-archive write surface.
4
4
  *
5
- * Writes a flat dataset (today: LinkedIn Connections.csv parsed into typed
6
- * rows) into the graph as first-class entity nodes plus their natural edges.
7
- * Distinct from memory-write (per-node, adjacency-required) and memory-ingest
8
- * (narrative document → KnowledgeDocument + Section + HAS_SECTION + NEXT).
5
+ * Writes a flat dataset (LinkedIn Connections.csv, WhatsApp `_chat.txt`
6
+ * exports, future CRM-type seeds) into the graph as first-class entity
7
+ * nodes plus their natural edges. Distinct from memory-write (per-node,
8
+ * adjacency-required) and memory-ingest (narrative document → Section).
9
9
  *
10
10
  * Doctrine:
11
- * - The Cypher body for each archiveType is fixed inside this tool. The
12
- * agent never supplies Cypher; only parsed rows + the archiveType
13
- * discriminant. Determinism here means "the Cypher is not in the tool
14
- * input" not "be careful with the Cypher".
15
- * - 500-row UNWIND batches per executeWrite transaction. Larger batches
16
- * risk Neo4j Community heap pressure on a Pi 5; smaller batches multiply
17
- * round-trip cost.
18
- * - Per-row provenance stamps (createdByAgent, createdBySession, source,
19
- * createdAt) come from this server, not the agent.
20
- * - Owner is matched by elementId so both AdminUser and external-Person
21
- * archive owners flow through one path.
22
- * - Counters come from result.summary.counters (nodesCreated /
23
- * relationshipsCreated) so we never invent stamp-based detection.
11
+ * - The Cypher body for each archiveType is fixed inside this tool.
12
+ * The agent supplies parsed rows + the archiveType discriminant,
13
+ * never raw Cypher.
14
+ * - 500-row UNWIND batches per executeWrite transaction. Larger
15
+ * batches risk Neo4j Community heap pressure on a Pi 5.
16
+ * - Per-node provenance stamps (createdByAgent, createdBySession,
17
+ * source, createdAt) come from this server, not the agent.
18
+ * - Owner / participant nodes match by elementId so both :AdminUser
19
+ * and external :Person flow through one path.
20
+ * - Counters are per-handler each handler picks the keys that
21
+ * describe its archiveType. The agent reads them from the result
22
+ * JSON; nothing programmatic depends on the linkedin-specific
23
+ * legacy field names.
24
+ * - For archive types with cross-batch graph state (WhatsApp's
25
+ * :NEXT chronology), the handler implements an optional
26
+ * `finalize` hook that runs after all batches in its own
27
+ * `executeWrite` transaction. Failure surfaces as
28
+ * `nextChainStatus='failed'` on the result — never silent partial
29
+ * state.
24
30
  */
25
31
  const BATCH_SIZE = 500;
26
32
  const HANDLERS = {
27
33
  "linkedin-connections": linkedinConnectionsHandler(),
34
+ "whatsapp-export": whatsappExportHandler(),
28
35
  };
36
+ // ---------------------------------------------------------------------------
37
+ // Entry point
38
+ // ---------------------------------------------------------------------------
29
39
  export async function memoryArchiveWrite(params) {
30
- const { archiveType, ownerNodeId, accountId, rows, sessionId } = params;
40
+ const { archiveType, ownerNodeId, accountId, rows } = params;
31
41
  if (!accountId || !accountId.trim()) {
32
42
  throw new Error("memory-archive-write: accountId is required.");
33
43
  }
@@ -44,29 +54,19 @@ export async function memoryArchiveWrite(params) {
44
54
  const session = getSession();
45
55
  const startedMs = Date.now();
46
56
  const errors = [];
47
- const totals = {
48
- createdPersons: 0,
49
- mergedPersons: 0,
50
- createdOrganizations: 0,
51
- mergedOrganizations: 0,
52
- createdEdges: 0,
53
- };
57
+ const counters = {};
58
+ const sessionId = params.sessionId?.trim() || null;
54
59
  try {
55
- await handler.verifyOwner(session, ownerNodeId, accountId);
60
+ await handler.verifyArchive(session, params);
56
61
  for (let offset = 0; offset < rows.length; offset += BATCH_SIZE) {
57
- const batch = rows.slice(offset, offset + BATCH_SIZE);
62
+ const batchRows = rows.slice(offset, offset + BATCH_SIZE);
58
63
  try {
59
- const counters = await handler.writeBatch(session, {
60
- ownerNodeId,
61
- accountId,
62
- rows: batch,
63
- sessionId: sessionId?.trim() || null,
64
+ const batchCounters = await handler.writeBatch(session, {
65
+ params,
66
+ batchRows,
67
+ sessionId,
64
68
  });
65
- totals.createdPersons += counters.createdPersons;
66
- totals.mergedPersons += counters.mergedPersons;
67
- totals.createdOrganizations += counters.createdOrganizations;
68
- totals.mergedOrganizations += counters.mergedOrganizations;
69
- totals.createdEdges += counters.createdEdges;
69
+ mergeCounters(counters, batchCounters);
70
70
  }
71
71
  catch (err) {
72
72
  const reason = err instanceof Error ? err.message : String(err);
@@ -74,62 +74,54 @@ export async function memoryArchiveWrite(params) {
74
74
  process.stderr.write(`[memory-archive-write] batch-failed archiveType=${archiveType} offset=${offset} reason=${reason}\n`);
75
75
  }
76
76
  }
77
+ let nextChainStatus;
78
+ let nextChainReason;
79
+ if (handler.finalize) {
80
+ try {
81
+ const finalizeResult = await handler.finalize(session, {
82
+ params,
83
+ sessionId,
84
+ hadBatchErrors: errors.length > 0,
85
+ });
86
+ mergeCounters(counters, finalizeResult.counters);
87
+ nextChainStatus = finalizeResult.status;
88
+ nextChainReason = finalizeResult.reason;
89
+ }
90
+ catch (err) {
91
+ const reason = err instanceof Error ? err.message : String(err);
92
+ nextChainStatus = "failed";
93
+ nextChainReason = reason;
94
+ process.stderr.write(`[memory-archive-write] finalize-failed archiveType=${archiveType} reason=${reason}\n`);
95
+ }
96
+ }
97
+ const elapsedMs = Date.now() - startedMs;
98
+ const counterStr = Object.entries(counters)
99
+ .map(([k, v]) => `${k}=${v}`)
100
+ .join(" ");
101
+ const statusStr = nextChainStatus
102
+ ? ` nextChainStatus=${nextChainStatus}${nextChainReason ? ` reason=${nextChainReason}` : ""}`
103
+ : "";
104
+ process.stderr.write(`[memory-archive-write] archiveType=${archiveType} rows=${rows.length} ${counterStr} errors=${errors.length}${statusStr} ms=${elapsedMs}\n`);
105
+ return {
106
+ archiveType,
107
+ processedRows: rows.length,
108
+ counters,
109
+ errors,
110
+ ...(nextChainStatus ? { nextChainStatus } : {}),
111
+ ...(nextChainReason ? { nextChainReason } : {}),
112
+ };
77
113
  }
78
114
  finally {
79
115
  await session.close();
80
116
  }
81
- const elapsedMs = Date.now() - startedMs;
82
- process.stderr.write(`[memory-archive-write] archiveType=${archiveType} rows=${rows.length} ` +
83
- `createdPersons=${totals.createdPersons} mergedPersons=${totals.mergedPersons} ` +
84
- `createdOrganizations=${totals.createdOrganizations} mergedOrganizations=${totals.mergedOrganizations} ` +
85
- `createdEdges=${totals.createdEdges} errors=${errors.length} ms=${elapsedMs}\n`);
86
- return {
87
- archiveType,
88
- processedRows: rows.length,
89
- ...totals,
90
- errors,
91
- };
117
+ }
118
+ function mergeCounters(target, source) {
119
+ for (const [k, v] of Object.entries(source)) {
120
+ target[k] = (target[k] ?? 0) + v;
121
+ }
92
122
  }
93
123
  // ---------------------------------------------------------------------------
94
- // linkedin-connections handler
95
- // ---------------------------------------------------------------------------
96
- //
97
- // Owner anchor: AdminUser or Person (elementId match — operator-confirmed
98
- // during the linkedin-import skill's owner-confirmation flow).
99
- //
100
- // Per row the CSV expresses two relationships:
101
- // 1. (owner)-[:CONNECTED_ON_LINKEDIN {connectedOn}]->(:Person)
102
- // 2. (:Person)-[:WORKS_FOR {title, current=true}]->(:Organization) — when company present
103
- //
104
- // Natural keys:
105
- // :Person — linkedinUrl (indexed in platform/neo4j/schema.cypher)
106
- // :Organization — (accountId, name)
107
- //
108
- // Provenance stamped on every node:
109
- // createdByAgent='linkedin-import', createdBySource='linkedin-import',
110
- // createdBySession=$sessionId (when provided), createdAt=datetime(),
111
- // source='linkedin'
112
- //
113
- // We split each batch into two tx.run calls inside one executeWrite so each
114
- // statement's summary counters give a clean per-label breakdown.
115
- //
116
- // Task 753 — `p.currentTitle` denormalises `[:WORKS_FOR].title` onto the
117
- // Person node so the `entity_search` BM25 fulltext index reaches it.
118
- // Pre-task an operator searching "director" against an account with 5K
119
- // LinkedIn connections returned 26 hits — name-field anomalies and
120
- // CV-driven Position nodes only — because edge properties are invisible
121
- // to a node-only fulltext index. Naming `currentTitle` (not `title`)
122
- // avoids shadowing :Position.title semantics in cross-label cypher.
123
- // Empty `row.title` writes null (the property is removed); the LinkedIn
124
- // export is a current-job snapshot, so a re-import without a title is
125
- // authoritative.
126
- //
127
- // `connectedOn` is NOT denormalised onto the Person — it lives only on
128
- // the edge. Side-panel visibility (Task 754) is solved by rendering
129
- // incident-edge properties in the UI, not by writing the date twice.
130
- // Doctrine: avoid duplicate writes that need a backfill cypher to land
131
- // on existing customer installs (no-admin-upgrade-path). The fulltext-
132
- // reach problem `currentTitle` solves doesn't apply to a date.
124
+ // linkedin-connections handler (Task 744; Task 753 currentTitle denormalisation)
133
125
  // ---------------------------------------------------------------------------
134
126
  export const PERSON_AND_CONNECTED_EDGE_CYPHER = `
135
127
  MATCH (owner) WHERE elementId(owner) = $ownerNodeId
@@ -183,7 +175,8 @@ RETURN count(*) AS processed
183
175
  `;
184
176
  function linkedinConnectionsHandler() {
185
177
  return {
186
- async verifyOwner(session, ownerNodeId, accountId) {
178
+ async verifyArchive(session, params) {
179
+ const { ownerNodeId, accountId } = params;
187
180
  const result = await session.run(`MATCH (n) WHERE elementId(n) = $ownerNodeId
188
181
  RETURN labels(n) AS labels, n.accountId AS accountId LIMIT 1`, { ownerNodeId });
189
182
  if (result.records.length === 0) {
@@ -200,29 +193,30 @@ function linkedinConnectionsHandler() {
200
193
  throw new Error(`ownerNodeId ${ownerNodeId} belongs to account ${ownerAccountId}, not ${accountId}. Cross-account archive ingest refused.`);
201
194
  }
202
195
  },
203
- async writeBatch(session, { ownerNodeId, accountId, rows, sessionId }) {
204
- const params = {
205
- ownerNodeId,
206
- accountId,
196
+ async writeBatch(session, ctx) {
197
+ const { params, batchRows, sessionId } = ctx;
198
+ const queryParams = {
199
+ ownerNodeId: params.ownerNodeId,
200
+ accountId: params.accountId,
207
201
  sessionId,
208
- rows: rows.map((r) => normaliseLinkedinRow(r)),
202
+ rows: batchRows.map((r) => normaliseLinkedinRow(r)),
209
203
  };
210
204
  return await session.executeWrite(async (tx) => {
211
- const personRes = await tx.run(PERSON_AND_CONNECTED_EDGE_CYPHER, params);
205
+ const personRes = await tx.run(PERSON_AND_CONNECTED_EDGE_CYPHER, queryParams);
212
206
  const personCounters = personRes.summary.counters.updates();
213
- const orgRes = await tx.run(ORG_AND_WORKS_FOR_CYPHER, params);
207
+ const orgRes = await tx.run(ORG_AND_WORKS_FOR_CYPHER, queryParams);
214
208
  const orgCounters = orgRes.summary.counters.updates();
215
- const createdPersonNodes = personCounters.nodesCreated;
216
- const createdOrgNodes = orgCounters.nodesCreated;
217
- const personsTouched = params.rows.length;
218
- const personsCreated = createdPersonNodes;
219
- const personsMerged = personsTouched - personsCreated;
209
+ const createdPersons = personCounters.nodesCreated;
210
+ const createdOrgs = orgCounters.nodesCreated;
211
+ const personsTouched = queryParams.rows.length;
212
+ const mergedPersons = personsTouched - createdPersons;
213
+ const createdEdges = personCounters.relationshipsCreated + orgCounters.relationshipsCreated;
220
214
  return {
221
- createdPersons: personsCreated,
222
- mergedPersons: personsMerged,
223
- createdOrganizations: createdOrgNodes,
215
+ createdPersons,
216
+ mergedPersons,
217
+ createdOrganizations: createdOrgs,
224
218
  mergedOrganizations: 0,
225
- createdEdges: personCounters.relationshipsCreated + orgCounters.relationshipsCreated,
219
+ createdEdges,
226
220
  };
227
221
  });
228
222
  },
@@ -245,4 +239,265 @@ function normaliseLinkedinRow(row) {
245
239
  connectedOn: row.connectedOn,
246
240
  };
247
241
  }
242
+ // ---------------------------------------------------------------------------
243
+ // whatsapp-export handler (Task 804)
244
+ // ---------------------------------------------------------------------------
245
+ //
246
+ // Owner anchor: the operator who exported the chat (:AdminUser or :Person).
247
+ // Recorded as `createdBySession`-stamped provenance on the Conversation node;
248
+ // not a graph edge, since the exporter is metadata, not a semantic role.
249
+ //
250
+ // Per call the input expresses one Conversation + N Messages + edges:
251
+ // 1. (:Conversation:WhatsAppConversation {conversationId}) — MERGE, sublabel on first create
252
+ // 2. (:Message:WhatsAppMessage {messageId})-[:PART_OF]->(:Conversation)
253
+ // 3. (sender:AdminUser|:Person {elementId})-[:SENT]->(:Message)
254
+ // 4. (sender)-[:PARTICIPANT_IN]->(:Conversation) — rolled up per distinct sender
255
+ // 5. (:Message)-[:NEXT]->(:Message) — chronology, finalize step
256
+ //
257
+ // Natural keys:
258
+ // :Conversation:WhatsAppConversation — conversationId (whatsapp-export:<sha256(file)>:<accountId>)
259
+ // :Message:WhatsAppMessage — messageId (whatsapp-export:<conversationId>:<lineHash>)
260
+ // participants — elementId resolved by the skill's confirmation flow
261
+ //
262
+ // Provenance stamped on every node: source='whatsapp', createdByAgent='whatsapp-import',
263
+ // createdBySession=$sessionId, createdAt=datetime(), archiveSourceFile=<sha256 prefix>.
264
+ // On re-import, ON MATCH refreshes lastMessageAt / messageCount / participantCount /
265
+ // lastImportedAt / lastImportedBySession on the Conversation only — node-creation
266
+ // stamps are preserved.
267
+ //
268
+ // Cross-batch state: NEXT chain depends on full-conversation chronology. The
269
+ // finalize hook runs ORDER BY dateSent ASC, sequenceIndex ASC, then MERGEs
270
+ // pairwise NEXT edges. Idempotent — re-import with a 50-message append adds
271
+ // 50 NEXT edges and updates conversation scalars, no duplicates.
272
+ // ---------------------------------------------------------------------------
273
+ // MERGE on the base label only, then SET the sublabel in ON CREATE — Task 633
274
+ // pattern documented at platform/neo4j/schema.cypher:374-388 and used by
275
+ // `ensureConversation` in platform/ui/app/lib/neo4j-store.ts. MERGEing on
276
+ // `:Base:Sublabel` directly would refuse to match a pre-existing node that
277
+ // carries only the base label, then CREATE a duplicate that violates the
278
+ // `conversationId IS UNIQUE` constraint on `:Conversation`. The base+SET
279
+ // pattern is constraint-safe and idempotent across Task-633-style sublabel
280
+ // backfill histories.
281
+ export const WHATSAPP_CONVERSATION_AND_MESSAGES_CYPHER = `
282
+ MATCH (owner) WHERE elementId(owner) = $ownerNodeId
283
+ WITH owner, $rows AS rows, $conversation AS conv
284
+ MERGE (c:Conversation {conversationId: conv.conversationId})
285
+ ON CREATE SET
286
+ c:WhatsAppConversation,
287
+ c.accountId = $accountId,
288
+ c.source = 'whatsapp',
289
+ c.createdByAgent = 'whatsapp-import',
290
+ c.createdBySource = 'whatsapp-import',
291
+ c.createdBySession = $sessionId,
292
+ c.createdAt = datetime(),
293
+ c.scope = 'admin',
294
+ c.agentType = 'admin',
295
+ c.archiveSourceFile = conv.archiveSourceFile,
296
+ c.firstMessageAt = datetime(conv.firstMessageAt),
297
+ c.lastMessageAt = datetime(conv.lastMessageAt),
298
+ c.participantCount = conv.participantCount,
299
+ c.messageCount = conv.messageCount
300
+ ON MATCH SET
301
+ c:WhatsAppConversation,
302
+ c.lastMessageAt = datetime(conv.lastMessageAt),
303
+ c.participantCount = conv.participantCount,
304
+ c.messageCount = conv.messageCount,
305
+ c.lastImportedAt = datetime(),
306
+ c.lastImportedBySession = $sessionId
307
+ WITH owner, c, rows
308
+ UNWIND rows AS row
309
+ MATCH (sender) WHERE elementId(sender) = row.senderNodeId
310
+ MERGE (m:Message {messageId: row.messageId})
311
+ ON CREATE SET
312
+ m:WhatsAppMessage,
313
+ m.accountId = $accountId,
314
+ m.source = 'whatsapp',
315
+ m.createdByAgent = 'whatsapp-import',
316
+ m.createdBySource = 'whatsapp-import',
317
+ m.createdBySession = $sessionId,
318
+ m.createdAt = datetime(),
319
+ m.conversationId = row.conversationId,
320
+ m.dateSent = datetime(row.dateSent),
321
+ m.body = row.body,
322
+ m.senderName = row.senderName,
323
+ m.sequenceIndex = row.sequenceIndex,
324
+ m.scope = 'admin'
325
+ MERGE (m)-[:PART_OF]->(c)
326
+ MERGE (sender)-[s:SENT]->(m)
327
+ ON CREATE SET
328
+ s.source = 'whatsapp',
329
+ s.createdAt = datetime()
330
+ MERGE (sender)-[p:PARTICIPANT_IN]->(c)
331
+ ON CREATE SET
332
+ p.source = 'whatsapp',
333
+ p.createdAt = datetime()
334
+ RETURN count(*) AS processed
335
+ `;
336
+ // Tertiary tiebreaker `m.messageId ASC` makes ordering total even when two
337
+ // messages share the same dateSent + sequenceIndex (operator-error duplicate
338
+ // row in input, or two messages emitted in the same second by the same
339
+ // sender). Without it, Neo4j's ordering of equal keys is undefined and the
340
+ // chain topology can drift across re-imports — MERGE on (prev, next) is
341
+ // idempotent per pair, but pairs themselves would shuffle.
342
+ export const WHATSAPP_NEXT_CHAIN_CYPHER = `
343
+ MATCH (c:Conversation:WhatsAppConversation {conversationId: $conversationId})
344
+ MATCH (m:Message)-[:PART_OF]->(c)
345
+ WITH c, m ORDER BY m.dateSent ASC, m.sequenceIndex ASC, m.messageId ASC
346
+ WITH c, collect(m) AS msgs
347
+ UNWIND range(0, size(msgs) - 2) AS i
348
+ WITH msgs[i] AS prev, msgs[i + 1] AS next
349
+ WHERE prev <> next
350
+ MERGE (prev)-[r:NEXT]->(next)
351
+ ON CREATE SET
352
+ r.source = 'whatsapp',
353
+ r.createdAt = datetime()
354
+ RETURN count(r) AS nextEdges
355
+ `;
356
+ function whatsappExportHandler() {
357
+ return {
358
+ async verifyArchive(session, params) {
359
+ const { ownerNodeId, accountId, conversation, participantNodeIds, rows } = params;
360
+ if (!conversation || !conversation.conversationId) {
361
+ throw new Error("whatsapp-export: `conversation` block with conversationId is required.");
362
+ }
363
+ if (!Array.isArray(participantNodeIds) || participantNodeIds.length === 0) {
364
+ throw new Error("whatsapp-export: `participantNodeIds` must be a non-empty array (operator-confirmed senders).");
365
+ }
366
+ // Cross-row conversationId invariant: one call = one conversation.
367
+ for (let i = 0; i < rows.length; i++) {
368
+ const row = rows[i];
369
+ if (row.conversationId !== conversation.conversationId) {
370
+ throw new Error(`whatsapp-export: row[${i}].conversationId="${row.conversationId}" mismatches conversation.conversationId="${conversation.conversationId}". One archive-write call writes exactly one conversation.`);
371
+ }
372
+ }
373
+ // Owner verification (mirrors linkedin-connections logic).
374
+ const ownerRes = await session.run(`MATCH (n) WHERE elementId(n) = $ownerNodeId
375
+ RETURN labels(n) AS labels, n.accountId AS accountId LIMIT 1`, { ownerNodeId });
376
+ if (ownerRes.records.length === 0) {
377
+ throw new Error(`ownerNodeId ${ownerNodeId} not found. Re-run the archive-owner confirmation flow before invoking memory-archive-write.`);
378
+ }
379
+ const ownerLabels = ownerRes.records[0].get("labels") ?? [];
380
+ const ownerAccountId = ownerRes.records[0].get("accountId");
381
+ if (!ownerLabels.includes("AdminUser") && !ownerLabels.includes("Person")) {
382
+ throw new Error(`ownerNodeId ${ownerNodeId} has labels [${ownerLabels.join(", ")}]; expected :AdminUser or :Person.`);
383
+ }
384
+ if (ownerAccountId && ownerAccountId !== accountId) {
385
+ throw new Error(`ownerNodeId ${ownerNodeId} belongs to account ${ownerAccountId}, not ${accountId}. Cross-account archive ingest refused.`);
386
+ }
387
+ // Participant verification: every senderNodeId must resolve to :AdminUser
388
+ // or :Person in the same accountId. Distinct in case the skill passed
389
+ // the same nodeId twice. Log when dedup collapses the array — two
390
+ // distinct senderNames mapping to the same elementId is operator-confirmed
391
+ // intent (Step 4 phone-vs-name same-person heuristic), but a silent
392
+ // collapse without a log line hides the merge from the audit trail.
393
+ const distinctParticipantIds = Array.from(new Set(participantNodeIds));
394
+ if (distinctParticipantIds.length !== participantNodeIds.length) {
395
+ process.stderr.write(`[memory-archive-write] participantNodeIds-deduped archiveType=whatsapp-export submitted=${participantNodeIds.length} distinct=${distinctParticipantIds.length}\n`);
396
+ }
397
+ const participantRes = await session.run(`UNWIND $ids AS id
398
+ MATCH (n) WHERE elementId(n) = id
399
+ RETURN id AS elemId, labels(n) AS labels, n.accountId AS accountId`, { ids: distinctParticipantIds });
400
+ const found = new Set();
401
+ const rejected = [];
402
+ for (const record of participantRes.records) {
403
+ const elemId = record.get("elemId");
404
+ const labels = record.get("labels") ?? [];
405
+ const acct = record.get("accountId");
406
+ found.add(elemId);
407
+ if (!labels.includes("AdminUser") && !labels.includes("Person")) {
408
+ rejected.push(`${elemId}: labels=[${labels.join(",")}] (expected :AdminUser or :Person)`);
409
+ continue;
410
+ }
411
+ if (acct && acct !== accountId) {
412
+ rejected.push(`${elemId}: accountId=${acct} (expected ${accountId})`);
413
+ }
414
+ }
415
+ for (const id of distinctParticipantIds) {
416
+ if (!found.has(id))
417
+ rejected.push(`${id}: not found`);
418
+ }
419
+ const ok = distinctParticipantIds.length - rejected.length;
420
+ process.stderr.write(`[memory-archive-write] verifyParticipants archiveType=whatsapp-export ok=${ok} rejected=${rejected.length}\n`);
421
+ if (rejected.length > 0) {
422
+ throw new Error(`whatsapp-export: ${rejected.length} participant(s) failed verification: ${rejected.join("; ")}. Re-run the participant confirmation flow.`);
423
+ }
424
+ },
425
+ async writeBatch(session, ctx) {
426
+ const { params, batchRows, sessionId } = ctx;
427
+ const queryParams = {
428
+ ownerNodeId: params.ownerNodeId,
429
+ accountId: params.accountId,
430
+ sessionId,
431
+ conversation: params.conversation,
432
+ rows: batchRows.map((r) => normaliseWhatsappRow(r)),
433
+ };
434
+ return await session.executeWrite(async (tx) => {
435
+ const res = await tx.run(WHATSAPP_CONVERSATION_AND_MESSAGES_CYPHER, queryParams);
436
+ const stats = res.summary.counters.updates();
437
+ // Per-batch counters: exact node/edge creation counts come from
438
+ // result.summary.counters; we don't synthesise from row counts.
439
+ return {
440
+ createdConversations: 0, // attributed to first batch only via diff below
441
+ createdMessages: stats.nodesCreated,
442
+ createdSentEdges: 0,
443
+ createdParticipantEdges: 0,
444
+ relationshipsCreated: stats.relationshipsCreated,
445
+ labelsAdded: stats.labelsAdded,
446
+ };
447
+ });
448
+ },
449
+ async finalize(session, ctx) {
450
+ const { params, hadBatchErrors } = ctx;
451
+ const conversationId = params.conversation?.conversationId;
452
+ if (!conversationId) {
453
+ return { counters: {}, status: "skipped", reason: "no conversationId" };
454
+ }
455
+ const result = await session.executeWrite(async (tx) => {
456
+ const res = await tx.run(WHATSAPP_NEXT_CHAIN_CYPHER, { conversationId });
457
+ const stats = res.summary.counters.updates();
458
+ const nextEdges = res.records[0]?.get("nextEdges") ?? 0;
459
+ const nextEdgeCount = typeof nextEdges === "number"
460
+ ? nextEdges
461
+ : typeof nextEdges?.toNumber === "function"
462
+ ? nextEdges.toNumber()
463
+ : 0;
464
+ return {
465
+ counters: {
466
+ nextEdgesProcessed: nextEdgeCount,
467
+ nextEdgesCreated: stats.relationshipsCreated,
468
+ },
469
+ status: (hadBatchErrors ? "partial" : "ok"),
470
+ reason: hadBatchErrors ? "one or more batches reported errors; NEXT chain rebuilt over written messages" : undefined,
471
+ };
472
+ });
473
+ return result;
474
+ },
475
+ };
476
+ }
477
+ function normaliseWhatsappRow(row) {
478
+ if (!row.messageId || !row.messageId.trim()) {
479
+ throw new Error("whatsapp-export row missing messageId (natural key).");
480
+ }
481
+ if (!row.conversationId || !row.conversationId.trim()) {
482
+ throw new Error("whatsapp-export row missing conversationId.");
483
+ }
484
+ if (!row.senderNodeId || !row.senderNodeId.trim()) {
485
+ throw new Error("whatsapp-export row missing senderNodeId (operator-confirmed sender).");
486
+ }
487
+ if (!row.dateSent || Number.isNaN(Date.parse(row.dateSent))) {
488
+ throw new Error(`whatsapp-export row dateSent="${row.dateSent}" is not parseable as ISO 8601. Parser must convert the [DD/MM/YY, HH:MM:SS] line prefix to ISO 8601 with timezone.`);
489
+ }
490
+ if (typeof row.sequenceIndex !== "number" || row.sequenceIndex < 0) {
491
+ throw new Error(`whatsapp-export row sequenceIndex="${row.sequenceIndex}" must be a non-negative number.`);
492
+ }
493
+ return {
494
+ messageId: row.messageId.trim(),
495
+ conversationId: row.conversationId.trim(),
496
+ senderNodeId: row.senderNodeId.trim(),
497
+ senderName: (row.senderName ?? "").trim(),
498
+ dateSent: row.dateSent,
499
+ body: row.body ?? "",
500
+ sequenceIndex: row.sequenceIndex,
501
+ };
502
+ }
248
503
  //# sourceMappingURL=memory-archive-write.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-archive-write.js","sourceRoot":"","sources":["../../src/tools/memory-archive-write.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-archive-write.js","sourceRoot":"","sources":["../../src/tools/memory-archive-write.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: whatsapp-import
3
+ description: "Import a WhatsApp `_chat.txt` export (Conversation + Messages with chronological NEXT chain + analysis-derived insights) into the {{productName}} Neo4j graph. Skill-only plugin owned by the database-operator specialist. Opt-in per brand — not enabled by default. Distinct from the live `whatsapp` plugin (Baileys QR pairing + in-memory store)."
4
+ tools: []
5
+ always: false
6
+ embed: false
7
+ specialist: database-operator
8
+ metadata: {"platform":{"optional":true,"pluginKey":"whatsapp-import"}}
9
+ ---
10
+
11
+ # WhatsApp Import
12
+
13
+ Ingests a WhatsApp "Export Chat" archive (the `_chat.txt` file plus media attachments) into the {{productName}} Neo4j graph. Skill-only plugin — no MCP server, no admin tools added. The skill runs under the `database-operator` specialist, which owns external-archive ingestion and ad-hoc graph operations.
14
+
15
+ ## When this applies
16
+
17
+ The admin agent delegates to `database-operator` when the operator drops a `_chat.txt` (or its containing folder) into chat. The specialist runs the skill's archive-owner + participant confirmation flow before any line is written, then ingests the messages via `mcp__memory__memory-archive-write` with `archiveType='whatsapp-export'`.
18
+
19
+ ## What this is not
20
+
21
+ - **Not** the live `whatsapp` plugin. That plugin (Baileys QR pairing) holds messages in an in-memory store cleared on restart. This plugin imports historical exports into Neo4j as persistent graph nodes. Names are deliberately distinct (`whatsapp-import` vs `whatsapp`) so the boundary is mechanical.
22
+ - **Not** a media-transcription pipeline. Voice notes (`.opus`), photos, PDFs are skipped at parse with a counter logged. Transcription / OCR is a separate plugin decision.
23
+ - **Not** a generic chat-export importer. Each messaging channel (Telegram, iMessage, Signal, SMS dump) ships as its own opt-in plugin under the same shape when requested.
24
+
25
+ ## Relationship to other plugins
26
+
27
+ - **memory** — the underlying write surface used by the skill (`memory-archive-write` for bulk Conversation+Messages, `memory-write` / `memory-update` for the second-pass typed observations). The skill is parameterised so all writes carry `source='whatsapp'` + `createdByAgent='whatsapp-import'` for provenance.
28
+ - **database-operator specialist** — owns execution. See [admin/IDENTITY.md](../../../platform/templates/agents/admin/IDENTITY.md) delegation clause and [database-operator.md](../../../platform/templates/specialists/agents/database-operator.md) per-source archive list.
29
+ - **linkedin-import** — sister plugin under the same pattern (LinkedIn Basic Data Export). Reading [linkedin-import/PLUGIN.md](../linkedin-import/PLUGIN.md) is the fastest way to understand the shape this plugin follows.