@rubytech/create-realagent 1.0.794 → 1.0.797

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 (70) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/migrations/003-person-name-eradicate.cypher +24 -0
  3. package/payload/platform/plugins/admin/PLUGIN.md +4 -0
  4. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-gate.test.sh +166 -0
  5. package/payload/platform/plugins/admin/hooks/archive-ingest-gate.sh +147 -0
  6. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +9 -5
  7. package/payload/platform/plugins/admin/mcp/dist/index.js +54 -18
  8. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  9. package/payload/platform/plugins/docs/references/internals.md +4 -0
  10. package/payload/platform/plugins/docs/references/settings.md +6 -0
  11. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -0
  12. package/payload/platform/plugins/memory/PLUGIN.md +4 -2
  13. package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
  14. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
  17. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +124 -1
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +12 -0
  20. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
  21. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +41 -2
  22. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
  23. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +9 -0
  24. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +44 -0
  26. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +20 -2
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
  29. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  31. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +0 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  33. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +2 -1
  34. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +1 -1
  35. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +1 -1
  36. package/payload/platform/plugins/memory/references/schema-base.md +11 -1
  37. package/payload/platform/plugins/whatsapp-import/PLUGIN.md +4 -0
  38. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts +8 -2
  39. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts.map +1 -1
  40. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js +66 -15
  41. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js.map +1 -1
  42. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +175 -0
  43. package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +78 -17
  44. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +2 -0
  45. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +8 -6
  46. package/payload/platform/scripts/logs-read.sh +17 -12
  47. package/payload/platform/scripts/seed-neo4j.sh +43 -20
  48. package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
  49. package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
  50. package/payload/server/chunk-BURNRCKP.js +3405 -0
  51. package/payload/server/chunk-JSBRDJBE.js +30 -0
  52. package/payload/server/chunk-KM23Y7SY.js +9896 -0
  53. package/payload/server/client-pool-PV45NUTN.js +29 -0
  54. package/payload/server/maxy-edge.js +3 -2
  55. package/payload/server/neo4j-migrations-IUSBODOP.js +51 -0
  56. package/payload/server/public/assets/{admin-jGbRjAxV.js → admin-Cz8hUAqx.js} +60 -60
  57. package/payload/server/public/assets/data-BvV94XHO.js +1 -0
  58. package/payload/server/public/assets/graph-CBu0rtrP.js +1 -0
  59. package/payload/server/public/assets/page-BNM63zsb.js +50 -0
  60. package/payload/server/public/assets/page-DM19J3ur.js +1 -0
  61. package/payload/server/public/assets/useAdminFetch-iYCQ9lT0.js +1 -0
  62. package/payload/server/public/data.html +3 -3
  63. package/payload/server/public/graph.html +3 -3
  64. package/payload/server/public/index.html +4 -4
  65. package/payload/server/server.js +116 -137
  66. package/payload/server/public/assets/data-BhrQjgR5.js +0 -1
  67. package/payload/server/public/assets/graph-Jj7seS-w.js +0 -1
  68. package/payload/server/public/assets/page-DIG7s5Jp.js +0 -1
  69. package/payload/server/public/assets/page-sZb3wcOM.js +0 -50
  70. package/payload/server/public/assets/share-2-BndjMKeG.js +0 -1
