@rubytech/create-realagent 1.0.826 → 1.0.829

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 (103) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +35 -2
  3. package/payload/platform/package.json +2 -2
  4. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +39 -54
  5. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +26 -52
  6. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +7 -7
  7. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  8. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  9. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -0
  10. package/payload/platform/plugins/memory/PLUGIN.md +5 -5
  11. package/payload/platform/plugins/memory/mcp/dist/index.js +18 -253
  12. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +51 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +103 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
  17. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +19 -4
  18. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +149 -56
  20. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
  21. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +16 -1
  22. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
  23. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +12 -3
  24. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +2 -138
  26. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts +2 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts.map +1 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +66 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts +2 -0
  32. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +148 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -64
  36. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +6 -336
  38. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +30 -0
  40. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +231 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +21 -17
  44. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +77 -37
  46. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  47. package/payload/platform/plugins/memory/references/schema-base.md +7 -2
  48. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +54 -4
  49. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  50. package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.d.ts +18 -0
  51. package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.d.ts.map +1 -0
  52. package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.js +31 -0
  53. package/payload/platform/plugins/whatsapp-import/lib/dist/delta-cursor.js.map +1 -0
  54. package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts +27 -12
  55. package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.d.ts.map +1 -1
  56. package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js +40 -20
  57. package/payload/platform/plugins/whatsapp-import/lib/dist/derive-keys.js.map +1 -1
  58. package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts +7 -4
  59. package/payload/platform/plugins/whatsapp-import/lib/dist/index.d.ts.map +1 -1
  60. package/payload/platform/plugins/whatsapp-import/lib/dist/index.js +9 -6
  61. package/payload/platform/plugins/whatsapp-import/lib/dist/index.js.map +1 -1
  62. package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.d.ts +25 -0
  63. package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.d.ts.map +1 -0
  64. package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.js +48 -0
  65. package/payload/platform/plugins/whatsapp-import/lib/dist/sessionize.js.map +1 -0
  66. package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.d.ts +3 -0
  67. package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.d.ts.map +1 -0
  68. package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.js +47 -0
  69. package/payload/platform/plugins/whatsapp-import/lib/dist/to-classifier-input.js.map +1 -0
  70. package/payload/platform/scripts/seed-neo4j.sh +15 -14
  71. package/payload/platform/templates/specialists/agents/database-operator.md +10 -17
  72. package/payload/server/chunk-CUSH3UXP.js +2305 -0
  73. package/payload/server/chunk-IWNDVGKT.js +10077 -0
  74. package/payload/server/chunk-KC7NUABI.js +654 -0
  75. package/payload/server/chunk-T2OPNP3L.js +654 -0
  76. package/payload/server/chunk-WUVXPZIV.js +1116 -0
  77. package/payload/server/client-pool-3TM3SRIA.js +32 -0
  78. package/payload/server/cloudflare-task-tracker-4NIODMGL.js +19 -0
  79. package/payload/server/cloudflare-task-tracker-CR6TL4VL.js +19 -0
  80. package/payload/server/maxy-edge.js +3 -3
  81. package/payload/server/neo4j-migrations-XTQ4WEV6.js +428 -0
  82. package/payload/server/public/assets/{admin-DOkUspG1.js → admin-BNwPsMhJ.js} +2 -2
  83. package/payload/server/public/assets/{graph-LLMJa4Ch.js → graph-N_Bw-8oT.js} +1 -1
  84. package/payload/server/public/assets/{page-DoaF3DB0.js → page-BKLGP-th.js} +1 -1
  85. package/payload/server/public/graph.html +2 -2
  86. package/payload/server/public/index.html +2 -2
  87. package/payload/server/server.js +281 -168
  88. package/payload/platform/plugins/whatsapp-import/PLUGIN.md +0 -46
  89. package/payload/platform/plugins/whatsapp-import/bin/ingest.mjs +0 -670
  90. package/payload/platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh +0 -131
  91. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/filter-gate.test.ts +0 -172
  92. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/ingest-idempotence.test.ts +0 -141
  93. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export-lrm.test.ts +0 -83
  94. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +0 -678
  95. package/payload/platform/plugins/whatsapp-import/lib/src/derive-keys.ts +0 -59
  96. package/payload/platform/plugins/whatsapp-import/lib/src/filter.ts +0 -136
  97. package/payload/platform/plugins/whatsapp-import/lib/src/index.ts +0 -19
  98. package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +0 -471
  99. package/payload/platform/plugins/whatsapp-import/lib/tsconfig.json +0 -9
  100. package/payload/platform/plugins/whatsapp-import/lib/vitest.config.ts +0 -9
  101. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +0 -131
  102. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +0 -109
  103. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import-enrich/SKILL.md +0 -333
