@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.
- package/package.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/lib/oauth-llm/dist/index.d.ts +1 -1
- package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/oauth-llm/dist/index.js +21 -0
- package/payload/platform/lib/oauth-llm/dist/index.js.map +1 -1
- package/payload/platform/lib/oauth-llm/src/index.ts +24 -0
- package/payload/platform/neo4j/migrations/007-conversation-archive-source.ts +116 -0
- package/payload/platform/neo4j/schema.cypher +12 -2
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +6 -6
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +14 -8
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +2 -2
- package/payload/platform/plugins/contacts/mcp/dist/index.js +5 -5
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +29 -23
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +6 -5
- package/payload/platform/plugins/{whatsapp-import/bin/ingest.mjs → memory/bin/conversation-archive-ingest.mjs} +136 -212
- package/payload/platform/plugins/{whatsapp-import/bin/whatsapp-ingest.sh → memory/bin/conversation-archive-ingest.sh} +27 -19
- package/payload/platform/plugins/memory/mcp/dist/index.js +26 -212
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +4 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -6
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +103 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +48 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +23 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js +237 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts +11 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js +21 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts +16 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js +39 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts +17 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js +90 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js +32 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +24 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +293 -33
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +9 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +16 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +12 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js +75 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js +67 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +2 -138
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +39 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +148 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -47
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +9 -318
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +14 -8
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +21 -17
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +77 -37
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +3 -1
- package/payload/platform/plugins/{whatsapp-import/skills/whatsapp-import → memory/skills/conversation-archive}/SKILL.md +45 -36
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +59 -6
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/scripts/seed-neo4j.sh +9 -8
- package/payload/platform/templates/specialists/agents/database-operator.md +7 -14
- package/payload/server/chunk-7BO5HDJC.js +10093 -0
- package/payload/server/chunk-CUSH3UXP.js +2305 -0
- package/payload/server/chunk-EL4DZ56X.js +1116 -0
- package/payload/server/chunk-IWNDVGKT.js +10077 -0
- package/payload/server/chunk-KC7NUABI.js +654 -0
- package/payload/server/chunk-QOJ2D26Z.js +654 -0
- package/payload/server/chunk-RC46ZYGT.js +2305 -0
- package/payload/server/chunk-WUVXPZIV.js +1116 -0
- package/payload/server/client-pool-3TM3SRIA.js +32 -0
- package/payload/server/client-pool-7NTEFNVQ.js +32 -0
- package/payload/server/cloudflare-task-tracker-4NIODMGL.js +19 -0
- package/payload/server/cloudflare-task-tracker-WE77WXSI.js +19 -0
- package/payload/server/maxy-edge.js +3 -3
- package/payload/server/neo4j-migrations-4XPNJNM6.js +490 -0
- package/payload/server/neo4j-migrations-XTQ4WEV6.js +428 -0
- package/payload/server/server.js +6 -6
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +0 -48
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/delta-append.test.ts +0 -163
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export-lrm.test.ts +0 -83
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +0 -678
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/sessionize.test.ts +0 -91
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/to-classifier-input.test.ts +0 -59
- package/payload/platform/plugins/whatsapp-import/lib/src/delta-cursor.ts +0 -54
- package/payload/platform/plugins/whatsapp-import/lib/src/derive-keys.ts +0 -82
- package/payload/platform/plugins/whatsapp-import/lib/src/index.ts +0 -22
- package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +0 -471
- package/payload/platform/plugins/whatsapp-import/lib/src/sessionize.ts +0 -81
- package/payload/platform/plugins/whatsapp-import/lib/src/to-classifier-input.ts +0 -48
- package/payload/platform/plugins/whatsapp-import/lib/tsconfig.json +0 -9
- package/payload/platform/plugins/whatsapp-import/lib/vitest.config.ts +0 -9
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/conversation-archive-shape.md +0 -143
- 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
|
-
});
|
package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/to-classifier-input.test.ts
DELETED
|
@@ -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";
|