@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.826",
3
+ "version": "1.0.829",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -258,6 +258,39 @@ OPTIONS {
258
258
  }
259
259
  };
260
260
 
261
+ // ----------------------------------------------------------
262
+ // ConversationArchive — chunked WhatsApp/messaging archive (Task 891).
263
+ //
264
+ // :ConversationArchive parent + :Section:Conversation chunks (HAS_SECTION + NEXT chain).
265
+ // MERGE-keyed on conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds).
266
+ // :Section base label is shared with KnowledgeDocument's children — chunks
267
+ // reuse the section_embedding vector index above and the universal fulltext
268
+ // index below.
269
+ //
270
+ // :PARTICIPANT_IN edges from :Person/:AdminUser to :ConversationArchive are
271
+ // MERGEd on every ingest (idempotent); the operator confirms each participant
272
+ // up front via the document-ingest skill's chat-mode participant flow
273
+ // (Task 894 — supersedes the prior whatsapp-import skill).
274
+ // ----------------------------------------------------------
275
+
276
+ CREATE CONSTRAINT conversation_archive_identity_unique IF NOT EXISTS
277
+ FOR (a:ConversationArchive) REQUIRE a.conversationIdentity IS UNIQUE;
278
+
279
+ CREATE INDEX conversation_archive_account IF NOT EXISTS
280
+ FOR (a:ConversationArchive) ON (a.accountId);
281
+
282
+ CREATE INDEX conversation_archive_session IF NOT EXISTS
283
+ FOR (a:ConversationArchive) ON (a.createdBySession);
284
+
285
+ CREATE VECTOR INDEX conversation_archive_embedding IF NOT EXISTS
286
+ FOR (a:ConversationArchive) ON (a.embedding)
287
+ OPTIONS {
288
+ indexConfig: {
289
+ `vector.dimensions`: 768,
290
+ `vector.similarity_function`: 'cosine'
291
+ }
292
+ };
293
+
261
294
  // Universal full-text BM25 index for hybrid keyword search (Task 748).
262
295
  //
263
296
  // Every operator-meaningful label written by the platform is in the index union;