@@ -3,7 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseExport = parseExport;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const node_fs_1 = require("node:fs");
6
- const TIMESTAMP_PREFIX_DDMMYY = /^\[(\d{2})\/(\d{2})\/(\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
6
+ // Year capture is `\d{2}|\d{4}` so a single regex covers both 2-digit (legacy)
7
+ // and 4-digit (modern WhatsApp default) prefixes — Task 845. Exactly 2 or 4
8
+ // chars; 3-digit years (truncation typos, hand-edited files) are rejected as
9
+ // not-a-prefix and surface via parse-grammar-miss, not silently coerced into
10
+ // year-202-AD timestamps. Year semantics are resolved per-match in
11
+ // `matchTimestampPrefix` from the captured length, not from the regex shape,
12
+ // so mixed-year files parse natively.
13
+ const TIMESTAMP_PREFIX_DDMMYY = /^\[(\d{2})\/(\d{2})\/(\d{4}|\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
7
14
  const TIMESTAMP_PREFIX_MMDDYY = TIMESTAMP_PREFIX_DDMMYY; // shape is identical; ordering differs in interpretation only
8
15
  // System-message patterns that appear WITHOUT a `: ` sender/body separator.
9
16
  // WhatsApp emits group-event and security-code lines as `<Sender> <verb> ...`
@@ -40,7 +47,7 @@ const MEDIA_ONLY_PATTERNS = [
40
47
  /^‎.+attached:\s*.+$/, // alternative LRM-prefixed format on some platforms
41
48
  ];
42
49
  function parseExport(input) {
43
- const { filePath, accountId, timezone, dateFormat = "DD/MM/YY" } = input;
50
+ const { filePath, accountId, timezone, dateFormat: explicitDateFormat } = input;
44
51
  if (!accountId || !accountId.trim()) {
45
52
  throw new Error("parse-export: accountId is required.");
46
53
  }
@@ -56,6 +63,12 @@ function parseExport(input) {
56
63
  throw new Error(`parse-export: file is empty — not a _chat.txt. file=${filePath}`);
57
64
  }
58
65
  const lines = text.split("\n");
66
+ // Auto-detect when `dateFormat` is omitted (Task 845): probe the first line
67
+ // that contains a timestamp prefix as DD/MM; lock DD/MM if range-valid,
68
+ // otherwise lock MM/DD. WhatsApp's locale is set per device, so a single
69
+ // file never mixes DD/MM and MM/DD — locking once from line 1 is correct.
70
+ // Concatenated multi-locale exports require an explicit `dateFormat`.
71
+ const ordering = resolveOrdering(explicitDateFormat, lines);
59
72
  const counters = {
60
73
  parsed: 0,
61
74
  systemSkipped: 0,
@@ -67,7 +80,7 @@ function parseExport(input) {
67
80
  const line = lines[i];
68
81
  if (line.length === 0 && i === lines.length - 1)
69
82
  continue; // trailing newline
70
- const prefixMatch = matchTimestampPrefix(line, dateFormat);
83
+ const prefixMatch = matchTimestampPrefix(line, ordering);
71
84
  if (prefixMatch) {
72
85
  raw.push({
73
86
  rawLineIndex: i + 1,
@@ -128,7 +141,13 @@ function parseExport(input) {
128
141
  counters.parsed++;
129
142
  }
130
143
  if (parsedLines.length === 0 && counters.systemSkipped === 0 && counters.mediaSkipped === 0) {
131
- throw new Error(`parse-export: zero parsed lines after walking ${filePath} not a _chat.txt or all lines failed grammar.`);
144
+ // Task 845: include a sanitised first-line sample so the operator knows
145
+ // WHY the file rejected — closes the diagnostic gap that left conversation
146
+ // 47c6a590-0c2c-4006-9aca-6ee9ec93c95f guessing. Echoed to stderr too so
147
+ // server.log has a grep-able adjunct to the existing parse-failed line.
148
+ const sample = sampleFirstNonBlankLine(lines, 100);
149
+ process.stderr.write(`[whatsapp-import] parse-grammar-miss first-line="${sample}"\n`);
150
+ throw new Error(`parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar. parse-grammar-miss first-line="${sample}"`);
132
151
  }
133
152
  return {
134
153
  conversationId,
@@ -160,37 +179,69 @@ function decodeAndNormalise(bytes) {
160
179
  text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
161
180
  return text;
162
181
  }
163
- function matchTimestampPrefix(line, dateFormat) {
164
- const re = dateFormat === "MM/DD/YY" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
182
+ function matchTimestampPrefix(line, ordering) {
183
+ const re = ordering === "MMDD" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
165
184
  const m = line.match(re);
166
185
  if (!m)
167
186
  return null;
168
- const a = parseInt(m[1], 10); // dd or mm depending on dateFormat
187
+ const a = parseInt(m[1], 10); // dd or mm depending on ordering
169
188
  const b = parseInt(m[2], 10); // mm or dd
170
- const yy = parseInt(m[3], 10);
189
+ const yearRaw = m[3];
171
190
  const hour = parseInt(m[4], 10);
172
191
  const minute = parseInt(m[5], 10);
173
192
  const second = m[6] !== undefined ? parseInt(m[6], 10) : 0;
174
193
  const remainder = m[7] ?? "";
175
- const day = dateFormat === "MM/DD/YY" ? b : a;
176
- const month = dateFormat === "MM/DD/YY" ? a : b;
194
+ const day = ordering === "MMDD" ? b : a;
195
+ const month = ordering === "MMDD" ? a : b;
177
196
  // Range-check before passing to Date.UTC — that function silently rolls
178
197
  // over invalid components (Date.UTC(2026, 13, 1) → 2027-02-01), which
179
- // would corrupt timestamps when the operator passes the wrong dateFormat
198
+ // would corrupt timestamps when the operator passes the wrong ordering
180
199
  // for a US-locale export. Reject as not-a-prefix; the caller retries the
181
- // file with the correct format or LOUD-FAILs when the file isn't a chat.
200
+ // file with the correct ordering or LOUD-FAILs when the file isn't a chat.
182
201
  if (month < 1 || month > 12 || day < 1 || day > 31)
183
202
  return null;
184
203
  if (hour > 23 || minute > 59 || second > 59)
185
204
  return null;
186
- // WhatsApp's two-digit year is unambiguous in the 21st century; explicit
187
- // shift here documents the assumption rather than relying on locale.
188
- const year = 2000 + yy;
205
+ // Task 845: branch year semantics on captured length. WhatsApp's modern
206
+ // exports emit 4-digit years; legacy exports emit 2-digit. Both are
207
+ // accepted by the same regex and disambiguated here so a single file may
208
+ // hold both shapes (mixed-year imports parse natively).
209
+ const year = yearRaw.length === 2 ? 2000 + parseInt(yearRaw, 10) : parseInt(yearRaw, 10);
189
210
  return {
190
211
  dateParts: { year, month, day, hour, minute, second },
191
212
  remainder,
192
213
  };
193
214
  }
215
+ function resolveOrdering(explicit, lines) {
216
+ if (explicit === "MM/DD/YY" || explicit === "MM/DD/YYYY")
217
+ return "MMDD";
218
+ if (explicit === "DD/MM/YY" || explicit === "DD/MM/YYYY")
219
+ return "DDMM";
220
+ // Auto-detect: probe the first prefix-matching line as DD/MM. If range-valid,
221
+ // lock DD/MM (WhatsApp's global default). Otherwise lock MM/DD (US-locale
222
+ // exports, which are the only meaningful exception). Locked once.
223
+ for (const line of lines) {
224
+ if (matchTimestampPrefix(line, "DDMM"))
225
+ return "DDMM";
226
+ if (matchTimestampPrefix(line, "MMDD"))
227
+ return "MMDD";
228
+ }
229
+ return "DDMM"; // No matching line — caller will throw zero-parsed-lines anyway.
230
+ }
231
+ function sampleFirstNonBlankLine(lines, maxScan) {
232
+ const scanLimit = Math.min(maxScan, lines.length);
233
+ for (let i = 0; i < scanLimit; i++) {
234
+ const trimmed = lines[i].trim();
235
+ if (trimmed.length === 0)
236
+ continue;
237
+ // Strip control characters (including tab, BEL, etc.) so the diagnostic
238
+ // line stays single-line and grep-friendly. Truncate to 80 chars per
239
+ // Task 845 brief — enough to recognise the offending header shape.
240
+ const sanitised = trimmed.replace(/[\x00-\x1F\x7F]/g, "");
241
+ return sanitised.slice(0, 80);
242
+ }
243
+ return "";
244
+ }
194
245
  function findFirstColonSeparator(remainder) {
195
246
  // Split on the FIRST `: ` (colon-space). A sender display name may itself
196
247
  // contain a `:` (e.g. "Joel: Work"), so we anchor on the first colon
@@ -1 +1 @@
1
- {"version":3,"file":"parse-export.js","sourceRoot":"","sources":["../src/parse-export.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parse-export.js","sourceRoot":"","sources":["../src/parse-export.ts"],"names":[],"mappings":"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"}
@@ -386,6 +386,181 @@ describe("parseExport — date format toggle", () => {
386
386
  });
387
387
  });
388
388
 
389
+ describe("parseExport — 4-digit year grammar (Task 845)", () => {
390
+ it("parses DD/MM/YYYY with explicit 4-digit dateFormat", () => {
391
+ // Joel→Adam Mackay export shape: WhatsApp's modern locale emits 4-digit years.
392
+ const filePath = writeChat(
393
+ "_chat.txt",
394
+ "[20/06/2025, 16:35:12] Joel Smalley: Hello\n",
395
+ );
396
+ const result = parseExport({
397
+ filePath,
398
+ accountId: "acct-123",
399
+ timezone: "Europe/London",
400
+ dateFormat: "DD/MM/YYYY",
401
+ });
402
+ expect(result.parsedLines).toHaveLength(1);
403
+ expect(result.parsedLines[0].senderName).toBe("Joel Smalley");
404
+ expect(result.parsedLines[0].body).toBe("Hello");
405
+ expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
406
+ });
407
+
408
+ it("parses MM/DD/YYYY with explicit 4-digit dateFormat", () => {
409
+ const filePath = writeChat(
410
+ "_chat.txt",
411
+ "[06/20/2025, 16:35:12] Joel: Hello\n",
412
+ );
413
+ const result = parseExport({
414
+ filePath,
415
+ accountId: "acct-123",
416
+ timezone: "America/New_York",
417
+ dateFormat: "MM/DD/YYYY",
418
+ });
419
+ expect(result.parsedLines).toHaveLength(1);
420
+ expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
421
+ });
422
+
423
+ it("auto-detects DD/MM/YYYY when dateFormat omitted (4-digit year, default WhatsApp locale)", () => {
424
+ // No explicit dateFormat; first matched line `[20/06/2025, ...]` is range-valid
425
+ // as DD/MM (day=20, month=6) → DD/MM lock.
426
+ const filePath = writeChat(
427
+ "_chat.txt",
428
+ "[20/06/2025, 16:35:12] Joel: Hello\n",
429
+ );
430
+ const result = parseExport({
431
+ filePath,
432
+ accountId: "acct-123",
433
+ timezone: "Europe/London",
434
+ });
435
+ expect(result.parsedLines).toHaveLength(1);
436
+ expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T/);
437
+ });
438
+
439
+ it("auto-detects MM/DD/YYYY when first line range-fails as DD/MM (US-locale export)", () => {
440
+ // `[06/20/2025, ...]` interpreted DD/MM → day=6, month=20 → range-fail.
441
+ // Auto-detect probes DD/MM first; falls back to MM/DD lock.
442
+ const filePath = writeChat(
443
+ "_chat.txt",
444
+ "[06/20/2025, 16:35:12] Joel: Hello\n",
445
+ );
446
+ const result = parseExport({
447
+ filePath,
448
+ accountId: "acct-123",
449
+ timezone: "America/New_York",
450
+ });
451
+ expect(result.parsedLines).toHaveLength(1);
452
+ expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T/);
453
+ });
454
+
455
+ it("accepts mixed 2-digit and 4-digit years in the same file (regex \\d{2,4})", () => {
456
+ const filePath = writeChat(
457
+ "_chat.txt",
458
+ [
459
+ "[20/06/25, 16:35:12] Joel: Two-digit year",
460
+ "[21/06/2025, 09:00:00] Joel: Four-digit year",
461
+ "",
462
+ ].join("\n"),
463
+ );
464
+ const result = parseExport({
465
+ filePath,
466
+ accountId: "acct-123",
467
+ timezone: "Europe/London",
468
+ });
469
+ expect(result.parsedLines).toHaveLength(2);
470
+ expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
471
+ expect(result.parsedLines[1].dateSent).toMatch(/^2025-06-21T09:00:00/);
472
+ });
473
+
474
+ it("2-digit year still maps to 2000+yy after grammar widening (regression)", () => {
475
+ const filePath = writeChat(
476
+ "_chat.txt",
477
+ "[14/03/26, 10:15:23] Joel: Hello\n",
478
+ );
479
+ const result = parseExport({
480
+ filePath,
481
+ accountId: "acct-123",
482
+ timezone: "Europe/London",
483
+ });
484
+ expect(result.parsedLines[0].dateSent).toMatch(/^2026-03-14T/);
485
+ });
486
+
487
+ it("4-digit year passes through unchanged (no 2000+ shift)", () => {
488
+ const filePath = writeChat(
489
+ "_chat.txt",
490
+ "[14/03/2099, 10:15:23] Joel: Hello\n",
491
+ );
492
+ const result = parseExport({
493
+ filePath,
494
+ accountId: "acct-123",
495
+ timezone: "Europe/London",
496
+ });
497
+ expect(result.parsedLines[0].dateSent).toMatch(/^2099-03-14T/);
498
+ });
499
+
500
+ it("rejects 3-digit years (truncation typos, hand-edited files) — silent year-202-AD coercion is the failure mode this guards against", () => {
501
+ // Without the explicit (\d{4}|\d{2}) alternation, the greedy `\d{2,4}`
502
+ // would match "202" and the year-length branch would emit year 202 AD
503
+ // timestamps that pass downstream validation but are clearly wrong.
504
+ // Reject as not-a-prefix; the file falls into the parse-grammar-miss
505
+ // diagnostic path so the operator sees the offending header shape.
506
+ const filePath = writeChat(
507
+ "_chat.txt",
508
+ "[14/03/202, 10:15:23] Joel: Hello\n",
509
+ );
510
+ expect(() =>
511
+ parseExport({
512
+ filePath,
513
+ accountId: "acct-123",
514
+ timezone: "Europe/London",
515
+ }),
516
+ ).toThrow(/parse-grammar-miss first-line="\[14\/03\/202, 10:15:23\] Joel: Hello"/);
517
+ });
518
+ });
519
+
520
+ describe("parseExport — parse-grammar-miss diagnostic (Task 845)", () => {
521
+ it("includes a sanitised first-line sample in the thrown error when no prefix matches", () => {
522
+ // Junk file with no timestamp prefixes → zero-parsed-lines path.
523
+ // The thrown error must include the first non-blank line so the operator
524
+ // knows WHY the file rejected, instead of guessing as in conversation
525
+ // 47c6a590-0c2c-4006-9aca-6ee9ec93c95f.
526
+ const filePath = writeChat(
527
+ "_chat.txt",
528
+ "this is some random text\nwith no timestamp prefixes\nat all.\n",
529
+ );
530
+ expect(() =>
531
+ parseExport({
532
+ filePath,
533
+ accountId: "acct-123",
534
+ timezone: "Europe/London",
535
+ }),
536
+ ).toThrow(/parse-grammar-miss first-line="this is some random text"/);
537
+ });
538
+
539
+ it("truncates the first-line sample to 80 characters", () => {
540
+ const longLine = "x".repeat(200);
541
+ const filePath = writeChat("_chat.txt", longLine + "\n");
542
+ expect(() =>
543
+ parseExport({
544
+ filePath,
545
+ accountId: "acct-123",
546
+ timezone: "Europe/London",
547
+ }),
548
+ ).toThrow(/parse-grammar-miss first-line="x{80}"/);
549
+ });
550
+
551
+ it("strips control characters from the first-line sample", () => {
552
+ // Tab, bell, and other control chars should not leak into the diagnostic.
553
+ const filePath = writeChat("_chat.txt", "junk\twith\x07control\x01chars\n");
554
+ expect(() =>
555
+ parseExport({
556
+ filePath,
557
+ accountId: "acct-123",
558
+ timezone: "Europe/London",
559
+ }),
560
+ ).toThrow(/parse-grammar-miss first-line="junkwithcontrolchars"/);
561
+ });
562
+ });
563
+
389
564
  describe("parseExport — LOUD-FAIL scenarios", () => {
390
565
  it("throws when the file is empty", () => {
391
566
  const filePath = writeChat("_chat.txt", "");
@@ -30,8 +30,14 @@ export interface ParseExportInput {
30
30
  accountId: string;
31
31
  /** IANA timezone the operator confirmed (e.g. `Europe/London`). */
32
32
  timezone: string;
33
- /** Defaults to `DD/MM/YY`; operator confirms when locale is ambiguous. */
34
- dateFormat?: "DD/MM/YY" | "MM/DD/YY";
33
+ /**
34
+ * Date ordering and year shape. Omit for auto-detect (Task 845): the parser
35
+ * probes the first matched line as DD/MM and locks that ordering if range-valid;
36
+ * otherwise locks MM/DD. Year shape is independent — `\d{2,4}` accepts 2-digit
37
+ * (mapped `2000+yy`) and 4-digit (passed through) years per-line, including
38
+ * mixed-year files.
39
+ */
40
+ dateFormat?: "DD/MM/YY" | "MM/DD/YY" | "DD/MM/YYYY" | "MM/DD/YYYY";
35
41
  }
36
42
 
37
43
  export interface ParsedLine {
@@ -58,8 +64,15 @@ export interface ParseExportResult {
58
64
  counters: ParseExportCounters;
59
65
  }
60
66
 
67
+ // Year capture is `\d{2}|\d{4}` so a single regex covers both 2-digit (legacy)
68
+ // and 4-digit (modern WhatsApp default) prefixes — Task 845. Exactly 2 or 4
69
+ // chars; 3-digit years (truncation typos, hand-edited files) are rejected as
70
+ // not-a-prefix and surface via parse-grammar-miss, not silently coerced into
71
+ // year-202-AD timestamps. Year semantics are resolved per-match in
72
+ // `matchTimestampPrefix` from the captured length, not from the regex shape,
73
+ // so mixed-year files parse natively.
61
74
  const TIMESTAMP_PREFIX_DDMMYY =
62
- /^\[(\d{2})\/(\d{2})\/(\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
75
+ /^\[(\d{2})\/(\d{2})\/(\d{4}|\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
63
76
 
64
77
  const TIMESTAMP_PREFIX_MMDDYY = TIMESTAMP_PREFIX_DDMMYY; // shape is identical; ordering differs in interpretation only
65
78
 
@@ -101,7 +114,7 @@ const MEDIA_ONLY_PATTERNS: RegExp[] = [
101
114
  ];
102
115
 
103
116
  export function parseExport(input: ParseExportInput): ParseExportResult {
104
- const { filePath, accountId, timezone, dateFormat = "DD/MM/YY" } = input;
117
+ const { filePath, accountId, timezone, dateFormat: explicitDateFormat } = input;
105
118
 
106
119
  if (!accountId || !accountId.trim()) {
107
120
  throw new Error("parse-export: accountId is required.");
@@ -123,6 +136,12 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
123
136
  }
124
137
 
125
138
  const lines = text.split("\n");
139
+ // Auto-detect when `dateFormat` is omitted (Task 845): probe the first line
140
+ // that contains a timestamp prefix as DD/MM; lock DD/MM if range-valid,
141
+ // otherwise lock MM/DD. WhatsApp's locale is set per device, so a single
142
+ // file never mixes DD/MM and MM/DD — locking once from line 1 is correct.
143
+ // Concatenated multi-locale exports require an explicit `dateFormat`.
144
+ const ordering = resolveOrdering(explicitDateFormat, lines);
126
145
  const counters: ParseExportCounters = {
127
146
  parsed: 0,
128
147
  systemSkipped: 0,
@@ -149,7 +168,7 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
149
168
  for (let i = 0; i < lines.length; i++) {
150
169
  const line = lines[i];
151
170
  if (line.length === 0 && i === lines.length - 1) continue; // trailing newline
152
- const prefixMatch = matchTimestampPrefix(line, dateFormat);
171
+ const prefixMatch = matchTimestampPrefix(line, ordering);
153
172
  if (prefixMatch) {
154
173
  raw.push({
155
174
  rawLineIndex: i + 1,
@@ -226,8 +245,16 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
226
245
  }
227
246
 
228
247
  if (parsedLines.length === 0 && counters.systemSkipped === 0 && counters.mediaSkipped === 0) {
248
+ // Task 845: include a sanitised first-line sample so the operator knows
249
+ // WHY the file rejected — closes the diagnostic gap that left conversation
250
+ // 47c6a590-0c2c-4006-9aca-6ee9ec93c95f guessing. Echoed to stderr too so
251
+ // server.log has a grep-able adjunct to the existing parse-failed line.
252
+ const sample = sampleFirstNonBlankLine(lines, 100);
253
+ process.stderr.write(
254
+ `[whatsapp-import] parse-grammar-miss first-line="${sample}"\n`,
255
+ );
229
256
  throw new Error(
230
- `parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar.`,
257
+ `parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar. parse-grammar-miss first-line="${sample}"`,
231
258
  );
232
259
  }
233
260
 
@@ -280,38 +307,72 @@ interface TimestampMatch {
280
307
  remainder: string;
281
308
  }
282
309
 
310
+ type Ordering = "DDMM" | "MMDD";
311
+
283
312
  function matchTimestampPrefix(
284
313
  line: string,
285
- dateFormat: "DD/MM/YY" | "MM/DD/YY",
314
+ ordering: Ordering,
286
315
  ): TimestampMatch | null {
287
- const re = dateFormat === "MM/DD/YY" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
316
+ const re = ordering === "MMDD" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
288
317
  const m = line.match(re);
289
318
  if (!m) return null;
290
- const a = parseInt(m[1], 10); // dd or mm depending on dateFormat
319
+ const a = parseInt(m[1], 10); // dd or mm depending on ordering
291
320
  const b = parseInt(m[2], 10); // mm or dd
292
- const yy = parseInt(m[3], 10);
321
+ const yearRaw = m[3];
293
322
  const hour = parseInt(m[4], 10);
294
323
  const minute = parseInt(m[5], 10);
295
324
  const second = m[6] !== undefined ? parseInt(m[6], 10) : 0;
296
325
  const remainder = m[7] ?? "";
297
- const day = dateFormat === "MM/DD/YY" ? b : a;
298
- const month = dateFormat === "MM/DD/YY" ? a : b;
326
+ const day = ordering === "MMDD" ? b : a;
327
+ const month = ordering === "MMDD" ? a : b;
299
328
  // Range-check before passing to Date.UTC — that function silently rolls
300
329
  // over invalid components (Date.UTC(2026, 13, 1) → 2027-02-01), which
301
- // would corrupt timestamps when the operator passes the wrong dateFormat
330
+ // would corrupt timestamps when the operator passes the wrong ordering
302
331
  // for a US-locale export. Reject as not-a-prefix; the caller retries the
303
- // file with the correct format or LOUD-FAILs when the file isn't a chat.
332
+ // file with the correct ordering or LOUD-FAILs when the file isn't a chat.
304
333
  if (month < 1 || month > 12 || day < 1 || day > 31) return null;
305
334
  if (hour > 23 || minute > 59 || second > 59) return null;
306
- // WhatsApp's two-digit year is unambiguous in the 21st century; explicit
307
- // shift here documents the assumption rather than relying on locale.
308
- const year = 2000 + yy;
335
+ // Task 845: branch year semantics on captured length. WhatsApp's modern
336
+ // exports emit 4-digit years; legacy exports emit 2-digit. Both are
337
+ // accepted by the same regex and disambiguated here so a single file may
338
+ // hold both shapes (mixed-year imports parse natively).
339
+ const year = yearRaw.length === 2 ? 2000 + parseInt(yearRaw, 10) : parseInt(yearRaw, 10);
309
340
  return {
310
341
  dateParts: { year, month, day, hour, minute, second },
311
342
  remainder,
312
343
  };
313
344
  }
314
345
 
346
+ function resolveOrdering(
347
+ explicit: ParseExportInput["dateFormat"],
348
+ lines: readonly string[],
349
+ ): Ordering {
350
+ if (explicit === "MM/DD/YY" || explicit === "MM/DD/YYYY") return "MMDD";
351
+ if (explicit === "DD/MM/YY" || explicit === "DD/MM/YYYY") return "DDMM";
352
+ // Auto-detect: probe the first prefix-matching line as DD/MM. If range-valid,
353
+ // lock DD/MM (WhatsApp's global default). Otherwise lock MM/DD (US-locale
354
+ // exports, which are the only meaningful exception). Locked once.
355
+ for (const line of lines) {
356
+ if (matchTimestampPrefix(line, "DDMM")) return "DDMM";
357
+ if (matchTimestampPrefix(line, "MMDD")) return "MMDD";
358
+ }
359
+ return "DDMM"; // No matching line — caller will throw zero-parsed-lines anyway.
360
+ }
361
+
362
+ function sampleFirstNonBlankLine(lines: readonly string[], maxScan: number): string {
363
+ const scanLimit = Math.min(maxScan, lines.length);
364
+ for (let i = 0; i < scanLimit; i++) {
365
+ const trimmed = lines[i].trim();
366
+ if (trimmed.length === 0) continue;
367
+ // Strip control characters (including tab, BEL, etc.) so the diagnostic
368
+ // line stays single-line and grep-friendly. Truncate to 80 chars per
369
+ // Task 845 brief — enough to recognise the offending header shape.
370
+ const sanitised = trimmed.replace(/[\x00-\x1F\x7F]/g, "");
371
+ return sanitised.slice(0, 80);
372
+ }
373
+ return "";
374
+ }
375
+
315
376
  function findFirstColonSeparator(remainder: string): number {
316
377
  // Split on the FIRST `: ` (colon-space). A sender display name may itself
317
378
  // contain a `:` (e.g. "Joel: Work"), so we anchor on the first colon
@@ -107,6 +107,8 @@ All writes route through `mcp__memory__memory-archive-write` (bulk Conversation+
107
107
 
108
108
  The deterministic parser also LOUD-FAILs on UTF-8 decode failure (`reason=encoding-error`), zero parsed lines (`reason=not-a-_chat.txt`), and missing required arguments (`reason=accountId|timezone`). All of these surface through the same tool error path; the agent does not need to detect them itself.
109
109
 
110
+ **Structurally enforced (Task 846).** The harness-level `platform/plugins/admin/hooks/archive-ingest-gate.sh` watches every `mcp__memory__whatsapp-export-parse` PostToolUse event; on `isError: true` it sets a parse-error flag and blocks every subsequent tool call this turn until the operator submits the next prompt. Editing parser source under `platform/plugins/*/lib/*` and running `vitest`/`bun test`/`npm test`/`npx jest` is denied unconditionally. The skill's behavioural rule above is the contract; the hook is the enforcement. See [database-operator's LOUD-FAIL prerogative](../../../../templates/specialists/agents/database-operator.md#prerogatives) and [.docs/hooks.md](../../../../../.docs/hooks.md) for the gate's full surface.
111
+
110
112
  ## Idempotency contract
111
113
 
112
114
  Re-importing the same `_chat.txt` is a no-op (`createdMessages=0`, `mergedMessages=N`, NEXT chain unchanged). Re-importing a re-exported file with appended messages adds only the delta and extends the NEXT chain to cover the new tail. Both paths are server-enforced via MERGE on `messageId` and the finalize hook's idempotent NEXT-MERGE.
@@ -15,13 +15,15 @@ WhatsApp's "Export Chat" produces a UTF-8 text file with a deterministic line gr
15
15
  Every message line begins with a square-bracketed timestamp prefix followed by `<Sender>: <body>`:
16
16
 
17
17
  ```
18
- [DD/MM/YY, HH:MM:SS] <Sender>: <body>
18
+ [DD/MM/YYYY, HH:MM:SS] <Sender>: <body> ← modern WhatsApp default (4-digit year)
19
+ [DD/MM/YY, HH:MM:SS] <Sender>: <body> ← legacy exports (2-digit year)
19
20
  ```
20
21
 
21
- - `DD/MM/YY` two-digit day, month, two-digit year (WhatsApp default; some locales emit `MM/DD/YY`operator confirms ambiguous orderings during the timezone prompt).
22
- - `HH:MM:SS` 24-hour time. Older exports may emit `HH:MM` (no seconds); treat seconds as 0 when absent.
23
- - `<Sender>` the sender's display name as WhatsApp showed it. May be a saved contact name, a phone number with country code (`+44 7700 900123`), or "You" for messages sent by the operator (older exports).
24
- - `<body>` message text, possibly multi-line.
22
+ - **Day/month ordering.** `DD/MM` is the WhatsApp default everywhere except US iOS, which emits `MM/DD`. The parser auto-detects from the first prefix-matching line when `dateFormat` is omitted: probe DD/MM first; if range-valid, lock DD/MM; otherwise lock MM/DD. The lock is per-file a single export never mixes orderings (the locale is set by the device that generated the export). Manually concatenated multi-locale archives are an explicit out-of-scope: pass `dateFormat` to override.
23
+ - **Year shape.** Both 2-digit (`\d{2}``2000+yy`) and 4-digit (`\d{4}` → as-is) years are accepted by the same regex (`\d{2,4}`). A single file may hold both shapes; year semantics are resolved per-line from the captured length, not per-file.
24
+ - **Time.** `HH:MM:SS` 24-hour; older exports may emit `HH:MM` (no seconds treat as `:00`).
25
+ - **Sender.** Saved contact name, phone number with country code (`+44 7700 900123`), or `You` for legacy operator-sent messages.
26
+ - **Body.** Message text, possibly multi-line.
25
27
 
26
28
  Trim trailing whitespace from each line before parsing.
27
29
 
@@ -100,7 +102,7 @@ The skill consumes this directly. The `messageId` is computed by the skill (not
100
102
  The parser throws (and `whatsapp-export-parse` returns `isError: true`) on:
101
103
 
102
104
  - Encoding error at file open (UTF-8 decode fails — the parser uses `TextDecoder` with `fatal: true`, so any invalid byte sequence aborts loudly rather than silently substituting U+FFFD).
103
- - Empty file or zero parsed lines after walking the file (the file isn't a `_chat.txt`).
105
+ - Empty file or zero parsed lines after walking the file (the file isn't a `_chat.txt`). The thrown error and the `[whatsapp-import] parse-grammar-miss first-line="<sample>"` stderr line both carry a sanitised first-line sample (control chars stripped, truncated to 80 chars) so the operator can recognise the offending header shape without re-running with a debugger (Task 845).
104
106
  - A timestamp prefix matches but the body parse fails (no `: ` separator after the closing `]` AND no system-pattern match) — emits `parse-error file=<...> line=<n> reason=no-sender-body-separator content="<...>"`.
105
107
  - Missing required input (`accountId`, `timezone`).
106
108