@rubytech/create-realagent 1.0.828 → 1.0.830

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 (144) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +1 -1
  3. package/payload/platform/lib/oauth-llm/dist/index.d.ts +1 -1
  4. package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/oauth-llm/dist/index.js +21 -0
  6. package/payload/platform/lib/oauth-llm/dist/index.js.map +1 -1
  7. package/payload/platform/lib/oauth-llm/src/index.ts +24 -0
  8. package/payload/platform/neo4j/migrations/007-conversation-archive-source.ts +116 -0
  9. package/payload/platform/neo4j/schema.cypher +12 -2
  10. package/payload/platform/package.json +2 -2
  11. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +6 -6
  12. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +14 -8
  13. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +2 -2
  14. package/payload/platform/plugins/contacts/mcp/dist/index.js +5 -5
  15. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +1 -1
  17. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
  18. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +29 -23
  19. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  20. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  21. package/payload/platform/plugins/memory/PLUGIN.md +6 -5
  22. package/payload/platform/plugins/{whatsapp-import/bin/ingest.mjs → memory/bin/conversation-archive-ingest.mjs} +136 -212
  23. package/payload/platform/plugins/{whatsapp-import/bin/whatsapp-ingest.sh → memory/bin/conversation-archive-ingest.sh} +27 -19
  24. package/payload/platform/plugins/memory/mcp/dist/index.js +26 -212
  25. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +4 -3
  27. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
  28. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -6
  29. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
  30. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +103 -0
  31. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +5 -0
  33. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -0
  34. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +30 -0
  35. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +48 -0
  37. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -0
  38. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +23 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts +3 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js +237 -0
  43. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js.map +1 -0
  44. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts +11 -0
  45. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts.map +1 -0
  46. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js +21 -0
  47. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts +16 -0
  49. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts.map +1 -0
  50. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js +39 -0
  51. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js.map +1 -0
  52. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts +17 -0
  53. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts.map +1 -0
  54. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js +90 -0
  55. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js.map +1 -0
  56. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts +9 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts.map +1 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js +32 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js.map +1 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts +3 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts.map +1 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js +27 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js.map +1 -0
  64. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts +45 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts.map +1 -0
  66. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js +125 -0
  67. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js.map +1 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +24 -1
  69. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
  70. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +293 -33
  71. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
  72. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +9 -2
  74. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -1
  75. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +16 -1
  76. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
  77. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +12 -3
  78. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
  79. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts +2 -0
  80. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts.map +1 -0
  81. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js +75 -0
  82. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js.map +1 -0
  83. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts +2 -0
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts.map +1 -0
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js +67 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js.map +1 -0
  87. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +2 -138
  88. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +39 -3
  90. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
  91. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts +2 -0
  92. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts.map +1 -0
  93. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +148 -0
  94. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -0
  95. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -47
  96. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  97. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +9 -318
  98. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  99. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +7 -0
  100. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  101. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +14 -8
  102. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  103. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +21 -17
  104. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  105. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +77 -37
  106. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  107. package/payload/platform/plugins/memory/references/schema-base.md +3 -1
  108. package/payload/platform/plugins/{whatsapp-import/skills/whatsapp-import → memory/skills/conversation-archive}/SKILL.md +45 -36
  109. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +59 -6
  110. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  111. package/payload/platform/scripts/seed-neo4j.sh +9 -8
  112. package/payload/platform/templates/specialists/agents/database-operator.md +7 -14
  113. package/payload/server/chunk-7BO5HDJC.js +10093 -0
  114. package/payload/server/chunk-CUSH3UXP.js +2305 -0
  115. package/payload/server/chunk-EL4DZ56X.js +1116 -0
  116. package/payload/server/chunk-IWNDVGKT.js +10077 -0
  117. package/payload/server/chunk-KC7NUABI.js +654 -0
  118. package/payload/server/chunk-QOJ2D26Z.js +654 -0
  119. package/payload/server/chunk-RC46ZYGT.js +2305 -0
  120. package/payload/server/chunk-WUVXPZIV.js +1116 -0
  121. package/payload/server/client-pool-3TM3SRIA.js +32 -0
  122. package/payload/server/client-pool-7NTEFNVQ.js +32 -0
  123. package/payload/server/cloudflare-task-tracker-4NIODMGL.js +19 -0
  124. package/payload/server/cloudflare-task-tracker-WE77WXSI.js +19 -0
  125. package/payload/server/maxy-edge.js +3 -3
  126. package/payload/server/neo4j-migrations-4XPNJNM6.js +490 -0
  127. package/payload/server/neo4j-migrations-XTQ4WEV6.js +428 -0
  128. package/payload/server/server.js +6 -6
  129. package/payload/platform/plugins/whatsapp-import/PLUGIN.md +0 -48
  130. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/delta-append.test.ts +0 -163
  131. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export-lrm.test.ts +0 -83
  132. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +0 -678
  133. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/sessionize.test.ts +0 -91
  134. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/to-classifier-input.test.ts +0 -59
  135. package/payload/platform/plugins/whatsapp-import/lib/src/delta-cursor.ts +0 -54
  136. package/payload/platform/plugins/whatsapp-import/lib/src/derive-keys.ts +0 -82
  137. package/payload/platform/plugins/whatsapp-import/lib/src/index.ts +0 -22
  138. package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +0 -471
  139. package/payload/platform/plugins/whatsapp-import/lib/src/sessionize.ts +0 -81
  140. package/payload/platform/plugins/whatsapp-import/lib/src/to-classifier-input.ts +0 -48
  141. package/payload/platform/plugins/whatsapp-import/lib/tsconfig.json +0 -9
  142. package/payload/platform/plugins/whatsapp-import/lib/vitest.config.ts +0 -9
  143. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/conversation-archive-shape.md +0 -143
  144. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +0 -109