@@ -276,7 +309,7 @@ OPTIONS {
276
309
  // Label union — every operator-meaningful label:
277
310
  // - Business identity: LocalBusiness, Service, PriceSpecification, OpeningHoursSpecification, Organization
278
311
  // - People: Person, UserProfile, Preference, AdminUser, AccessGrant
279
- // - Knowledge: KnowledgeDocument, Section, Chunk (legacy), DigitalDocument, CreativeWork,
312
+ // - Knowledge: KnowledgeDocument, ConversationArchive (Task 891), Section, Chunk (legacy), DigitalDocument, CreativeWork,
280
313
  // Question, FAQPage, DefinedTerm, Review, ImageObject
281
314
  // - Conversational: Conversation, AdminConversation, PublicConversation, Message,
282
315
  // UserMessage, AssistantMessage, ToolCall
@@ -309,7 +342,7 @@ OPTIONS {
309
342
  CREATE FULLTEXT INDEX entity_search IF NOT EXISTS
310
343
  FOR (n:LocalBusiness|Service|PriceSpecification|OpeningHoursSpecification|Organization
311
344
  |Person|UserProfile|Preference|AdminUser|AccessGrant
312
- |KnowledgeDocument|Section|Chunk|DigitalDocument|CreativeWork|Question|FAQPage|DefinedTerm|Review|ImageObject
345
+ |KnowledgeDocument|ConversationArchive|Section|Chunk|DigitalDocument|CreativeWork|Question|FAQPage|DefinedTerm|Review|ImageObject
313
346
  |Conversation|AdminConversation|PublicConversation|Message|UserMessage|AssistantMessage|ToolCall
314
347
  |Task|Project|Event
315
348
  |Workflow|WorkflowStep|WorkflowRun|StepResult
@@ -6,8 +6,8 @@
6
6
  "plugins/*/mcp"
7
7
  ],
8
8
  "scripts": {
9
- "build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p plugins/whatsapp-import/lib/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
10
- "build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p plugins/whatsapp-import/lib/tsconfig.json",
9
+ "build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
10
+ "build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json",
11
11
  "build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
12
12
  "build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
13
13
  "build:telegram": "tsc -p plugins/telegram/mcp/tsconfig.json",
@@ -1,23 +1,30 @@
1
1
  #!/usr/bin/env bash
2
- # Regression test for archive-ingest-surface-gate.sh (Task 855).
2
+ # Regression test for archive-ingest-surface-gate.sh (Task 855, trimmed by Task 894).
3
3
  #
4
- # Covers:
5
- # Preserved-from-Task-846:
6
- # 1. Edit on /platform/plugins/<x>/lib/*BLOCKED
7
- # 2. Edit on benign pathALLOWED
8
- # 3. Bash with `npx vitest`/`bun test`/`npm test` BLOCKED
9
- # 4. PostToolUse on whatsapp-export-parse with isError:true sets flag
10
- # 5. Subsequent PreToolUse on ANY tool BLOCKED
11
- # 6. UserPromptSubmit clears flag normal allow resumes
12
- # 7. PostToolUse with isError:false flag absent
13
- # 8. Stale flag (>600s) auto-clears
14
- # New (Task 855):
15
- # A. PreToolUse mcp__memory__whatsapp-export-parse BLOCKED
16
- # B. PreToolUse mcp__memory__whatsapp-export-insight-write → BLOCKED
17
- # C. PreToolUse mcp__memory__memory-archive-write w/ archiveType=whatsapp-export → BLOCKED
18
- # D. PreToolUse mcp__memory__memory-archive-write w/ archiveType=linkedin-connections → ALLOWED
19
- # E. PreToolUse Bash invoking whatsapp-ingest.sh → ALLOWED
20
- # F. Default-allow emits a [archive-ingest-gate] decision=allow log line
4
+ # Covers (post-Task-894):
5
+ # 1. Edit on /platform/plugins/<x>/lib/* → BLOCKED
6
+ # 2. Edit on benign path ALLOWED
7
+ # 3. Bash with `npx vitest`/`bun test`/`npm test` BLOCKED
8
+ # 4. PreToolUse memory-archive-write w/ archiveType=linkedin-connectionsALLOWED
9
+ # 5. Default-allow emits a [archive-ingest-gate] decision=allow log line
10
+ # 6. Parse-error flag lifecycle: PostToolUse on any *-export-parse / *-import-parse
11
+ # with isError:true sets flag; subsequent PreToolUse blocks; UserPromptSubmit
12
+ # clears; stale flag (>600s) auto-clears; parse-success leaves flag absent.
13
+ # 7. Bypass attempts: nested file_path in old_string + top-level file_path in
14
+ # lib/* BLOCKED.
15
+ # 8. Fail-closed terminal check.
16
+ #
17
+ # Removed by Task 894:
18
+ # - WhatsApp-specific MCP-tool blocks (the three legacy whatsapp-export tools
19
+ # no longer exist; chat archives flow through memory-classify mode='chat'
20
+ # and memory-ingest parentLabel='ConversationArchive' via document-ingest).
21
+ # - memory-archive-write archiveType='whatsapp-export' block (the enum value
22
+ # was dropped from memory-archive-write).
23
+ #
24
+ # A generic LinkedIn-shaped fixture stands in for the parse-error lifecycle —
25
+ # the gate's PostToolUse pattern matches `mcp__*__*-export-parse` /
26
+ # `mcp__*__*-import-parse`, so any plausible parser tool name exercises the
27
+ # flag-set / flag-clear path.
21
28
 
22
29
  set -u
23
30
 
@@ -51,18 +58,20 @@ run_case() {
51
58
  fi
52
59
  }
53
60
 
54
- # Preserved cases ---------------------------------------------------------
61
+ # Plugin-source-edit block ------------------------------------------------
55
62
 
56
- run_case "Edit on platform/plugins/whatsapp-import/lib/src/parse-export.ts → BLOCKED" \
57
- '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/platform/plugins/whatsapp-import/lib/src/parse-export.ts","old_string":"a","new_string":"b"}}' \
63
+ run_case "Edit on platform/plugins/linkedin-import/lib/src/parse.ts → BLOCKED" \
64
+ '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/platform/plugins/linkedin-import/lib/src/parse.ts","old_string":"a","new_string":"b"}}' \
58
65
  2
59
66
 
60
67
  run_case "Edit on README.md → ALLOWED" \
61
68
  '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/README.md","old_string":"a","new_string":"b"}}' \
62
69
  0
63
70
 
71
+ # Test-runner block (Bash) ------------------------------------------------
72
+
64
73
  run_case "Bash 'npx vitest run' → BLOCKED" \
65
- '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npx vitest run parse-export.test.ts"}}' \
74
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npx vitest run x.test.ts"}}' \
66
75
  2
67
76
 
68
77
  run_case "Bash 'ls -la' → ALLOWED" \
@@ -77,45 +86,18 @@ run_case "Bash 'npm test' → BLOCKED" \
77
86
  '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npm test"}}' \
78
87
  2
79
88
 
80
- # New (Task 855) cases ----------------------------------------------------
81
-
82
- run_case "PreToolUse mcp__memory__whatsapp-export-parse → BLOCKED" \
83
- '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"/tmp/_chat.txt","accountId":"acct1","timezone":"Europe/London"}}' \
84
- 2
85
-
86
- run_case "PreToolUse mcp__memory__whatsapp-export-insight-write → BLOCKED" \
87
- '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__whatsapp-export-insight-write","tool_input":{"kind":"MENTIONS","name":"Joel"}}' \
88
- 2
89
-
90
- run_case "PreToolUse memory-archive-write w/ archiveType=whatsapp-export → BLOCKED" \
91
- '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__memory-archive-write","tool_input":{"archiveType":"whatsapp-export","ownerNodeId":"x","accountId":"a","rows":[]}}' \
92
- 2
89
+ # memory-archive-write LinkedIn passes through unchanged ----------------
93
90
 
94
91
  run_case "PreToolUse memory-archive-write w/ archiveType=linkedin-connections → ALLOWED" \
95
92
  '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__memory-archive-write","tool_input":{"archiveType":"linkedin-connections","ownerNodeId":"x","accountId":"a","rows":[]}}' \
96
93
  0
97
94
 
98
- # Bypass attempts (Task 855 code-review C1): nested archiveType in rows[0]
99
- # or conversation must NOT defeat the block. The gate must read the
100
- # top-level tool_input.archiveType, not the first textual occurrence.
101
- run_case "BYPASS: nested rows[0].archiveType=linkedin + top-level archiveType=whatsapp-export → BLOCKED" \
102
- '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__memory-archive-write","tool_input":{"rows":[{"archiveType":"linkedin-connections"}],"archiveType":"whatsapp-export","ownerNodeId":"x","accountId":"a"}}' \
103
- 2
104
-
105
- run_case "BYPASS: conversation.archiveType=linkedin + top-level archiveType=whatsapp-export → BLOCKED" \
106
- '{"hook_event_name":"PreToolUse","tool_name":"mcp__memory__memory-archive-write","tool_input":{"conversation":{"archiveType":"linkedin-connections","conversationId":"x"},"archiveType":"whatsapp-export","ownerNodeId":"x","accountId":"a","rows":[]}}' \
107
- 2
108
-
109
95
  # Plugin-source-edit path block must read tool_input.file_path top-level,
110
96
  # not a nested file_path in old_string/new_string.
111
97
  run_case "BYPASS: nested file_path in old_string + top-level file_path in lib/* → BLOCKED" \
112
- '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/repo/platform/plugins/whatsapp-import/lib/src/parse-export.ts","old_string":"file_path:/safe/path","new_string":"x"}}' \
98
+ '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/repo/platform/plugins/linkedin-import/lib/src/parse.ts","old_string":"file_path:/safe/path","new_string":"x"}}' \
113
99
  2
114
100
 
115
- run_case "PreToolUse Bash invoking whatsapp-ingest.sh → ALLOWED" \
116
- '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh /tmp/chat.zip --owner-element-id 4:abc:1 --subject-person-id 4:abc:2 --scope admin --filter all"}}' \
117
- 0
118
-
119
101
  # Default-allow log-line check
120
102
  LOG=$(printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' | bash "$HOOK" 2>&1 1>/dev/null)
121
103
  if printf '%s' "$LOG" | grep -q '\[archive-ingest-gate\] decision=allow tool=Read reason=default'; then
@@ -136,10 +118,13 @@ else
136
118
  FAIL=$((FAIL + 1))
137
119
  fi
138
120
 
139
- # Parse-error flag lifecycle (preserved)
121
+ # Parse-error flag lifecycle (preserved). The gate's PostToolUse pattern is
122
+ # `mcp__*__*-export-parse` / `mcp__*__*-import-parse` — exercise via the
123
+ # linkedin-import-parse name (synthetic — the linkedin importer currently
124
+ # uses memory-archive-write, but the gate matches by tool name shape).
140
125
  rm -f "$FLAG_FILE"
141
126
  run_case "PostToolUse parse-error sets flag (exit 0)" \
142
- '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":true,"content":[{"type":"text","text":"parse-error file=_chat.txt line=1 reason=not-a-_chat.txt"}]}}' \
127
+ '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__linkedin-import-parse","tool_input":{"filePath":"connections.csv"},"tool_response":{"isError":true,"content":[{"type":"text","text":"parse-error file=connections.csv line=1 reason=missing-header"}]}}' \
143
128
  0
144
129
  [[ -f "$FLAG_FILE" ]] && { echo "PASS: parse-error flag created"; PASS=$((PASS+1)); } \
145
130
  || { echo "FAIL: parse-error flag NOT created" >&2; FAIL=$((FAIL+1)); }
@@ -168,7 +153,7 @@ run_case "Stale flag auto-clears, PreToolUse Read → ALLOWED" \
168
153
  # PostToolUse parse-success leaves flag absent
169
154
  rm -f "$FLAG_FILE"
170
155
  run_case "PostToolUse parse-success (isError:false) does NOT set flag" \
171
- '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":false,"content":[{"type":"text","text":"{\"parsedLines\":[]}"}]}}' \
156
+ '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__linkedin-import-parse","tool_input":{"filePath":"connections.csv"},"tool_response":{"isError":false,"content":[{"type":"text","text":"{\"parsedLines\":[]}"}]}}' \
172
157
  0
173
158
  [[ ! -f "$FLAG_FILE" ]] && { echo "PASS: parse-success leaves flag absent"; PASS=$((PASS+1)); } \
174
159
  || { echo "FAIL: parse-success incorrectly created flag" >&2; FAIL=$((FAIL+1)); }
@@ -1,22 +1,24 @@
1
1
  #!/usr/bin/env bash
2
- # Archive-ingest surface gate (Task 855; supersedes Task 846).
2
+ # Archive-ingest surface gate (Task 855, updated by Task 891, trimmed by Task 894).
3
3
  #
4
- # Five enforcements, one script — phase decided by `hook_event_name` on stdin.
5
- # Task 855 narrows the database-operator subagent's effective surface during
6
- # WhatsApp archive ingestion to exactly one Bash entry
7
- # (`whatsapp-import/bin/whatsapp-ingest.sh`) plus read-only neighbours, by
8
- # blocking the legacy MCP deviation tools mechanically.
4
+ # Three enforcements, one script — phase decided by `hook_event_name` on stdin.
5
+ # Task 855 narrowed the database-operator subagent's effective surface during
6
+ # archive ingestion. Task 894 retired the WhatsApp `_chat.txt` deterministic
7
+ # parser and its three legacy MCP tools entirely — chat archives now flow
8
+ # through the unified `document-ingest` pipeline (`memory-classify` mode='chat'
9
+ # + `memory-ingest` parentLabel='ConversationArchive'). The WhatsApp-specific
10
+ # block lists were removed because the tools they referenced no longer exist
11
+ # and the `archiveType=whatsapp-export` enum value was dropped from
12
+ # `memory-archive-write`. The remaining blocks still cover LinkedIn and any
13
+ # future flat-dataset archive types that ship per-source parsers.
9
14
  #
10
- # 1. PreToolUse on the three legacy WhatsApp MCP tools — BLOCK unconditionally.
11
- # The single deterministic Bash entry (Task 855) is the only supported path
12
- # for `archiveType=whatsapp-export`. The legacy MCP tools' source remains
13
- # until cleanup; the gate stops them being invoked.
14
- # mcp__memory__whatsapp-export-parse
15
- # mcp__memory__whatsapp-export-insight-write
16
- # mcp__memory__memory-archive-write (only when `archiveType` is
17
- # `whatsapp-export`; LinkedIn
18
- # and other archiveTypes pass
19
- # through unchanged.)
15
+ # 1. Parse-error gate: PostToolUse on any `mcp__*__*-export-parse` /
16
+ # `mcp__*__*-import-parse` tool whose `tool_response.isError == true`
17
+ # writes a flag file. Subsequent PreToolUse on ANY tool blocks until
18
+ # UserPromptSubmit clears the flag. A 600s TTL is the cross-session safety
19
+ # net. Preserved from Task 846 because LinkedIn and future per-source archive
20
+ # parsers still use the legacy MCP path until they migrate to their own
21
+ # deterministic Bash entries.
20
22
  #
21
23
  # 2. PreToolUse Edit/Write/NotebookEdit: deny writes under
22
24
  # `*platform/plugins/*/lib/*` (parser/CSV-shape source for any *-import or
@@ -26,18 +28,10 @@
26
28
  # 3. PreToolUse Bash: deny commands invoking JavaScript test runners
27
29
  # (vitest|bun test|npm test|npx jest|node .*vitest). Preserved from Task 846.
28
30
  #
29
- # 4. Parse-error gate: PostToolUse on any `mcp__*__*-export-parse` /
30
- # `mcp__*__*-import-parse` tool whose `tool_response.isError == true`
31
- # writes a flag file. Subsequent PreToolUse on ANY tool blocks until
32
- # UserPromptSubmit clears the flag. A 600s TTL is the cross-session safety
33
- # net. Preserved from Task 846 because LinkedIn and future per-source archive
34
- # parsers still use the legacy MCP path until they migrate to their own
35
- # deterministic Bash entries.
36
- #
37
- # 5. Logging: every PreToolUse decision emits one line in the format
31
+ # 4. Logging: every PreToolUse decision emits one line in the format
38
32
  # [archive-ingest-gate] decision=<allow|block> tool=<name> reason=<r> ...
39
33
  # so the operator can grep the full decision trail for one ingest from
40
- # server.log alongside the [whatsapp-ingest] script lines.
34
+ # server.log.
41
35
  #
42
36
  # Exit codes follow Claude Code hook protocol: 0 = allow, 2 = block (stderr
43
37
  # message shown to the agent). Fail-closed on terminal stdin to match
@@ -134,21 +128,12 @@ if [ -f "$FLAG_FILE" ]; then
134
128
  fi
135
129
  fi
136
130
 
137
- # --- Block 2: legacy WhatsApp MCP tools — denied unconditionally ----------
138
- case "$TOOL_NAME" in
139
- mcp__memory__whatsapp-export-parse|mcp__memory__whatsapp-export-insight-write)
140
- emit_decision "block" "denied-mcp-legacy" \
141
- "Blocked: ${TOOL_NAME} is the Task 804 legacy path. Task 855 ships the deterministic Bash entry — invoke 'bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh <archive> --owner-element-id <id> --subject-person-id <id> --scope <admin|public> --filter <all|senders=<csv>|date-range=<from>..<to>>' once. Parse, archive-write, and insight all run in-process; do not call legacy MCP tools."
142
- ;;
143
- esac
144
-
145
131
  # Helper: extract a top-level field from `tool_input` via python3 — never via
146
132
  # grep+sed against the raw JSON, which would pick the first textual occurrence
147
- # including nested-object matches (`rows[0].archiveType`,
148
- # `conversation.archiveType`, etc.) and let a malicious payload bypass the
149
- # block. python3 is the project standard for JSON-aware hook parsing
150
- # (mirrors lane-gate.sh:31-46). On parse failure return empty string
151
- # downstream block conditions skip cleanly.
133
+ # including nested-object matches (`rows[0].archiveType`, etc.) and let a
134
+ # malicious payload bypass the block. python3 is the project standard for
135
+ # JSON-aware hook parsing (mirrors lane-gate.sh:31-46). On parse failure
136
+ # return empty string downstream block conditions skip cleanly.
152
137
  extract_tool_input_field() {
153
138
  local field="$1"
154
139
  printf '%s' "$INPUT" | python3 -c "
@@ -161,18 +146,7 @@ except Exception:
161
146
  " 2>/dev/null || echo ""
162
147
  }
163
148
 
164
- # --- Block 3: memory-archive-write conditional on whatsapp-export -----------
165
- # LinkedIn and future archiveTypes flow unchanged through memory-archive-write.
166
- # Only `archiveType=whatsapp-export` is now restricted to the script path.
167
- if [ "$TOOL_NAME" = "mcp__memory__memory-archive-write" ]; then
168
- ARCHIVE_TYPE=$(extract_tool_input_field archiveType)
169
- if [ "$ARCHIVE_TYPE" = "whatsapp-export" ]; then
170
- emit_decision "block" "denied-mcp-legacy archiveType=whatsapp-export" \
171
- "Blocked: memory-archive-write with archiveType='whatsapp-export' is the Task 804 legacy path. Task 855 ships the deterministic Bash entry — invoke 'bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh <archive> --owner-element-id <id> --subject-person-id <id> --scope <admin|public> --filter <all|senders=<csv>|date-range=<from>..<to>>' once. Other archiveTypes (linkedin-connections, …) flow through memory-archive-write unchanged."
172
- fi
173
- fi
174
-
175
- # --- Block 4: plugin-source path block (Edit/Write/NotebookEdit) -----------
149
+ # --- Block 2: plugin-source path block (Edit/Write/NotebookEdit) -----------
176
150
  case "$TOOL_NAME" in
177
151
  Edit|Write|NotebookEdit)
178
152
  FILE_PATH=$(extract_tool_input_field file_path)
@@ -185,7 +159,7 @@ case "$TOOL_NAME" in
185
159
  ;;
186
160
  esac
187
161
 
188
- # --- Block 5: shell test-runner block (Bash) -------------------------------
162
+ # --- Block 3: shell test-runner block (Bash) -------------------------------
189
163
  COMMAND=""
190
164
  if [ "$TOOL_NAME" = "Bash" ]; then
191
165
  COMMAND=$(extract_tool_input_field command)
@@ -210,28 +210,28 @@ Pin the operator's persona and bootstrap the graph nodes that satisfy the graph-
210
210
  - `status`: `"running"`
211
211
  - `kind`: `"onboarding-establish-owner"`
212
212
  - `inputsProvided`: the keys you actually pass on `inputs` (e.g. `["mode"]`); records the call shape
213
- - `inputs`: the form payload at minimum `{ mode: "personal" | "business-owner" }`. Add other operator-supplied non-secret fields you want the audit panel to render (e.g. when the user later provides email/phone in step 9 personal mode, you can append a follow-up `task-update` rather than reopening the Task)
213
+ - `inputs`: the form payload known at creation time — `{ mode: "personal" | "business-owner" }`
214
214
  - `inputSchema`: `{ secretFields: [] }` — the step-9 mode select carries no secrets; declare the empty list explicitly so the contract is visible at the call site
215
215
 
216
216
  The returned `taskId` is the action-provenance handle for this step — every subsequent `memory-write` for an action-provenance-gated label (`Person`, `UserProfile`, `AdminUser`, `Organization`, `LocalBusiness`) MUST pass it as `producedByTaskId` so the inbound `:PRODUCED` edge from the Task is composed into the write. The Task is auto-linked to the current `AdminConversation` via `RAISED_DURING` (this is what makes `MATCH (c:AdminConversation)<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(entity)` traversable from the conversation that initiated onboarding).
217
217
 
218
- The `inputs` you pass land on the Task as `inputs.<field>` props after `redactSecrets` strips any secret-tagged keys. The contract is enforced centrallynever re-classify what is or isn't a secret at the call site; declare the form's secret fields once in `inputSchema.secretFields`.
218
+ **Recording what you collect.** The `onboarding-establish-owner` Task is the audit record for step 9. Record every operator-meaningful fact you collect using the existing surfaces — `description` for the running summary, `note` (via `task-update`) for facts as they land (email collected, phone declined, resolved Person/UserProfile elementIds), `appendStep` for phase markers. Agent's judgement decides which slot. Contract: by `task-complete`, a reader of the Task properties can reconstruct what was collected, what was declined, and which entities were touched without consulting any other source. No secrets in any property regardless of slot `task-create`'s `inputs` is centrally redacted via `inputSchema.secretFields`; the post-creation slots carry no schema, so the agent's responsibility is to never write secret material into them.
219
219
 
220
220
  Then branch on the mode.
221
221
 
222
222
  ### `business-owner`
223
223
 
224
- Invoke the `business-profile` skill, passing the `taskId` so the skill can thread it as `producedByTaskId` into every `memory-write` it issues. The skill follows its first-run path: create the `AdminUser` node, create the `LocalBusiness` node, create the `Organization` node, collect identity + address + whichever additional domains (hours, services, FAQs, brand assets) the user provides. When `business-profile` reports that the required nodes exist in the graph, call `task-update(appendStep:"business-profile-complete")` then write the `HAS_PROFILE` edge from the personal-profile `Person` to the operator's `UserProfile` via `memory-update` (one `memory-search` to resolve both elementIds; the `UserProfile` already exists from step 6 onwards via the lazy-create in `loadUserProfile`). Then call `task-complete(taskId)` and `onboarding-complete-step` with step 9. Do not mark step 9 complete before the required nodes + the HAS_PROFILE edge exist — the gate's precondition must be real, not just recorded.
224
+ Invoke the `business-profile` skill, passing the `taskId` so the skill can thread it as `producedByTaskId` into every `memory-write` it issues. The skill follows its first-run path: create the `AdminUser` node, create the `LocalBusiness` node, create the `Organization` node, collect identity + address + whichever additional domains (hours, services, FAQs, brand assets) the user provides. When `business-profile` reports that the required nodes exist in the graph, call `task-update` with both `appendStep:"business-profile-complete"` AND `note:"Business profile complete — AdminUser=<elementId>, LocalBusiness=<elementId>, Organization=<elementId>"` (the resolved elementIds from the skill's writes; one call carries both the phase marker and the audit content). Then write the `HAS_PROFILE` edge from the personal-profile `Person` to the operator's `UserProfile` via `memory-update` (one `memory-search` to resolve both elementIds; the `UserProfile` already exists from step 6 onwards via the lazy-create in `loadUserProfile`). Then call `task-complete(taskId)` and `onboarding-complete-step` with step 9. Do not mark step 9 complete before the required nodes + the HAS_PROFILE edge exist — the gate's precondition must be real, not just recorded.
225
225
 
226
226
  ### `personal`
227
227
 
228
228
  Personal mode does not register a `LocalBusiness`. The `AdminUser` and personal-profile `Person` nodes were written deterministically at PIN setup time (Task 830 — `writeAdminUserAndPerson`, run as `createdBy.agent === 'system'` and therefore exempt from the action-provenance gate), so this step only enriches the existing Person with operator-identity fields and links it to the `UserProfile`:
229
229
 
230
- 1. **Ask the user for their email AND phone number** in one short conversational message {{productName}} stores both on the personal-profile Person for downstream features (notifications, contact-method matching, identity-coverage signal that the agent uses to detect missing identity in future turns). The user may decline either; record what they provide. (Operator-identity fix: the system-prompt's "Identity coverage" block surfaces missing identity fields on every turn, so a partial answer becomes a follow-up prompt rather than a silent gap.)
231
- 2. **Attach the identity to Person.** Call `profile-update` with `personFields: { email: "<value>", telephone: "<value>" }` (omit either key the user declined). The tool resolves the personal-profile Person via `(au:AdminUser {userId:$you})-[:OWNS]->(p:Person)` server-side and writes the canonical `email`/`telephone` fields. Use canonical `telephone` — `phone` is the schema synonym, not the canonical name; `profile-update` rejects `phone` rather than silently rewriting it. The tool throws if the OWNS edge is missing rather than silently no-oping (the PIN-setup path is the only place that edge is created — a missing edge means PIN setup never ran or was rolled back).
232
- 3. **Append the step.** Call `task-update(appendStep:"identity-attached")`.
230
+ 1. **Elicit Person properties that help {{productName}} serve this operator.** Open by default identity (email, phone, names), context (where they live, languages they speak, what they do, who they work for), or anything else the operator volunteers that makes future assistance more useful. The personal-profile Person is comprehensive, not enumerated: ask in one short conversational message for what feels natural to volunteer at first contact, accept whatever the operator offers, do not chase a fixed list. The user may decline any field; record what they provide.
231
+ 2. **Persist via `profile-update.personFields`.** Pass a single object whose keys are the Person properties the operator volunteered, in the operator's volunteered phrasing — the central schema validator handles synonyms (e.g. `phone` rejects with "use telephone") and Forbidden Properties (e.g. `name` rejects with "use givenName + familyName"); the agent does not pre-rewrite. The tool resolves the personal-profile Person via `(au:AdminUser {userId:$you})-[:OWNS]->(p:Person)` server-side and throws loudly if the OWNS edge is missing rather than silently no-oping (the PIN-setup path is the only place that edge is created — a missing edge means PIN setup never ran or was rolled back).
232
+ 3. **Append the step + record the facts.** Call `task-update` with both `appendStep:"identity-attached"` AND `note:"Identity attached — email=<value-or-declined>, telephone=<value-or-declined>"` (use the actual values the operator supplied; for declines write the literal `declined`). One call, both fields. The note carries the operator-meaningful audit content; the step is the phase marker.
233
233
  4. **Link the personal-profile `Person` to the `UserProfile`.** Call `memory-search` to resolve the `UserProfile` elementId for the operator (the lazy `loadUserProfile` write created it on the first admin session). Then call `memory-update` on the Person to add the `HAS_PROFILE` edge to the UserProfile. (`HAS_PROFILE` from `:Person` is a sibling pattern to the existing `AdminUser→HAS_PROFILE→UserProfile`; both are valid sources for the same edge type. See [schema-base.md Relationship Patterns](../../../memory/references/schema-base.md).)
234
- 5. **Close the action record.** Call `task-update(appendStep:"profile-linked")` then `task-complete(taskId)`.
234
+ 5. **Close the action record.** Call `task-update` with both `appendStep:"profile-linked"` AND `note:"Profile linked — Person=<elementId>, UserProfile=<elementId>"` (the resolved elementIds from steps 2 and 4). Then call `task-complete(taskId)`.
235
235
  6. **Mark step 9 complete.** Call `onboarding-complete-step` with step 9.
236
236
 
237
237
  After step 9 completes in personal mode, tell the user that {{productName}} is configured for personal use — their employer (if any) is not registered here. If they later become the operator for a business of their own, they can ask {{productName}} to set up a business profile, which invokes the `business-profile` skill directly.
@@ -22,7 +22,7 @@ Ask the agent to set up Cloudflare. The agent first confirms the domain is alrea
22
22
  - **Proxy apex** — optional bare-domain hostname (e.g. `yourdomain.com`) that should also serve the public agent.
23
23
  - **Admin password** — the password used to gate remote access to the admin surface.
24
24
 
25
- When you submit, the `/api/admin/cloudflare/setup` endpoint runs — in strict order — `setRemotePassword`, launches a `cloudflare-setup` action (earlier platform fixes: `systemd-run --user` transient unit wrapping `setup-tunnel.sh <brand> <port> <hostname...>`), and registers a post-exit handler to write alias-domains for every non-`public.*` public or apex hostname (so e.g. `chat.yourdomain.com` is classified as public by `isPublicHost`). The script runs end-to-end:
25
+ When you submit, the `/api/admin/cloudflare/setup` endpoint runs — in strict order — `setRemotePassword`, launches a `cloudflare-setup` action (earlier platform fixes: `systemd-run --user` transient unit wrapping `setup-tunnel.sh <brand> <port> <hostname...>`), and registers a post-exit handler that writes alias-domains for every non-`public.*` public or apex hostname (so e.g. `chat.yourdomain.com` is classified as public by `isPublicHost`) and closes the audit Task. When the post-exit handler observes the systemd-run unit gone before its terminal status was sampled (a known systemd-run cgroup race), it consults `tunnel.state` on disk and verifies the tunnel identity matches the form you submitted; if so, the audit Task closes `completed` despite the missed observation. Otherwise it closes `failed` with `endpoint-died-pre-reconcile` and the boot reconciler may still recover the Task at next restart. The script runs end-to-end:
26
26
 
27
27
  - `cloudflared tunnel login` — OAuth browser sign-in. The VNC browser opens the Cloudflare authorize page; pick the account that owns your domain, click Authorize. `cert.pem` lands.
28
28
  - Tunnel resolution from operator-supplied identity (operator-selected-tunnel fix). The form populates a tunnel-select dropdown from `GET /api/admin/cloudflare/tunnels` (which calls `cloudflared tunnel list --output json` on your logged-in account). You either pick an existing tunnel from the list or type a name to create a new one. The form posts EXACTLY ONE of `{tunnelId, tunnelName}`; the script enforces the same constraint as defence in depth. Pre-fix the script derived `${BRAND}-$(hostname -s)` locally; that broke the operator-state-is-authoritative doctrine and silently created orphan tunnels whenever the device hostname changed. Stream log emits `step=tunnel-resolve source=operator-selected|operator-created tunnel_id=… tunnel_name=…` once the UUID is known.
@@ -40,7 +40,7 @@ These are enabled during onboarding and can be added or removed at any time. Som
40
40
  | `waitlist` | Waitlist lifecycle — extract sign-ups from conversations, review | — |
41
41
  | `replicate` | Image generation — three models for photorealistic, design, and fast draft images | Content producer, Research assistant |
42
42
  | `linkedin-import` | Import a LinkedIn Basic Data Export — Profile and Connections today, more CSVs as references land | Database operator |
43
- | `whatsapp-import` | Import a WhatsApp `_chat.txt` export. Two-phase contract: **Phase 1 (load)** is a single Bash entry — `bash platform/plugins/whatsapp-import/bin/whatsapp-ingest.sh <archive> --owner-element-id <id> --subject-person-id <id> --scope <admin\|public> --filter <chosen>` runs parse → operator-supplied filter → archive-write in one process (LLM-FREE), landing `:Conversation:WhatsAppConversation` + `:Message:WhatsAppMessage` with NEXT chain. The writer is bound to the operator-confirmed `{owner, subject}` `:Person` pair (bound-pair sender contract): any parsed senderName outside the closed canonical-name set LOUD-FAILs with `parser-miss`; the script does not auto-create participants. **Phase 2 (enrich)** is operator-driven: ask "enrich the X chat" / "wire observations from yesterday's import" and the database-operator runs the `whatsapp-import-enrich` skill runs the chunked Haiku insight pass `mcp__memory__whatsapp-export-insight-pass` (parallel cap=8 chunks at a time, ≤90s wall for a 1700-message DM), then walks the auto-extracted observations row-by-row and writes operator-confirmed wiring (`:MENTIONS`/`:RELATED_TO` edges with evidence, `:Task` and `:Preference` nodes). Idempotent re-running surfaces only items still in `observationStatus='auto-extracted'`. Distinct from the live `whatsapp` plugin which is a Baileys QR-pairing channel. | Database operator |
43
+ | WhatsApp `_chat.txt` archive | (No dedicated plugin.) Chat archives are document-shaped narrative content and route through the unified `document-ingest` skill with `mode='chat'` and `parentLabel='ConversationArchive'`. The skill confirms participants up front (owner + others, no auto-creation), computes `archiveSha256` for cleanup-on-re-ingest discipline, and lets `memory-classify`'s chat prompt produce `:Section:Conversation` chunks bounded by topic transitions. Distinct from the live `whatsapp` plugin (Baileys QR-pairing channel). | Database operator |
44
44
 
45
45
  ### Claude Official (marketplace)
46
46
 
@@ -207,6 +207,7 @@ Failure modes:
207
207
  - `[client-event] kind=post-restart-resume phase=start` absent: form's CustomEvent never reached the chat hook (regression in form `onExit` or chat-listener mount).
208
208
  - `[client-event] phase=start` present, `[admin-resume] reason=post-restart` absent: `waitForRestartCycle` exhausted its bound (brand never restarted) or `/resume` rejected — check `phase=health-timeout` / `phase=resume-rejected`.
209
209
  - All four present except `[persist] role=user … Cloudflare setup completed`: marker chat POST failed — check the chat surface for an inline error or a `phase=resume-error` line.
210
+ - Refresh after a successful Cloudflare setup shows a visible `Cloudflare setup completed (actionId: …)` user bubble: the resume mappers' synthetic-marker suppression broke or the marker shape drifted. Server-side `[admin-resume] syntheticHidden=<n>` field on the same line counts user rows the client should hide (`_componentDone` envelopes, `_lifecycle` envelopes, and the `Cloudflare setup completed (actionId: …)` literal) — a count of 0 against a refresh that should have hidden one means the helper at `platform/ui/app/lib/synthetic-marker.ts` no longer recognises the producing literal (check `CloudflareSetupForm.tsx` and `useAdminChat.ts` for shape drift).
210
211
 
211
212
  ---
212
213
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memory
3
- description: "Graph memory plugin. Provides memory-search, memory-rank, memory-write, and memory-update tools for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses."
3
+ description: "Graph memory plugin. Provides memory-search, memory-rank, memory-write, and memory-update tools for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion (memory-classify + memory-ingest) supports two modes: `document` (default) for unstructured PDF/web content → KnowledgeDocument + Section, and `chat` for chat archives → ConversationArchive + Section:Conversation chunks (the chunked-archive contract)."
4
4
  tools:
5
5
  - memory-search
6
6
  - memory-rank
@@ -20,8 +20,6 @@ tools:
20
20
  - memory-edit-attachment
21
21
  - memory-rename-attachment
22
22
  - memory-archive-write
23
- - whatsapp-export-parse
24
- - whatsapp-export-insight-write
25
23
  - conversation-list
26
24
  - conversation-search
27
25
  - profile-read
@@ -85,9 +83,9 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
85
83
 
86
84
  The owner's profile and preferences accumulate organically from conversation — never from questionnaires. Load the conversational memory skill via `plugin-read` for full guidance on when to observe preferences, how to handle remember/forget requests, and how to answer "what do you know about me?" transparently with confidence scores and evidence trail.
87
85
 
88
- ## UserProfile Field Management
86
+ ## UserProfile + Person Field Management
89
87
 
90
- `profile-update` serves two purposes: managing Preference nodes (category/key/value) and setting top-level UserProfile node properties via the `profileFields` parameter.
88
+ `profile-update` serves three purposes: managing Preference nodes (category/key/value), setting top-level UserProfile node properties via `profileFields`, and setting Person properties on the operator's personal-profile Person via `personFields`.
91
89
 
92
90
  Settable UserProfile fields: `timezone` (IANA format, e.g. `Europe/London`), `locale`, `givenName`, `role`, `expertise`. These are account metadata stored directly on the UserProfile node — distinct from Preference nodes, which represent learned behavioural patterns.
93
91
 
@@ -98,6 +96,8 @@ Example — setting a user's timezone:
98
96
 
99
97
  Restricted fields (`accountId`, `embedding`, `profileVersion`) cannot be set via `profileFields`.
100
98
 
99
+ **Personal-profile Person via `personFields`.** The personal-profile Person is open by default — the agent decides which Person properties best help it serve this operator (identity, contact, context); the central schema validator enforces synonym + Forbidden Properties rejection (per `references/schema-base.md`). Writes route to the personal-profile Person via the OWNS edge from the AdminUser. Validation runs in `mode: "update"` so the required-property loop is skipped on partial SET (givenName/familyName were established at PIN setup). Pass any Person properties the operator volunteers; the validator surfaces synonym (e.g. `phone`) and forbidden (e.g. `name`) errors with descriptive messages. No allow-list, no enumeration — see the schema-base.md Person row notes for the doctrine.
100
+
101
101
  ## Schema References
102
102
 
103
103
  Before any structured write, load `references/schema-base.md` via `plugin-read`. This defines property naming rules, required-property groups for documented types, forbidden-property rules, and relationship patterns. If the `LocalBusiness` node has a `businessType` property, also load the matching vertical schema (`references/schema-{businessType}.md`) — it extends the base with vertical-specific types. Confirm which schemas were consulted before writing.