@rubytech/create-maxy 1.0.808 → 1.0.810
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/plugins/memory/mcp/dist/index.js +86 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts +23 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js +401 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js.map +1 -0
- package/payload/platform/plugins/memory/references/schema-base.md +12 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +3 -1
- package/payload/platform/plugins/whatsapp-import/bin/ingest.mjs +225 -346
- package/payload/platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh +28 -10
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts +21 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js +41 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/filter.d.ts +29 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/filter.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/filter.js +123 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/filter.js.map +1 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts +4 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.js +9 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/filter-gate.test.ts +170 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/ingest-idempotence.test.ts +141 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/derive-keys.ts +59 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/filter.ts +136 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/index.ts +12 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +80 -25
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import-enrich/SKILL.md +22 -3
- package/payload/platform/templates/specialists/agents/database-operator.md +9 -4
- package/payload/server/public/assets/admin-Bwrd2DBq.js +352 -0
- package/payload/server/public/index.html +1 -1
- package/payload/server/server.js +271 -188
- package/payload/server/public/assets/admin-MxaCgGHZ.js +0 -352
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getSession } from "../lib/neo4j.js";
|
|
3
|
+
import { callOauthLlm } from "../../../../../lib/oauth-llm/dist/index.js";
|
|
4
|
+
import { HAIKU_MODEL } from "../../../../../lib/models/dist/index.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// whatsapp-export-insight-pass — Phase 2 chunked-Haiku insight extraction
|
|
7
|
+
// over an already-loaded WhatsApp Conversation (Task 871).
|
|
8
|
+
//
|
|
9
|
+
// Phase 1 (whatsapp-ingest.sh) is now LLM-free. The operator triggers this
|
|
10
|
+
// tool consciously via the `whatsapp-import-enrich` skill ("enrich the X
|
|
11
|
+
// chat"). Each chunk runs Haiku with a forced submit-tool call; the server
|
|
12
|
+
// applies a confidence>=0.8 gate per item before MERGE-keying it as an
|
|
13
|
+
// :Observation node in the graph. Re-running over the same conversation is
|
|
14
|
+
// idempotent — the MERGE key collapses identical items into one row.
|
|
15
|
+
//
|
|
16
|
+
// Doctrine alignment:
|
|
17
|
+
// - feedback_deterministic_means_remove_llm.md — Phase 1 has no LLM;
|
|
18
|
+
// this tool is the only sanctioned LLM entry for WhatsApp ingest.
|
|
19
|
+
// - feedback_compress_at_ingest_for_bulk_archives.md — small chunks
|
|
20
|
+
// (50 msgs, overlap 5) keep per-message attention, and the confidence
|
|
21
|
+
// gate compresses on write.
|
|
22
|
+
// - feedback_classifier_runs_off_oauth_not_api_key.md — admin-side
|
|
23
|
+
// classifier; OAuth, never the API key.
|
|
24
|
+
// - feedback_loud_failures.md — Haiku fallback / write failures log a
|
|
25
|
+
// structured line; the per-chunk loop continues on per-call errors so
|
|
26
|
+
// one chunk does not pollute the full pass.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const INSIGHT_CHUNK_SIZE = 50;
|
|
29
|
+
const INSIGHT_CHUNK_OVERLAP = 5;
|
|
30
|
+
const CONFIDENCE_THRESHOLD = 0.8;
|
|
31
|
+
const INSIGHT_SYSTEM_PROMPT = `You extract structured insights from a chunk of a WhatsApp conversation.
|
|
32
|
+
|
|
33
|
+
Return STRICT JSON via the provided tool. No prose, no commentary. Only items with concrete, verbatim evidence in the chunk. Empty arrays are valid; prefer omission to invention.
|
|
34
|
+
|
|
35
|
+
Definitions:
|
|
36
|
+
- "mention": a person, organisation, place, or named topic referred to by name.
|
|
37
|
+
- "task": something a participant committed to do or asked another to do (imperative or future-tense).
|
|
38
|
+
- "preference": stated like, dislike, opinion, or rule of behaviour.
|
|
39
|
+
- "observedRelationship": an explicit relational claim (works at, is married to, manages, etc.).
|
|
40
|
+
|
|
41
|
+
Every item carries a "confidence" float in [0,1]. Confidence reflects how unambiguously the evidence in the chunk supports the claim — not your prior belief about the world. Conversational filler ("let me know"), throwaway names without context, or speculative language ("maybe Ben works at Acme") MUST score below 0.8. The server discards items below 0.8.
|
|
42
|
+
|
|
43
|
+
Snippets must be ≤80 characters of the original message body, no sender names, no timestamps.`;
|
|
44
|
+
const INSIGHT_TOOL = {
|
|
45
|
+
name: "submit_insights",
|
|
46
|
+
description: "Submit the structured insights extracted from the chunk.",
|
|
47
|
+
input_schema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
mentions: {
|
|
51
|
+
type: "array",
|
|
52
|
+
items: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
name: { type: "string" },
|
|
56
|
+
snippet: { type: "string" },
|
|
57
|
+
confidence: { type: "number" },
|
|
58
|
+
},
|
|
59
|
+
required: ["name", "snippet", "confidence"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
tasks: {
|
|
63
|
+
type: "array",
|
|
64
|
+
items: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
task: { type: "string" },
|
|
68
|
+
snippet: { type: "string" },
|
|
69
|
+
confidence: { type: "number" },
|
|
70
|
+
},
|
|
71
|
+
required: ["task", "snippet", "confidence"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
preferences: {
|
|
75
|
+
type: "array",
|
|
76
|
+
items: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
subject: { type: "string" },
|
|
80
|
+
preference: { type: "string" },
|
|
81
|
+
confidence: { type: "number" },
|
|
82
|
+
},
|
|
83
|
+
required: ["subject", "preference", "confidence"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
observedRelationships: {
|
|
87
|
+
type: "array",
|
|
88
|
+
items: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
from: { type: "string" },
|
|
92
|
+
to: { type: "string" },
|
|
93
|
+
relationship: { type: "string" },
|
|
94
|
+
confidence: { type: "number" },
|
|
95
|
+
},
|
|
96
|
+
required: ["from", "to", "relationship", "confidence"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["mentions", "tasks", "preferences", "observedRelationships"],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
// MERGE-keyed Observation write. The natural key is
|
|
104
|
+
// (conversationId, sourceMessageRef, kind, contentHash) — Task 870's
|
|
105
|
+
// idempotence contract. `sourceMessageRef` is `chunk:<idx>` for items the
|
|
106
|
+
// chunked pass cannot attribute to a single :Message; Phase 2 enrichment
|
|
107
|
+
// recovers a message-level ref by snippet match later.
|
|
108
|
+
const INSIGHT_WRITE_CYPHER = `
|
|
109
|
+
MATCH (c:Conversation:WhatsAppConversation {conversationId: $conversationId})
|
|
110
|
+
UNWIND $observations AS obs
|
|
111
|
+
MERGE (o:Observation {
|
|
112
|
+
conversationId: $conversationId,
|
|
113
|
+
sourceMessageRef: obs.sourceMessageRef,
|
|
114
|
+
kind: obs.kind,
|
|
115
|
+
contentHash: obs.contentHash
|
|
116
|
+
})
|
|
117
|
+
ON CREATE SET
|
|
118
|
+
o:WhatsAppObservation,
|
|
119
|
+
o.accountId = $accountId,
|
|
120
|
+
o.summary = obs.summary,
|
|
121
|
+
o.snippet = obs.snippet,
|
|
122
|
+
o.subject = obs.subject,
|
|
123
|
+
o.from = obs.from,
|
|
124
|
+
o.to = obs.to,
|
|
125
|
+
o.confidence = obs.confidence,
|
|
126
|
+
o.source = 'whatsapp',
|
|
127
|
+
o.createdByAgent = 'whatsapp-export-insight-pass',
|
|
128
|
+
o.createdBySource = 'whatsapp-export-insight-pass',
|
|
129
|
+
o.createdBySession = $sessionId,
|
|
130
|
+
o.createdAt = datetime(),
|
|
131
|
+
o.scope = 'admin',
|
|
132
|
+
o.insightPass = true,
|
|
133
|
+
o.observationStatus = 'auto-extracted'
|
|
134
|
+
ON MATCH SET
|
|
135
|
+
o.lastSeenAt = datetime(),
|
|
136
|
+
o.lastSeenBySession = $sessionId
|
|
137
|
+
MERGE (o)-[r:OBSERVED_IN]->(c)
|
|
138
|
+
ON CREATE SET r.source = 'whatsapp', r.createdAt = datetime()
|
|
139
|
+
RETURN count(o) AS touched
|
|
140
|
+
`;
|
|
141
|
+
export async function whatsappExportInsightPass(params) {
|
|
142
|
+
const { conversationId, accountId } = params;
|
|
143
|
+
const sessionId = params.sessionId ?? null;
|
|
144
|
+
const startedMs = Date.now();
|
|
145
|
+
const session = getSession();
|
|
146
|
+
let messages;
|
|
147
|
+
try {
|
|
148
|
+
const res = await session.run(`MATCH (m:Message)-[:PART_OF]->(c:Conversation:WhatsAppConversation {conversationId: $conversationId})
|
|
149
|
+
WHERE m.accountId = $accountId
|
|
150
|
+
WITH m ORDER BY m.dateSent ASC, m.sequenceIndex ASC, m.messageId ASC
|
|
151
|
+
RETURN m.messageId AS messageId, m.senderName AS senderName, m.body AS body, m.dateSent AS dateSent`, { conversationId, accountId });
|
|
152
|
+
messages = res.records.map((r) => ({
|
|
153
|
+
messageId: r.get("messageId"),
|
|
154
|
+
senderName: r.get("senderName"),
|
|
155
|
+
body: r.get("body"),
|
|
156
|
+
dateSent: String(r.get("dateSent")),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await session.close();
|
|
161
|
+
}
|
|
162
|
+
if (messages.length === 0) {
|
|
163
|
+
process.stderr.write(`[whatsapp-export-insight-pass] empty-conversation conversationId=${conversationId}\n`);
|
|
164
|
+
return emptyResult(conversationId, Date.now() - startedMs);
|
|
165
|
+
}
|
|
166
|
+
// Build chunks with overlap. Step = chunkSize - overlap so consecutive
|
|
167
|
+
// chunks share the last `overlap` messages — preserves cross-message
|
|
168
|
+
// claims that straddle a chunk boundary.
|
|
169
|
+
const step = INSIGHT_CHUNK_SIZE - INSIGHT_CHUNK_OVERLAP;
|
|
170
|
+
const chunks = [];
|
|
171
|
+
for (let start = 0; start < messages.length; start += step) {
|
|
172
|
+
chunks.push(messages.slice(start, start + INSIGHT_CHUNK_SIZE));
|
|
173
|
+
if (start + INSIGHT_CHUNK_SIZE >= messages.length)
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
const totals = {
|
|
177
|
+
mentions: 0,
|
|
178
|
+
tasks: 0,
|
|
179
|
+
preferences: 0,
|
|
180
|
+
observedRelationships: 0,
|
|
181
|
+
rejectedLowConfidence: 0,
|
|
182
|
+
written: 0,
|
|
183
|
+
};
|
|
184
|
+
process.stderr.write(`[whatsapp-export-insight-pass] start chunkSize=${INSIGHT_CHUNK_SIZE} overlap=${INSIGHT_CHUNK_OVERLAP} confidenceThreshold=${CONFIDENCE_THRESHOLD} chunks=${chunks.length} conversationId=${conversationId}\n`);
|
|
185
|
+
for (let idx = 0; idx < chunks.length; idx++) {
|
|
186
|
+
const chunk = chunks[idx];
|
|
187
|
+
const transcript = chunk
|
|
188
|
+
.map((m, j) => `[${j + 1}] ${m.senderName}: ${m.body}`)
|
|
189
|
+
.join("\n");
|
|
190
|
+
let llmResult;
|
|
191
|
+
try {
|
|
192
|
+
llmResult = await callOauthLlm({
|
|
193
|
+
model: HAIKU_MODEL,
|
|
194
|
+
system: INSIGHT_SYSTEM_PROMPT,
|
|
195
|
+
userMessage: transcript,
|
|
196
|
+
maxTokens: 8192,
|
|
197
|
+
timeoutMs: 180_000,
|
|
198
|
+
tools: [INSIGHT_TOOL],
|
|
199
|
+
toolChoiceName: INSIGHT_TOOL.name,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
process.stderr.write(`[whatsapp-export-insight-pass] chunk=${idx + 1}/${chunks.length} threw="${err instanceof Error ? err.message : String(err)}"\n`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (llmResult.kind === "fallback") {
|
|
207
|
+
process.stderr.write(`[whatsapp-export-insight-pass] chunk=${idx + 1}/${chunks.length} fallback cause=${llmResult.cause} reason="${llmResult.reason}"\n`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (llmResult.kind !== "ok-tool") {
|
|
211
|
+
process.stderr.write(`[whatsapp-export-insight-pass] chunk=${idx + 1}/${chunks.length} unexpected-result kind=${llmResult.kind}\n`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const input = (llmResult.input ?? {});
|
|
215
|
+
let chunkMentions = 0;
|
|
216
|
+
let chunkTasks = 0;
|
|
217
|
+
let chunkPreferences = 0;
|
|
218
|
+
let chunkObserved = 0;
|
|
219
|
+
let chunkRejected = 0;
|
|
220
|
+
// sourceMessageRef is constant `chunk-pass` for chunked items: the
|
|
221
|
+
// chunked Haiku has no per-message provenance, AND the 5-message overlap
|
|
222
|
+
// window deliberately surfaces the same message in two chunks. If we
|
|
223
|
+
// keyed by `chunk:<idx>`, the same mention extracted from both halves
|
|
224
|
+
// would land as two rows (different MERGE key), defeating idempotence.
|
|
225
|
+
// A constant ref means identical `(kind, contentHash)` collapses to one
|
|
226
|
+
// row regardless of which chunk surfaced it; cross-run idempotence holds
|
|
227
|
+
// even when chunk indices shift after a Phase-1 delta re-import.
|
|
228
|
+
// Phase 2 enrichment recovers a per-message ref by snippet match later.
|
|
229
|
+
const sourceMessageRef = "chunk-pass";
|
|
230
|
+
const observations = [];
|
|
231
|
+
for (const m of asArray(input.mentions)) {
|
|
232
|
+
const conf = numericConfidence(m.confidence);
|
|
233
|
+
if (conf < CONFIDENCE_THRESHOLD) {
|
|
234
|
+
chunkRejected++;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const summary = strSlice(m.name, 200);
|
|
238
|
+
const snippet = strSlice(m.snippet, 200);
|
|
239
|
+
observations.push({
|
|
240
|
+
kind: "mention",
|
|
241
|
+
sourceMessageRef,
|
|
242
|
+
contentHash: contentHashFor("mention", { summary, snippet }),
|
|
243
|
+
summary,
|
|
244
|
+
snippet,
|
|
245
|
+
subject: null,
|
|
246
|
+
from: null,
|
|
247
|
+
to: null,
|
|
248
|
+
confidence: conf,
|
|
249
|
+
});
|
|
250
|
+
chunkMentions++;
|
|
251
|
+
}
|
|
252
|
+
for (const t of asArray(input.tasks)) {
|
|
253
|
+
const conf = numericConfidence(t.confidence);
|
|
254
|
+
if (conf < CONFIDENCE_THRESHOLD) {
|
|
255
|
+
chunkRejected++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const summary = strSlice(t.task, 200);
|
|
259
|
+
const snippet = strSlice(t.snippet, 200);
|
|
260
|
+
observations.push({
|
|
261
|
+
kind: "task",
|
|
262
|
+
sourceMessageRef,
|
|
263
|
+
contentHash: contentHashFor("task", { summary, snippet }),
|
|
264
|
+
summary,
|
|
265
|
+
snippet,
|
|
266
|
+
subject: null,
|
|
267
|
+
from: null,
|
|
268
|
+
to: null,
|
|
269
|
+
confidence: conf,
|
|
270
|
+
});
|
|
271
|
+
chunkTasks++;
|
|
272
|
+
}
|
|
273
|
+
for (const p of asArray(input.preferences)) {
|
|
274
|
+
const conf = numericConfidence(p.confidence);
|
|
275
|
+
if (conf < CONFIDENCE_THRESHOLD) {
|
|
276
|
+
chunkRejected++;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const summary = strSlice(p.preference, 200);
|
|
280
|
+
const subject = strSlice(p.subject, 200);
|
|
281
|
+
observations.push({
|
|
282
|
+
kind: "preference",
|
|
283
|
+
sourceMessageRef,
|
|
284
|
+
contentHash: contentHashFor("preference", { summary, subject }),
|
|
285
|
+
summary,
|
|
286
|
+
snippet: null,
|
|
287
|
+
subject,
|
|
288
|
+
from: null,
|
|
289
|
+
to: null,
|
|
290
|
+
confidence: conf,
|
|
291
|
+
});
|
|
292
|
+
chunkPreferences++;
|
|
293
|
+
}
|
|
294
|
+
for (const r of asArray(input.observedRelationships)) {
|
|
295
|
+
const conf = numericConfidence(r.confidence);
|
|
296
|
+
if (conf < CONFIDENCE_THRESHOLD) {
|
|
297
|
+
chunkRejected++;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const summary = strSlice(r.relationship, 200);
|
|
301
|
+
const from = strSlice(r.from, 200);
|
|
302
|
+
const to = strSlice(r.to, 200);
|
|
303
|
+
observations.push({
|
|
304
|
+
kind: "observed-relationship",
|
|
305
|
+
sourceMessageRef,
|
|
306
|
+
contentHash: contentHashFor("observed-relationship", { summary, from, to }),
|
|
307
|
+
summary,
|
|
308
|
+
snippet: null,
|
|
309
|
+
subject: null,
|
|
310
|
+
from,
|
|
311
|
+
to,
|
|
312
|
+
confidence: conf,
|
|
313
|
+
});
|
|
314
|
+
chunkObserved++;
|
|
315
|
+
}
|
|
316
|
+
totals.mentions += chunkMentions;
|
|
317
|
+
totals.tasks += chunkTasks;
|
|
318
|
+
totals.preferences += chunkPreferences;
|
|
319
|
+
totals.observedRelationships += chunkObserved;
|
|
320
|
+
totals.rejectedLowConfidence += chunkRejected;
|
|
321
|
+
process.stderr.write(`[whatsapp-export-insight-pass] chunk=${idx + 1}/${chunks.length} mentions=${chunkMentions} tasks=${chunkTasks} preferences=${chunkPreferences} observed=${chunkObserved} rejected-low-confidence=${chunkRejected}\n`);
|
|
322
|
+
if (observations.length === 0)
|
|
323
|
+
continue;
|
|
324
|
+
const writeSession = getSession();
|
|
325
|
+
try {
|
|
326
|
+
const writeRes = await writeSession.executeWrite(async (tx) => {
|
|
327
|
+
const res = await tx.run(INSIGHT_WRITE_CYPHER, {
|
|
328
|
+
conversationId,
|
|
329
|
+
accountId,
|
|
330
|
+
sessionId,
|
|
331
|
+
observations,
|
|
332
|
+
});
|
|
333
|
+
const stats = res.summary.counters.updates();
|
|
334
|
+
return stats.nodesCreated;
|
|
335
|
+
});
|
|
336
|
+
totals.written += writeRes;
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
process.stderr.write(`[whatsapp-export-insight-pass] chunk=${idx + 1}/${chunks.length} write-failed reason="${err instanceof Error ? err.message : String(err)}"\n`);
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
await writeSession.close().catch(() => { });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const elapsedMs = Date.now() - startedMs;
|
|
346
|
+
process.stderr.write(`[whatsapp-export-insight-pass] done conversationId=${conversationId} chunks=${chunks.length} mentions=${totals.mentions} tasks=${totals.tasks} preferences=${totals.preferences} observed=${totals.observedRelationships} rejected-low-confidence=${totals.rejectedLowConfidence} written=${totals.written} ms=${elapsedMs}\n`);
|
|
347
|
+
return {
|
|
348
|
+
conversationId,
|
|
349
|
+
chunks: chunks.length,
|
|
350
|
+
chunkSize: INSIGHT_CHUNK_SIZE,
|
|
351
|
+
overlap: INSIGHT_CHUNK_OVERLAP,
|
|
352
|
+
confidenceThreshold: CONFIDENCE_THRESHOLD,
|
|
353
|
+
totals,
|
|
354
|
+
ms: elapsedMs,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function emptyResult(conversationId, ms) {
|
|
358
|
+
return {
|
|
359
|
+
conversationId,
|
|
360
|
+
chunks: 0,
|
|
361
|
+
chunkSize: INSIGHT_CHUNK_SIZE,
|
|
362
|
+
overlap: INSIGHT_CHUNK_OVERLAP,
|
|
363
|
+
confidenceThreshold: CONFIDENCE_THRESHOLD,
|
|
364
|
+
totals: {
|
|
365
|
+
mentions: 0,
|
|
366
|
+
tasks: 0,
|
|
367
|
+
preferences: 0,
|
|
368
|
+
observedRelationships: 0,
|
|
369
|
+
rejectedLowConfidence: 0,
|
|
370
|
+
written: 0,
|
|
371
|
+
},
|
|
372
|
+
ms,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function asArray(v) {
|
|
376
|
+
return Array.isArray(v) ? v : [];
|
|
377
|
+
}
|
|
378
|
+
function numericConfidence(v) {
|
|
379
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
380
|
+
if (!Number.isFinite(n))
|
|
381
|
+
return 0;
|
|
382
|
+
if (n < 0)
|
|
383
|
+
return 0;
|
|
384
|
+
if (n > 1)
|
|
385
|
+
return 1;
|
|
386
|
+
return n;
|
|
387
|
+
}
|
|
388
|
+
function strSlice(v, max) {
|
|
389
|
+
return String(v ?? "").slice(0, max);
|
|
390
|
+
}
|
|
391
|
+
function contentHashFor(kind, fields) {
|
|
392
|
+
// Stable normalisation: NFKC + trim + lowercase per Task 870's idempotence
|
|
393
|
+
// contract. The hash is the natural-key tail; identical (kind, fields)
|
|
394
|
+
// collapse to one MERGE row across re-runs.
|
|
395
|
+
const ordered = Object.keys(fields).sort();
|
|
396
|
+
const norm = ordered
|
|
397
|
+
.map((k) => `${k}=${(fields[k] ?? "").normalize("NFKC").trim().toLowerCase()}`)
|
|
398
|
+
.join("|");
|
|
399
|
+
return createHash("sha256").update(`${kind}|${norm}`).digest("hex");
|
|
400
|
+
}
|
|
401
|
+
//# sourceMappingURL=whatsapp-export-insight-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-export-insight-pass.js","sourceRoot":"","sources":["../../src/tools/whatsapp-export-insight-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface WhatsappExportPreviewParams {
|
|
2
|
+
filePath: string;
|
|
3
|
+
accountId: string;
|
|
4
|
+
timezone: string;
|
|
5
|
+
/** Same enum as whatsapp-export-parse — propagated through to the parser. */
|
|
6
|
+
dateFormat?: "DD/MM/YY" | "MM/DD/YY" | "DD/MM/YYYY" | "MM/DD/YYYY";
|
|
7
|
+
}
|
|
8
|
+
export interface WhatsappExportPreviewSender {
|
|
9
|
+
name: string;
|
|
10
|
+
messageCount: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WhatsappExportPreviewResult {
|
|
13
|
+
/** sha256 hex of the raw file bytes — same value as `archiveSourceFile` minus the `whatsapp-export:` prefix. */
|
|
14
|
+
conversationSha256: string;
|
|
15
|
+
archiveSourceFile: string;
|
|
16
|
+
archiveBytes: number;
|
|
17
|
+
parsed: number;
|
|
18
|
+
mediaSkipped: number;
|
|
19
|
+
systemSkipped: number;
|
|
20
|
+
totalMessages: number;
|
|
21
|
+
dateRange: {
|
|
22
|
+
first: string | null;
|
|
23
|
+
last: string | null;
|
|
24
|
+
};
|
|
25
|
+
senders: WhatsappExportPreviewSender[];
|
|
26
|
+
}
|
|
27
|
+
export declare function whatsappExportPreview(params: WhatsappExportPreviewParams): Promise<WhatsappExportPreviewResult>;
|
|
28
|
+
//# sourceMappingURL=whatsapp-export-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-export-preview.d.ts","sourceRoot":"","sources":["../../src/tools/whatsapp-export-preview.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC;CACpE;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gHAAgH;IAChH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE;QACT,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,2BAA2B,EAAE,CAAC;CACxC;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,2BAA2B,CAAC,CAuCtC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { parseExport } from "../../../../whatsapp-import/lib/dist/parse-export.js";
|
|
3
|
+
export async function whatsappExportPreview(params) {
|
|
4
|
+
const { filePath, accountId, timezone, dateFormat } = params;
|
|
5
|
+
const archiveBytes = statSync(filePath).size;
|
|
6
|
+
const input = { filePath, accountId, timezone, dateFormat };
|
|
7
|
+
const result = parseExport(input);
|
|
8
|
+
// Build sender histogram in encounter order — operator scans top-down.
|
|
9
|
+
const counts = new Map();
|
|
10
|
+
for (const line of result.parsedLines) {
|
|
11
|
+
counts.set(line.senderName, (counts.get(line.senderName) ?? 0) + 1);
|
|
12
|
+
}
|
|
13
|
+
const senders = Array.from(counts.entries())
|
|
14
|
+
.map(([name, messageCount]) => ({ name, messageCount }))
|
|
15
|
+
.sort((a, b) => b.messageCount - a.messageCount);
|
|
16
|
+
const first = result.parsedLines[0]?.dateSent ?? null;
|
|
17
|
+
const last = result.parsedLines[result.parsedLines.length - 1]?.dateSent ?? null;
|
|
18
|
+
// archiveSourceFile is `whatsapp-export:<sha>` — strip prefix for the
|
|
19
|
+
// shorter scalar the operator surfaces.
|
|
20
|
+
const conversationSha256 = result.archiveSourceFile.replace(/^whatsapp-export:/, "");
|
|
21
|
+
process.stderr.write(`[whatsapp-ingest] preview parsed=${result.counters.parsed} senders=${senders.length} first=${first ?? "-"} last=${last ?? "-"}\n`);
|
|
22
|
+
return {
|
|
23
|
+
conversationSha256,
|
|
24
|
+
archiveSourceFile: result.archiveSourceFile,
|
|
25
|
+
archiveBytes,
|
|
26
|
+
parsed: result.counters.parsed,
|
|
27
|
+
mediaSkipped: result.counters.mediaSkipped,
|
|
28
|
+
systemSkipped: result.counters.systemSkipped,
|
|
29
|
+
totalMessages: result.parsedLines.length,
|
|
30
|
+
dateRange: { first, last },
|
|
31
|
+
senders,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=whatsapp-export-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-export-preview.js","sourceRoot":"","sources":["../../src/tools/whatsapp-export-preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,sDAAsD,CAAC;AAqDnF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAmC;IAEnC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAE7D,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAE9E,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAElC,uEAAuE;IACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,OAAO,GAAkC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;SACxE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;SACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;IAEjF,sEAAsE;IACtE,wCAAwC;IACxC,MAAM,kBAAkB,GAAG,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAErF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oCAAoC,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY,OAAO,CAAC,MAAM,UAAU,KAAK,IAAI,GAAG,SAAS,IAAI,IAAI,GAAG,IAAI,CACnI,CAAC;IAEF,OAAO;QACL,kBAAkB;QAClB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,YAAY;QACZ,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC9B,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY;QAC1C,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,aAAa;QAC5C,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM;QACxC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;QAC1B,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -34,6 +34,8 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
34
34
|
| DefinedTerm | `DefinedTerm` | `schema:DefinedTerm` | — | `accountId`, `name`, `description` |
|
|
35
35
|
| AccessGrant | `AccessGrant` | platform-native | — | `accountId`, `agentSlug`, `contactMethod`, `contactValue`, `status` (scope must be `admin`) |
|
|
36
36
|
| Position | `Position` | platform-native (analogue of `schema:OrganizationRole`) | — | `accountId`, `title`, `startDate` |
|
|
37
|
+
| WhatsApp Conversation | `WhatsAppConversation` | extends `schema:Conversation` | — | `accountId`, `conversationId`, `archiveSourceFile`, `firstMessageAt`, `lastMessageAt`, `participantCount`, `messageCount`, `scope`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
38
|
+
| WhatsApp Message | `WhatsAppMessage` | extends `schema:Message` | — | `accountId`, `conversationId`, `messageId`, `dateSent`, `body`, `senderName`, `sequenceIndex`, `scope`, `createdByAgent`, `createdBySession`, `createdAt` |
|
|
37
39
|
|
|
38
40
|
**Branding properties on LocalBusiness:** `primaryColor`, `accentColor`, `backgroundColor`, `tagline` — optional, used to brand the public chat endpoint. Written via `memory-update` on the LocalBusiness node. Hex color values must match `#[0-9a-fA-F]{3,8}`. Logo and icon are linked via `HAS_BRAND_ASSET → ImageObject` with `purpose: "logo"` or `"icon"`.
|
|
39
41
|
|
|
@@ -117,8 +119,18 @@ Do not write placeholder values ("TBD", "unknown", empty strings) for properties
|
|
|
117
119
|
(:UserProfile)-[:HAS_SKILL]->(:DefinedTerm {category:'skill'})
|
|
118
120
|
(:KnowledgeDocument)-[:REFERENCES]->(:*)
|
|
119
121
|
(:KnowledgeDocument)-[:HAS_SECTION]->(:Section)
|
|
122
|
+
(:Message)-[:PART_OF]->(:Conversation)
|
|
123
|
+
(:Person|:AdminUser)-[:SENT]->(:Message)
|
|
124
|
+
(:Person|:AdminUser)-[:PARTICIPANT_IN]->(:Conversation)
|
|
125
|
+
(:Message)-[:NEXT]->(:Message)
|
|
120
126
|
```
|
|
121
127
|
|
|
128
|
+
### WhatsApp ingest natural-key contract (Task 870)
|
|
129
|
+
|
|
130
|
+
Live nodes carry compound labels: `:Conversation:WhatsAppConversation` and `:Message:WhatsAppMessage`. The Node Types table registers `WhatsAppConversation` and `WhatsAppMessage` individually — the schema validator matches on the more-specific sublabel. Base `Conversation` and `Message` rules (e.g. `dateSent` on Message) still apply via the base label.
|
|
131
|
+
|
|
132
|
+
`:Message:WhatsAppMessage` natural key is `messageId = whatsapp-export:msg:<conversationSha256>:<dateSentISO>:<NFKC-trim-lower(senderName)>:<sha256-hex(body)>`. Re-imports of the same archive — same operator-confirmed timezone — collapse to the same Message identity. The chain `(:Message)-[:NEXT]->(:Message)` is rebuilt deterministically by ordering on `(dateSent, sequenceIndex, messageId)`; `sequenceIndex` is preserved on the node only as a tiebreaker, never as part of `messageId`.
|
|
133
|
+
|
|
122
134
|
### Document-ingestion section kinds (Task 740, replacing Task 737's typed-vs-UNMAPPED fork)
|
|
123
135
|
|
|
124
136
|
The `document-ingest` skill maps unstructured-document sections onto a closed enumeration of `kind` values. Every section becomes one `:Section` node; recognised kinds carry a secondary label so the same node serves as both the document section and the typed entity. Identity-kind anchor edges point from the document subject directly at the multi-labeled section node — there is no parallel "Section vs typed-node" concept.
|
|
@@ -45,7 +45,9 @@ Three tools for reading WhatsApp conversation context. Messages come from the in
|
|
|
45
45
|
|
|
46
46
|
## Live persistence (Task 857)
|
|
47
47
|
|
|
48
|
-
Every `messages.upsert` event (both `notify` and `append`, both `fromMe` directions) writes a `:Message:WhatsAppMessage` row to Neo4j attached to the sessionKey-keyed `:Conversation`. A single capture site at `platform/ui/app/lib/whatsapp/manager.ts` covers inbound, outbound (Baileys echoes agent-sent messages back through `messages.upsert` with `fromMe=true`), and owner-mirror — without touching `outbound/send.ts`. `messageId` namespace is `whatsapp-live:<
|
|
48
|
+
Every `messages.upsert` event (both `notify` and `append`, both `fromMe` directions) writes a `:Message:WhatsAppMessage` row to Neo4j attached to the sessionKey-keyed `:Conversation`. A single capture site at `platform/ui/app/lib/whatsapp/manager.ts` covers inbound, outbound (Baileys echoes agent-sent messages back through `messages.upsert` with `fromMe=true`), and owner-mirror — without touching `outbound/send.ts`. `messageId` namespace is `whatsapp-live:<waName>:<remoteJid>:<msg.key.id>` where `<waName>` is the Baileys credential dirname (e.g. `default`); collision-free with the `whatsapp-export:` namespace used by the offline `whatsapp-import` plugin. Persist failures are loud (`[whatsapp-persist] FAIL …`) and never block dispatch — silent loss is the worse failure mode.
|
|
49
|
+
|
|
50
|
+
**`accountId` contract (Task 872).** `n.accountId` on every `:Conversation`, `:Person`, and `:Message:WhatsAppMessage` row stamped by this plugin is the **platform-side UUID** resolved by [`resolvePlatformAccountId()`](../../ui/app/lib/whatsapp/platform-account-id.ts) from `data/accounts/<uuid>/account.json` — NOT the Baileys credential dirname (which is only used as the `messageId`/`sessionKey` namespace token). The boot-time line `[whatsapp-persist] resolved-account-id waname=<dir> uuid=<uuid>` records the resolution. Doctrine: see `.docs/neo4j.md` "Account isolation invariant" — migration 004 `pruneAlienAccounts` `DETACH DELETE`s any node whose `accountId` is not a UUID dir on every boot. The helper loud-throws on zero or multi accounts (Phase 0 single-account invariant), aborting the WhatsApp connection start before any write can occur.
|
|
49
51
|
|
|
50
52
|
## Skills
|
|
51
53
|
|