@@ -13,10 +13,9 @@ import { memoryReindex } from "./tools/memory-reindex.js";
13
13
  import { memoryIngestExtract } from "./tools/memory-ingest-extract.js";
14
14
  import { memoryIngest } from "./tools/memory-ingest.js";
15
15
  import { memoryArchiveWrite } from "./tools/memory-archive-write.js";
16
- import { whatsappExportParse } from "./tools/whatsapp-export-parse.js";
17
- import { whatsappExportInsightWrite } from "./tools/whatsapp-export-insight-write.js";
18
- import { whatsappExportPreview } from "./tools/whatsapp-export-preview.js";
19
- import { whatsappExportInsightPass } from "./tools/whatsapp-export-insight-pass.js";
16
+ // Task 894: WhatsApp `_chat.txt` ingestion flows through the unified
17
+ // document-ingest pipeline (memory-classify mode='chat' + memory-ingest
18
+ // parentLabel='ConversationArchive'); no WhatsApp-specific MCP tools remain.
20
19
  import { memoryIngestWeb } from "./tools/memory-ingest-web.js";
21
20
  import { memoryClassify } from "./tools/memory-classify.js";
22
21
  import { memoryUpdate } from "./tools/memory-update.js";
@@ -779,50 +778,29 @@ if (!readOnly) {
779
778
  };
780
779
  }
781
780
  });