@@ -1,91 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { sessionize } from "../sessionize.js";
3
- import type { ParsedLine } from "../parse-export.js";
4
-
5
- function mk(dateSent: string, body = "x"): ParsedLine {
6
- return { senderName: "Joel", dateSent, body, sequenceIndex: 0 };
7
- }
8
-
9
- describe("sessionize", () => {
10
- it("returns [] for empty input", () => {
11
- expect(sessionize([], 12)).toEqual([]);
12
- });
13
-
14
- it("returns one one-message session for a single message", () => {
15
- const out = sessionize([mk("2026-03-14T10:00:00+00:00")], 12);
16
- expect(out).toHaveLength(1);
17
- expect(out[0].messages).toHaveLength(1);
18
- expect(out[0].index).toBe(0);
19
- expect(out[0].firstMessageAt).toBe("2026-03-14T10:00:00+00:00");
20
- expect(out[0].lastMessageAt).toBe("2026-03-14T10:00:00+00:00");
21
- });
22
-
23
- it("groups messages within the gap into one session", () => {
24
- const messages = [
25
- mk("2026-03-14T10:00:00+00:00"),
26
- mk("2026-03-14T11:00:00+00:00"),
27
- mk("2026-03-14T12:00:00+00:00"),
28
- ];
29
- const out = sessionize(messages, 12);
30
- expect(out).toHaveLength(1);
31
- expect(out[0].messages).toHaveLength(3);
32
- });
33
-
34
- it("cuts at gap > gapHours", () => {
35
- const messages = [
36
- mk("2026-03-14T10:00:00+00:00"),
37
- mk("2026-03-14T11:00:00+00:00"),
38
- mk("2026-03-14T23:30:00+00:00"), // 12.5h gap from previous
39
- mk("2026-03-15T00:00:00+00:00"),
40
- ];
41
- const out = sessionize(messages, 12);
42
- expect(out).toHaveLength(2);
43
- expect(out[0].messages).toHaveLength(2);
44
- expect(out[1].messages).toHaveLength(2);
45
- expect(out[1].index).toBe(1);
46
- });
47
-
48
- it("cuts at exact-at-threshold (gap == gapHours triggers a cut)", () => {
49
- const messages = [
50
- mk("2026-03-14T10:00:00+00:00"),
51
- mk("2026-03-14T22:00:00+00:00"), // exactly 12h gap
52
- ];
53
- const out = sessionize(messages, 12);
54
- expect(out).toHaveLength(2);
55
- });
56
-
57
- it("does not cut at gap just under threshold", () => {
58
- const messages = [
59
- mk("2026-03-14T10:00:00+00:00"),
60
- mk("2026-03-14T21:59:59+00:00"), // 11h59m59s gap
61
- ];
62
- const out = sessionize(messages, 12);
63
- expect(out).toHaveLength(1);
64
- });
65
-
66
- it("emits firstMessageAt / lastMessageAt from the session boundary messages", () => {
67
- const messages = [
68
- mk("2026-03-14T10:00:00+00:00"),
69
- mk("2026-03-14T11:00:00+00:00"),
70
- mk("2026-03-14T12:00:00+00:00"),
71
- ];
72
- const out = sessionize(messages, 12);
73
- expect(out[0].firstMessageAt).toBe("2026-03-14T10:00:00+00:00");
74
- expect(out[0].lastMessageAt).toBe("2026-03-14T12:00:00+00:00");
75
- });
76
-
77
- it("rejects non-positive gapHours", () => {
78
- expect(() => sessionize([mk("2026-03-14T10:00:00+00:00")], 0)).toThrow(/positive/);
79
- expect(() => sessionize([mk("2026-03-14T10:00:00+00:00")], -1)).toThrow(/positive/);
80
- });
81
-
82
- it("indexes sessions starting at 0 in chronological order", () => {
83
- const messages = [
84
- mk("2026-03-14T10:00:00+00:00"),
85
- mk("2026-03-15T10:00:00+00:00"), // 24h gap
86
- mk("2026-03-16T10:00:00+00:00"), // 24h gap
87
- ];
88
- const out = sessionize(messages, 12);
89
- expect(out.map((s) => s.index)).toEqual([0, 1, 2]);
90
- });
91
- });
@@ -1,59 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { toClassifierInput } from "../to-classifier-input.js";
3
- import type { Session } from "../sessionize.js";
4
-
5
- function mkSession(messages: Session["messages"]): Session {
6
- return {
7
- index: 0,
8
- firstMessageAt: messages[0].dateSent,
9
- lastMessageAt: messages[messages.length - 1].dateSent,
10
- messages,
11
- };
12
- }
13
-
14
- describe("toClassifierInput", () => {
15
- it("renders one line per message in `[ts] Sender: body` form", () => {
16
- const session = mkSession([
17
- { senderName: "Joel", dateSent: "2026-03-14T10:00:00+00:00", body: "hi", sequenceIndex: 0 },
18
- { senderName: "Adam", dateSent: "2026-03-14T10:01:00+00:00", body: "hey", sequenceIndex: 1 },
19
- ]);
20
- expect(toClassifierInput(session)).toBe(
21
- "[2026-03-14 10:00:00 +00:00] Joel: hi\n[2026-03-14 10:01:00 +00:00] Adam: hey",
22
- );
23
- });
24
-
25
- it("preserves the operator's offset in the formatted timestamp", () => {
26
- const session = mkSession([
27
- { senderName: "Joel", dateSent: "2026-03-14T10:00:00+01:00", body: "hi", sequenceIndex: 0 },
28
- ]);
29
- expect(toClassifierInput(session)).toBe("[2026-03-14 10:00:00 +01:00] Joel: hi");
30
- });
31
-
32
- it("normalises a 'Z' offset to +00:00 in the rendered form", () => {
33
- const session = mkSession([
34
- { senderName: "Joel", dateSent: "2026-03-14T10:00:00Z", body: "hi", sequenceIndex: 0 },
35
- ]);
36
- expect(toClassifierInput(session)).toBe("[2026-03-14 10:00:00 +00:00] Joel: hi");
37
- });
38
-
39
- it("preserves multi-line message bodies verbatim (no escape, internal newlines retained)", () => {
40
- const session = mkSession([
41
- {
42
- senderName: "Joel",
43
- dateSent: "2026-03-14T10:00:00+00:00",
44
- body: "line one\nline two",
45
- sequenceIndex: 0,
46
- },
47
- ]);
48
- expect(toClassifierInput(session)).toBe(
49
- "[2026-03-14 10:00:00 +00:00] Joel: line one\nline two",
50
- );
51
- });
52
-
53
- it("falls back to the raw ISO when the timestamp shape does not match (defensive)", () => {
54
- const session = mkSession([
55
- { senderName: "Joel", dateSent: "not-an-iso", body: "hi", sequenceIndex: 0 },
56
- ]);
57
- expect(toClassifierInput(session)).toBe("[not-an-iso] Joel: hi");
58
- });
59
- });
@@ -1,54 +0,0 @@
1
- import type { ParsedLine } from "./parse-export.js";
2
- import { deriveMessageContentHash } from "./derive-keys.js";
3
-
4
- // ---------------------------------------------------------------------------
5
- // delta-cursor — locate the cursor for delta-append re-imports (Task 891).
6
- //
7
- // Pure function. Given a parsed re-export and the `lastIngestedMessageHash`
8
- // recorded on the prior `:ConversationArchive`, find the position of the
9
- // matching message in the parsed sequence and return `index + 1` (the slice
10
- // start for the delta). Three outcomes:
11
- //
12
- // { kind: "found", deltaStart } → slice parsedLines from deltaStart
13
- // { kind: "empty" } → cursor is the last line; no delta
14
- // { kind: "missing" } → cursor not found (LOUD-FAIL upstream)
15
- //
16
- // `missing` covers both the "operator deleted prior messages from the
17
- // re-export" case and the "operator imported a different chat archive"
18
- // case. The orchestrator emits `[whatsapp-import] FAIL delta-cursor-missing`
19
- // and exits non-zero when this surfaces.
20
- // ---------------------------------------------------------------------------
21
-
22
- export type CursorResult =
23
- | { kind: "found"; deltaStart: number }
24
- | { kind: "empty" }
25
- | { kind: "missing" };
26
-
27
- /**
28
- * Walk parsed lines forward and return the first index whose content hash
29
- * matches `lastIngestedMessageHash`. The first match is correct because
30
- * messages with identical (dateSent, normalisedSenderName, body) tuples
31
- * are genuine duplicates — there is no way to disambiguate them and slicing
32
- * after the first occurrence is the chronologically safe choice.
33
- */
34
- export function findDeltaCursor(
35
- parsedLines: readonly ParsedLine[],
36
- lastIngestedMessageHash: string,
37
- ): CursorResult {
38
- if (!lastIngestedMessageHash || !lastIngestedMessageHash.trim()) {
39
- throw new Error("findDeltaCursor: lastIngestedMessageHash must be non-empty");
40
- }
41
- for (let i = 0; i < parsedLines.length; i++) {
42
- const line = parsedLines[i];
43
- const hash = deriveMessageContentHash({
44
- dateSent: line.dateSent,
45
- senderName: line.senderName,
46
- body: line.body,
47
- });
48
- if (hash === lastIngestedMessageHash) {
49
- if (i === parsedLines.length - 1) return { kind: "empty" };
50
- return { kind: "found", deltaStart: i + 1 };
51
- }
52
- }
53
- return { kind: "missing" };
54
- }
@@ -1,82 +0,0 @@
1
- import { createHash } from "node:crypto";
2
-
3
- // ---------------------------------------------------------------------------
4
- // derive-keys — natural-key derivation for whatsapp-import (Task 891,
5
- // supersedes Task 870's per-message contract).
6
- //
7
- // Pure functions. No I/O. The whole point is that re-imports of the same
8
- // archive collapse to the same identity regardless of release-level drift in
9
- // chunk indices, hash widths, or arbitrary tiebreakers.
10
- //
11
- // Identity contracts (Task 891 brief):
12
- //
13
- // conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds.join(","))
14
- // messageContentHash = sha256(dateSent + "|" + NFKC-trim-lower(senderName) + "|" + body)
15
- //
16
- // `conversationIdentity` is stable across re-exports — same operator + same
17
- // participant set → same identity, regardless of file bytes. DM and group
18
- // follow the same formula; the difference is the participant array length.
19
- // `messageContentHash` is content-only (no archive sha256, no chunk index)
20
- // so cursor lookup survives a fresh re-export of the same chat.
21
- // ---------------------------------------------------------------------------
22
-
23
- export function normaliseSenderName(name: string): string {
24
- return name.normalize("NFKC").trim().toLowerCase();
25
- }
26
-
27
- export function sha256Hex(input: string): string {
28
- return createHash("sha256").update(input).digest("hex");
29
- }
30
-
31
- export interface DeriveConversationIdentityInput {
32
- accountId: string;
33
- /**
34
- * Element IDs of every confirmed participant (owner + others). Order is
35
- * not significant; the function sorts internally so the same set always
36
- * produces the same identity.
37
- */
38
- participantElementIds: readonly string[];
39
- }
40
-
41
- /**
42
- * Compute the stable identity for a conversation. Same accountId + same
43
- * participant set ⇒ same identity, regardless of message content or export
44
- * file bytes. DM and group chats use this identical formula.
45
- */
46
- export function deriveConversationIdentity(
47
- input: DeriveConversationIdentityInput,
48
- ): string {
49
- if (!input.accountId || !input.accountId.trim()) {
50
- throw new Error("deriveConversationIdentity: accountId is required");
51
- }
52
- if (input.participantElementIds.length === 0) {
53
- throw new Error("deriveConversationIdentity: participantElementIds must be non-empty");
54
- }
55
- const sorted = [...input.participantElementIds].sort();
56
- return sha256Hex(`${input.accountId}:${sorted.join(",")}`);
57
- }
58
-
59
- export interface DeriveMessageContentHashInput {
60
- /** ISO 8601 with timezone offset, as emitted by parseExport. */
61
- dateSent: string;
62
- /** Raw senderName from the export line. Normalised internally. */
63
- senderName: string;
64
- /** Raw message body. */
65
- body: string;
66
- }
67
-
68
- /**
69
- * Compute a content-only hash for a single message. Used as the delta-append
70
- * cursor: `:ConversationArchive.lastIngestedMessageHash` records the hash of
71
- * the last ingested message; on re-import, the orchestrator finds the line
72
- * with the matching hash and slices everything after it.
73
- *
74
- * Excludes archive sha256 deliberately — the cursor must survive a fresh
75
- * re-export of the same chat (different file bytes, same message tuples).
76
- */
77
- export function deriveMessageContentHash(
78
- input: DeriveMessageContentHashInput,
79
- ): string {
80
- const norm = normaliseSenderName(input.senderName);
81
- return sha256Hex(`${input.dateSent}|${norm}|${input.body}`);
82
- }
@@ -1,22 +0,0 @@
1
- export { parseExport } from "./parse-export.js";
2
- export type {
3
- ParseExportInput,
4
- ParseExportResult,
5
- ParseExportCounters,
6
- ParsedLine,
7
- } from "./parse-export.js";
8
- export {
9
- normaliseSenderName,
10
- sha256Hex,
11
- deriveConversationIdentity,
12
- deriveMessageContentHash,
13
- } from "./derive-keys.js";
14
- export type {
15
- DeriveConversationIdentityInput,
16
- DeriveMessageContentHashInput,
17
- } from "./derive-keys.js";
18
- export { sessionize } from "./sessionize.js";
19
- export type { Session } from "./sessionize.js";
20
- export { toClassifierInput } from "./to-classifier-input.js";
21
- export { findDeltaCursor } from "./delta-cursor.js";
22
- export type { CursorResult } from "./delta-cursor.js";