782
- server.tool("memory-archive-write", "Bulk-archive write surface (Task 744; Task 804). Writes a flat dataset (typed entities + natural edges) into the graph " +
781
+ server.tool("memory-archive-write", "Bulk-archive write surface (Task 744). Writes a flat dataset (typed entities + natural edges) into the graph " +
783
782
  "in 500-row UNWIND batches. The Cypher body is fixed server-side per `archiveType`; the agent supplies parsed " +
784
- "rows + the discriminant, never raw Cypher. Use ONLY for first-class entity exports (LinkedIn Connections, " +
785
- "WhatsApp `_chat.txt` exports, future CRM-type seeds). Use memory-ingest for narrative documents " +
786
- "(KnowledgeDocument + Section + NEXT) and memory-write for single-node operator-driven writes. " +
787
- "Currently supported archiveType values: `linkedin-connections`, `whatsapp-export`. " +
788
- "When archiveType='whatsapp-export', the `conversation` block and `participantNodeIds` array are required; " +
789
- "otherwise they are ignored.", {
783
+ "rows + the discriminant, never raw Cypher. Use ONLY for flat first-class entity exports (LinkedIn Connections, " +
784
+ "future CRM-type seeds). Use memory-ingest for narrative content — including WhatsApp `_chat.txt` archives, " +
785
+ "which are document-shaped narrative and flow through document-ingest with `parentLabel='ConversationArchive'` " +
786
+ "(the unified-ingest pipeline). Use memory-write for single-node operator-driven writes. " +
787
+ "Currently supported archiveType values: `linkedin-connections`.", {
790
788
  archiveType: z
791
- .enum(["linkedin-connections", "whatsapp-export"])
789
+ .enum(["linkedin-connections"])
792
790
  .describe("Discriminant naming the per-source schema and Cypher body the server runs. Add a new value here only when the corresponding handler is added in memory-archive-write.ts."),
793
791
  ownerNodeId: z
794
792
  .string()
795
793
  .min(1)
796
- .describe("elementId of the archive owner — :AdminUser for an operator's own archive, or :Person for an external-archive owner. Confirmed during the skill's owner-confirmation flow before this tool is invoked. For `whatsapp-export`, this is the operator who exported the chat (metadata for provenance, not a row-level subject)."),
794
+ .describe("elementId of the archive owner — :AdminUser for an operator's own archive, or :Person for an external-archive owner. Confirmed during the skill's owner-confirmation flow before this tool is invoked."),
797
795
  rows: z
798
796
  .array(z.record(z.string(), z.unknown()))
799
797
  .min(1)
800
- .describe("Parsed rows. The skill's selective-ingest gate runs BEFORE this tool — large blanket archives get filtered (Company / Position / Connected On range for linkedin-connections; date / sender / keyword for whatsapp-export) before the write call."),
798
+ .describe("Parsed rows. The skill's selective-ingest gate runs BEFORE this tool — large blanket archives get filtered (e.g. Company / Position / Connected On range for linkedin-connections) before the write call."),
801
799
  sessionId: z
802
800
  .string()
803
801
  .optional()
804
802
  .describe("Skill-run UUID for provenance stamping. Falls back to SESSION_ID env var when absent."),
805
- conversation: z
806
- .object({
807
- conversationId: z.string().min(1),
808
- archiveSourceFile: z.string().min(1),
809
- firstMessageAt: z.string().min(1),
810
- lastMessageAt: z.string().min(1),
811
- participantCount: z.number().int().nonnegative(),
812
- messageCount: z.number().int().nonnegative(),
813
- })
814
- .optional()
815
- .describe("Required for archiveType='whatsapp-export'. One archive-write call writes exactly one Conversation; every row's conversationId must match this block's conversationId or the write is refused."),
816
- participantNodeIds: z
817
- .array(z.string().min(1))
818
- .optional()
819
- .describe("Required for archiveType='whatsapp-export'. Array of elementIds (:AdminUser or :Person) covering every distinct sender in `rows`. Server-side `verifyParticipants` rejects unresolved or cross-account ids before any write."),
820
- archiveFilePath: z
821
- .string()
822
- .min(1)
823
- .optional()
824
- .describe("Required for archiveType='whatsapp-export' (Task 805). Operator-supplied path to the source `_chat.txt`. The server re-computes sha256 of the file bytes and asserts it matches `conversation.archiveSourceFile`. Mismatch is a hard reject before any Cypher runs — closes the silent-substitution window where an agent could pair rows[] for one file with a stale conversation block for another. Pass the same path you supplied to `whatsapp-export-parse`."),
825
- }, async ({ archiveType, ownerNodeId, rows, sessionId: sessionIdOverride, conversation, participantNodeIds, archiveFilePath }) => {
803
+ }, async ({ archiveType, ownerNodeId, rows, sessionId: sessionIdOverride }) => {
826
804
  try {
827
805
  const result = await memoryArchiveWrite({
828
806
  archiveType,
@@ -830,9 +808,6 @@ if (!readOnly) {
830
808
  accountId,
831
809
  rows: rows,
832
810
  sessionId: resolveSessionId(sessionIdOverride),
833
- conversation,
834
- participantNodeIds,
835
- archiveFilePath,
836
811
  });
837
812
  return {
838
813
  content: [{
@@ -851,214 +826,6 @@ if (!readOnly) {
851
826
  };
852
827
  }
853
828
  });
854
- server.tool("whatsapp-export-parse", "Deterministic WhatsApp `_chat.txt` parser (Task 805). Reads the file, computes sha256 over the raw bytes, " +
855
- "decodes UTF-8 (LOUD-FAIL on encoding error), and walks the line grammar — timestamp prefix, sender/body split, " +
856
- "multi-line body accumulation, system-message and media-only skip patterns. Returns " +
857
- "`{conversationId, archiveSourceFile, parsedLines[], counters}`. The skill consumes the result and passes the " +
858
- "same `archiveSourceFile` and `archiveFilePath` to `memory-archive-write`, where the server re-computes the " +
859
- "hash and asserts the parser ran on the same file. The agent never tokenises lines itself — this tool replaces " +
860
- "the prose grammar in `references/export-parse.md` with deterministic code so silent line-drops cannot occur.", {
861
- filePath: z
862
- .string()
863
- .min(1)
864
- .describe("Absolute path to the `_chat.txt` file (typically dropped into chat by the operator and resolved against the attachment store)."),
865
- timezone: z
866
- .string()
867
- .min(1)
868
- .describe("IANA timezone the operator confirmed (e.g. 'Europe/London', 'America/New_York', 'UTC'). Each parsed timestamp is emitted as ISO 8601 with the offset that this zone holds for the wall-clock instant — DST is handled correctly."),
869
- dateFormat: z
870
- .enum(["DD/MM/YY", "MM/DD/YY", "DD/MM/YYYY", "MM/DD/YYYY"])
871
- .optional()
872
- .describe("Date ordering and year shape. Omit for auto-detect (Task 845): the parser probes the first matched line as DD/MM and locks that ordering if range-valid; otherwise locks MM/DD. Year shape is independent — both 2-digit (legacy) and 4-digit (modern) years are accepted within the same file. Pass an explicit value only when the operator confirms a US-locale export or when auto-detect would mis-lock on a manually concatenated multi-locale archive."),
873
- }, async ({ filePath, timezone, dateFormat }) => {
874
- try {
875
- const result = await whatsappExportParse({
876
- filePath,
877
- accountId,
878
- timezone,
879
- dateFormat,
880
- });
881
- return {
882
- content: [{
883
- type: "text",
884
- text: JSON.stringify(result),
885
- }],
886
- };
887
- }
888
- catch (err) {
889
- return {
890
- content: [{
891
- type: "text",
892
- text: `whatsapp-export-parse failed: ${err instanceof Error ? err.message : String(err)}`,
893
- }],
894
- isError: true,
895
- };
896
- }
897
- });
898
- server.tool("whatsapp-export-insight-write", "Server-side gated insight-edge writer for WhatsApp imports (Task 805). Writes `:MENTIONS` edges from " +
899
- "`:Message` to existing `:Person`/`:AdminUser` nodes, and `:RELATED_TO` edges between two existing distinct " +
900
- "Persons. The server re-runs `memory-search` for the named entity and asserts at least one of the operator-" +
901
- "supplied `candidateElementIds` matches a live result; rejects single-first-name names without a disambiguator " +
902
- "flag; requires `operatorConfirmed: true` for `:RELATED_TO` writes. The agent cannot bypass these gates — they " +
903
- "live in code, not in the skill markdown that prior versions of the insight pass relied on.", {
904
- kind: z
905
- .enum(["MENTIONS", "RELATED_TO"])
906
- .describe("Edge type. MENTIONS connects a :Message to a Person/AdminUser referenced in the body. RELATED_TO connects two distinct Persons whose relationship the conversation evidences."),
907
- conversationId: z
908
- .string()
909
- .min(1)
910
- .describe("conversationId of the originating WhatsApp conversation (audit context)."),
911
- messageId: z
912
- .string()
913
- .optional()
914
- .describe("messageId of the source message — required for kind='MENTIONS', omitted for kind='RELATED_TO'."),
915
- fromNodeId: z
916
- .string()
917
- .min(1)
918
- .describe("elementId of the edge tail. For MENTIONS: the :Message. For RELATED_TO: the first :Person/:AdminUser."),
919
- toNodeId: z
920
- .string()
921
- .min(1)
922
- .describe("elementId of the edge head. For MENTIONS: the :Person/:AdminUser referenced. For RELATED_TO: the second :Person/:AdminUser."),
923
- name: z
924
- .string()
925
- .min(1)
926
- .describe("The display name as it appeared in the chat (e.g. 'Sarah', 'Sarah Chen'). Used to re-run memory-search server-side for the gate."),
927
- candidateElementIds: z
928
- .array(z.string().min(1))
929
- .min(1)
930
- .describe("Element IDs the agent's prior memory-search returned. The server re-runs the search and asserts at least one of these IDs matches a live result. If none match, the gate rejects the write (the agent's claimed evidence does not survive a fresh query)."),
931
- disambiguatorOk: z
932
- .boolean()
933
- .optional()
934
- .describe("Set to true ONLY when the chat-mention name carries an unambiguous disambiguator (full name 'Sarah Chen', explicit role context 'Sarah at Acme', email/phone). Single-first-name mentions without this flag are rejected as 'first-name-only' regardless of memory-search hit count."),
935
- operatorConfirmed: z
936
- .boolean()
937
- .optional()
938
- .describe("Set to true ONLY for RELATED_TO writes the operator has explicitly confirmed in chat. Defaults to false — every RELATED_TO without explicit confirmation is rejected to prevent inferred-relationship landfill."),
939
- evidenceMessageIds: z
940
- .array(z.string().min(1))
941
- .optional()
942
- .describe("For RELATED_TO: messageIds whose body evidenced the relationship. Stored as an edge property for forensic review."),
943
- reason: z
944
- .string()
945
- .optional()
946
- .describe("Free-text justification (one sentence) the operator can grep in audit logs. Not load-bearing for the gate; it's audit signal."),
947
- sessionId: z
948
- .string()
949
- .optional()
950
- .describe("Skill-run UUID for provenance. Falls back to SESSION_ID env var when absent."),
951
- }, async (params) => {
952
- try {
953
- const result = await whatsappExportInsightWrite({
954
- ...params,
955
- accountId,
956
- sessionId: resolveSessionId(params.sessionId),
957
- allowedScopes,
958
- envAgentSlug,
959
- envKeywordSubscriptions,
960
- });
961
- return {
962
- content: [{
963
- type: "text",
964
- text: JSON.stringify(result),
965
- }],
966
- };
967
- }
968
- catch (err) {
969
- return {
970
- content: [{
971
- type: "text",
972
- text: `whatsapp-export-insight-write failed: ${err instanceof Error ? err.message : String(err)}`,
973
- }],
974
- isError: true,
975
- };
976
- }
977
- });
978
- server.tool("whatsapp-export-preview", "Parse-only preview of a WhatsApp `_chat.txt` archive (Task 871). Returns parsed/skipped counts, " +
979
- "the date range, the per-sender message histogram, the file's sha256, and total byte size — enough for the " +
980
- "operator to choose a `--filter` value (`all`, `senders=<csv>`, `date-range=<isoFrom>..<isoTo>`) before invoking " +
981
- "the deterministic Bash entry `whatsapp-ingest.sh`. Read-only: NO Cypher writes, NO Neo4j connection. " +
982
- "Phase 1 contract — preview-then-filter-then-write. Pair with `whatsapp-ingest.sh` (Phase 1 archive-write) " +
983
- "and `whatsapp-export-insight-pass` (Phase 2 enrichment) — the three tools cover the two-phase WhatsApp " +
984
- "ingest pipeline.", {
985
- filePath: z
986
- .string()
987
- .min(1)
988
- .describe("Absolute path to the `_chat.txt` file (typically the operator-supplied archive path resolved against the attachment store)."),
989
- timezone: z
990
- .string()
991
- .min(1)
992
- .describe("IANA timezone the operator confirmed (e.g. 'Europe/London'). Same parameter as the parser; controls how the parsed timestamps render for the dateRange field."),
993
- dateFormat: z
994
- .enum(["DD/MM/YY", "MM/DD/YY", "DD/MM/YYYY", "MM/DD/YYYY"])
995
- .optional()
996
- .describe("Override auto-detected date ordering / year shape. Same enum as `whatsapp-export-parse`. Omit unless the operator confirms a US-locale export or a manually concatenated multi-locale archive."),
997
- }, async ({ filePath, timezone, dateFormat }) => {
998
- try {
999
- const result = await whatsappExportPreview({
1000
- filePath,
1001
- accountId,
1002
- timezone,
1003
- dateFormat,
1004
- });
1005
- return {
1006
- content: [{
1007
- type: "text",
1008
- text: JSON.stringify(result),
1009
- }],
1010
- };
1011
- }
1012
- catch (err) {
1013
- return {
1014
- content: [{
1015
- type: "text",
1016
- text: `whatsapp-export-preview failed: ${err instanceof Error ? err.message : String(err)}`,
1017
- }],
1018
- isError: true,
1019
- };
1020
- }
1021
- });
1022
- server.tool("whatsapp-export-insight-pass", "Phase 2 chunked-Haiku insight extraction over an already-loaded WhatsApp Conversation (Task 871). " +
1023
- "Walks the Messages of `:Conversation:WhatsAppConversation {conversationId}` in chronological order, " +
1024
- "chunks them at 50 messages with 5-message overlap, runs Haiku per chunk, and MERGE-keys " +
1025
- "`:Observation` nodes (kind ∈ {mention, task, preference, observed-relationship}) connected " +
1026
- "`:OBSERVED_IN`→Conversation. Server-side gates: confidence>=0.8 per item (lower-confidence items rejected before write). " +
1027
- "Idempotent — re-running collapses identical (conversationId, sourceMessageRef, kind, contentHash) into the same row. " +
1028
- "Phase 1 (`whatsapp-ingest.sh`) writes ZERO observations; this tool is the only sanctioned LLM entry for " +
1029
- "WhatsApp ingest. Operator triggers consciously via the `whatsapp-import-enrich` skill — never automatic on Phase 1 completion.", {
1030
- conversationId: z
1031
- .string()
1032
- .min(1)
1033
- .describe("conversationId of the already-loaded :Conversation:WhatsAppConversation. Phase 1 must have completed (`c.lastImportedAt IS NOT NULL`)."),
1034
- sessionId: z
1035
- .string()
1036
- .optional()
1037
- .describe("Skill-run UUID for provenance. Falls back to SESSION_ID env var when absent."),
1038
- }, async (params) => {
1039
- try {
1040
- const result = await whatsappExportInsightPass({
1041
- conversationId: params.conversationId,
1042
- accountId,
1043
- sessionId: resolveSessionId(params.sessionId),
1044
- });
1045
- return {
1046
- content: [{
1047
- type: "text",
1048
- text: JSON.stringify(result),
1049
- }],
1050
- };
1051
- }
1052
- catch (err) {
1053
- return {
1054
- content: [{
1055
- type: "text",
1056
- text: `whatsapp-export-insight-pass failed: ${err instanceof Error ? err.message : String(err)}`,
1057
- }],
1058
- isError: true,
1059
- };
1060
- }
1061
- });
1062
829
  server.tool("memory-ingest-web", "Adapter for web-content ingestion (Task 737). Accepts a URL and its pre-fetched readable content " +
1063
830
  "(the agent calls WebFetch first, then passes the text here), writes content to a temp file, and delegates " +
1064
831
  "to memory-ingest-extract — caching the text under a freshly-generated attachmentId. The skill then drives " +
@@ -1587,7 +1354,7 @@ if (!readOnly) {
1587
1354
  await dbSession.close();
1588
1355
  }
1589
1356
  });
1590
- server.tool("profile-update", "Create, update, or reinforce a user preference; set top-level UserProfile fields (timezone, locale, givenName, role, expertise) via profileFields; AND set operator-identity fields (email, telephone) on the operator's personal-profile Person via personFields (Task 886 §D — Person is the canonical operator-identity store; UserProfile carries preferences and behavioural state only). Supports modes: reinforce (re-observed, boost confidence), update (value changed), contradict (value contradicts prior), merge (combine overlapping preferences).", {
1357
+ server.tool("profile-update", "Create, update, or reinforce a user preference; set top-level UserProfile fields via profileFields; AND set Person properties on the operator's personal-profile Person via personFields. The personal-profile Person is open by default pass any Person properties that help the agent assist this operator effectively (identity, contact, context, anything that makes future assistance more useful). The schema validator enforces rules centrally: synonyms reject (e.g. `phone` → use `telephone`), Forbidden Properties reject (e.g. `name` → use `givenName` + `familyName`), and any other key the agent judges useful is permitted. Writes route to the personal-profile Person via the OWNS edge from the AdminUser — the tool throws loudly if that edge is missing rather than silently no-oping. Supports modes: reinforce (re-observed, boost confidence), update (value changed), contradict (value contradicts prior), merge (combine overlapping preferences).", {
1591
1358
  category: z.enum(["communication", "scheduling", "decision", "workflow", "content", "interaction"])
1592
1359
  .describe("Preference category"),
1593
1360
  key: z.string().describe("Preference identifier (e.g. 'response_length', 'meeting_time')"),
@@ -1600,11 +1367,8 @@ if (!readOnly) {
1600
1367
  .describe("Conversation ID for evidence linking"),
1601
1368
  profileFields: z.record(z.string(), z.unknown()).optional()
1602
1369
  .describe("Top-level UserProfile fields to update (e.g. timezone, locale, role)"),
1603
- personFields: z.object({
1604
- email: z.string().optional(),
1605
- telephone: z.string().optional(),
1606
- }).optional()
1607
- .describe("Operator-identity fields written to the OWNS-bound Person (Task 886 §D). Use canonical `telephone` (NOT `phone` — that is the schema synonym, not the canonical name). Tool throws if the AdminUser-OWNS-Person edge is missing rather than silently no-op."),
1370
+ personFields: z.record(z.string(), z.unknown()).optional()
1371
+ .describe("Person properties to set on the operator's personal-profile Person — open by default; the agent decides which keys best help it serve this operator (identity, contact, context). Synonyms and Forbidden Properties are rejected by the central schema validator with descriptive errors; everything else is permitted."),
1608
1372
  mergeSourceIds: z.array(z.string()).optional()
1609
1373
  .describe("For mode 'merge': preferenceIds of sources to combine into this preference"),
1610
1374
  }, async ({ category, key, value, source, mode, conversationId, profileFields, personFields, mergeSourceIds }) => {
@@ -1622,8 +1386,9 @@ if (!readOnly) {
1622
1386
  mode,
1623
1387
  conversationId,
1624
1388
  profileFields: profileFields,
1625
- personFields,
1389
+ personFields: personFields,
1626
1390
  mergeSourceIds,
1391
+ validator,
1627
1392
  });
1628
1393
  return {
1629
1394
  content: [{