@prmichaelsen/remember-mcp 3.0.0 → 3.12.0

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 (208) hide show
  1. package/AGENT.md +296 -250
  2. package/CHANGELOG.md +338 -0
  3. package/README.md +68 -45
  4. package/agent/commands/acp.clarification-create.md +382 -0
  5. package/agent/commands/acp.project-info.md +309 -0
  6. package/agent/commands/acp.project-remove.md +379 -0
  7. package/agent/commands/acp.project-update.md +296 -0
  8. package/agent/commands/acp.task-create.md +17 -9
  9. package/agent/commands/git.commit.md +13 -1
  10. package/agent/design/comment-memory-type.md +2 -2
  11. package/agent/design/local.collaborative-memory-sync.md +265 -0
  12. package/agent/design/local.content-flags.md +210 -0
  13. package/agent/design/local.ghost-persona-system.md +273 -0
  14. package/agent/design/local.group-acl-integration.md +338 -0
  15. package/agent/design/local.memory-acl-schema.md +352 -0
  16. package/agent/design/local.memory-collection-pattern-v2.md +348 -0
  17. package/agent/design/local.moderation-and-space-config.md +257 -0
  18. package/agent/design/local.v2-api-reference.md +621 -0
  19. package/agent/design/local.v2-migration-guide.md +191 -0
  20. package/agent/design/local.v2-usage-examples.md +265 -0
  21. package/agent/design/permissions-storage-architecture.md +11 -3
  22. package/agent/design/trust-escalation-prevention.md +9 -2
  23. package/agent/design/trust-system-implementation.md +12 -3
  24. package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
  25. package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
  26. package/agent/progress.yaml +628 -49
  27. package/agent/scripts/acp.common.sh +2 -0
  28. package/agent/scripts/acp.install.sh +11 -1
  29. package/agent/scripts/acp.package-install-optimized.sh +454 -0
  30. package/agent/scripts/acp.package-install.sh +247 -300
  31. package/agent/scripts/acp.project-info.sh +218 -0
  32. package/agent/scripts/acp.project-remove.sh +302 -0
  33. package/agent/scripts/acp.project-update.sh +296 -0
  34. package/agent/scripts/acp.yaml-parser.sh +128 -10
  35. package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
  36. package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
  37. package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
  38. package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
  39. package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
  40. package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
  41. package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
  42. package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
  43. package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
  44. package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
  45. package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
  46. package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
  47. package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
  48. package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
  49. package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
  50. package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
  51. package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
  52. package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
  53. package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
  54. package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
  55. package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
  56. package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
  57. package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
  58. package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
  59. package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
  60. package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
  61. package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
  62. package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
  63. package/dist/collections/composite-ids.d.ts +106 -0
  64. package/dist/collections/core-infrastructure.spec.d.ts +11 -0
  65. package/dist/collections/dot-notation.d.ts +106 -0
  66. package/dist/collections/tracking-arrays.d.ts +176 -0
  67. package/dist/constants/content-types.d.ts +1 -0
  68. package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
  69. package/dist/schema/v2-collections.d.ts +210 -0
  70. package/dist/server-factory.d.ts +15 -0
  71. package/dist/server-factory.js +2798 -1029
  72. package/dist/server.js +2526 -1012
  73. package/dist/services/access-control.d.ts +103 -0
  74. package/dist/services/access-control.spec.d.ts +2 -0
  75. package/dist/services/credentials-provider.d.ts +24 -0
  76. package/dist/services/credentials-provider.spec.d.ts +2 -0
  77. package/dist/services/escalation.service.d.ts +22 -0
  78. package/dist/services/escalation.service.spec.d.ts +2 -0
  79. package/dist/services/ghost-config.service.d.ts +55 -0
  80. package/dist/services/ghost-config.service.spec.d.ts +2 -0
  81. package/dist/services/space-config.service.d.ts +23 -0
  82. package/dist/services/space-config.service.spec.d.ts +2 -0
  83. package/dist/services/trust-enforcement.d.ts +83 -0
  84. package/dist/services/trust-enforcement.spec.d.ts +2 -0
  85. package/dist/services/trust-validator.d.ts +43 -0
  86. package/dist/services/trust-validator.spec.d.ts +2 -0
  87. package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
  88. package/dist/tools/confirm.d.ts +8 -1
  89. package/dist/tools/create-memory.d.ts +2 -1
  90. package/dist/tools/create-memory.spec.d.ts +10 -0
  91. package/dist/tools/create-relationship.d.ts +2 -1
  92. package/dist/tools/delete-memory.d.ts +2 -1
  93. package/dist/tools/delete-relationship.d.ts +2 -1
  94. package/dist/tools/deny.d.ts +2 -1
  95. package/dist/tools/find-similar.d.ts +2 -1
  96. package/dist/tools/get-preferences.d.ts +2 -1
  97. package/dist/tools/ghost-config.d.ts +27 -0
  98. package/dist/tools/ghost-config.spec.d.ts +2 -0
  99. package/dist/tools/moderate.d.ts +20 -0
  100. package/dist/tools/moderate.spec.d.ts +5 -0
  101. package/dist/tools/publish.d.ts +11 -3
  102. package/dist/tools/query-memory.d.ts +3 -1
  103. package/dist/tools/query-space.d.ts +4 -1
  104. package/dist/tools/retract.d.ts +29 -0
  105. package/dist/tools/revise.d.ts +45 -0
  106. package/dist/tools/revise.spec.d.ts +8 -0
  107. package/dist/tools/search-memory.d.ts +2 -1
  108. package/dist/tools/search-relationship.d.ts +2 -1
  109. package/dist/tools/search-space.d.ts +25 -5
  110. package/dist/tools/search-space.spec.d.ts +9 -0
  111. package/dist/tools/set-preference.d.ts +2 -1
  112. package/dist/tools/update-memory.d.ts +2 -1
  113. package/dist/tools/update-relationship.d.ts +2 -1
  114. package/dist/types/access-result.d.ts +48 -0
  115. package/dist/types/access-result.spec.d.ts +2 -0
  116. package/dist/types/auth.d.ts +46 -0
  117. package/dist/types/ghost-config.d.ts +36 -0
  118. package/dist/types/memory.d.ts +3 -1
  119. package/dist/types/preferences.d.ts +1 -1
  120. package/dist/utils/auth-helpers.d.ts +14 -0
  121. package/dist/utils/auth-helpers.spec.d.ts +2 -0
  122. package/dist/utils/test-data-generator.d.ts +124 -0
  123. package/dist/utils/test-data-generator.spec.d.ts +12 -0
  124. package/dist/v2-performance.e2e.d.ts +17 -0
  125. package/dist/v2-smoke.e2e.d.ts +14 -0
  126. package/dist/weaviate/client.d.ts +5 -8
  127. package/dist/weaviate/space-schema.d.ts +2 -2
  128. package/docs/performance/v2-benchmarks.md +80 -0
  129. package/jest.e2e.config.js +14 -3
  130. package/package.json +1 -1
  131. package/scripts/.collection-recreation-state.yaml +16 -0
  132. package/scripts/.gitkeep +5 -0
  133. package/scripts/README-collection-recreation.md +224 -0
  134. package/scripts/README.md +51 -0
  135. package/scripts/backup-collections.ts +543 -0
  136. package/scripts/delete-collection.ts +137 -0
  137. package/scripts/migrate-recreate-collections.ts +578 -0
  138. package/scripts/migrate-v1-to-v2.ts +1094 -0
  139. package/scripts/package-lock.json +1113 -0
  140. package/scripts/package.json +27 -0
  141. package/src/collections/composite-ids.ts +193 -0
  142. package/src/collections/core-infrastructure.spec.ts +353 -0
  143. package/src/collections/dot-notation.ts +212 -0
  144. package/src/collections/tracking-arrays.ts +298 -0
  145. package/src/constants/content-types.ts +20 -0
  146. package/src/schema/v2-collections-comments.spec.ts +141 -0
  147. package/src/schema/v2-collections.ts +433 -0
  148. package/src/server-factory.ts +89 -20
  149. package/src/server.ts +45 -17
  150. package/src/services/access-control.spec.ts +383 -0
  151. package/src/services/access-control.ts +291 -0
  152. package/src/services/credentials-provider.spec.ts +22 -0
  153. package/src/services/credentials-provider.ts +34 -0
  154. package/src/services/escalation.service.spec.ts +183 -0
  155. package/src/services/escalation.service.ts +150 -0
  156. package/src/services/ghost-config.service.spec.ts +339 -0
  157. package/src/services/ghost-config.service.ts +219 -0
  158. package/src/services/space-config.service.spec.ts +102 -0
  159. package/src/services/space-config.service.ts +79 -0
  160. package/src/services/trust-enforcement.spec.ts +309 -0
  161. package/src/services/trust-enforcement.ts +197 -0
  162. package/src/services/trust-validator.spec.ts +108 -0
  163. package/src/services/trust-validator.ts +105 -0
  164. package/src/tools/confirm-publish-moderation.spec.ts +240 -0
  165. package/src/tools/confirm.ts +869 -135
  166. package/src/tools/create-memory.spec.ts +126 -0
  167. package/src/tools/create-memory.ts +20 -27
  168. package/src/tools/create-relationship.ts +17 -8
  169. package/src/tools/delete-memory.ts +13 -6
  170. package/src/tools/delete-relationship.ts +15 -6
  171. package/src/tools/deny.ts +8 -1
  172. package/src/tools/find-similar.ts +21 -8
  173. package/src/tools/get-preferences.ts +10 -1
  174. package/src/tools/ghost-config.spec.ts +180 -0
  175. package/src/tools/ghost-config.ts +230 -0
  176. package/src/tools/moderate.spec.ts +277 -0
  177. package/src/tools/moderate.ts +219 -0
  178. package/src/tools/publish.ts +99 -41
  179. package/src/tools/query-memory.ts +28 -6
  180. package/src/tools/query-space.ts +39 -4
  181. package/src/tools/retract.ts +292 -0
  182. package/src/tools/revise.spec.ts +146 -0
  183. package/src/tools/revise.ts +283 -0
  184. package/src/tools/search-memory.ts +30 -7
  185. package/src/tools/search-relationship.ts +11 -2
  186. package/src/tools/search-space.spec.ts +341 -0
  187. package/src/tools/search-space.ts +323 -99
  188. package/src/tools/set-preference.ts +10 -1
  189. package/src/tools/update-memory.ts +16 -5
  190. package/src/tools/update-relationship.ts +10 -1
  191. package/src/types/access-result.spec.ts +193 -0
  192. package/src/types/access-result.ts +62 -0
  193. package/src/types/auth.ts +52 -0
  194. package/src/types/ghost-config.ts +46 -0
  195. package/src/types/memory.ts +9 -1
  196. package/src/types/preferences.ts +2 -2
  197. package/src/utils/auth-helpers.spec.ts +75 -0
  198. package/src/utils/auth-helpers.ts +25 -0
  199. package/src/utils/test-data-generator.spec.ts +317 -0
  200. package/src/utils/test-data-generator.ts +292 -0
  201. package/src/utils/weaviate-filters.ts +4 -4
  202. package/src/v2-performance.e2e.ts +173 -0
  203. package/src/v2-smoke.e2e.ts +401 -0
  204. package/src/weaviate/client.spec.ts +5 -5
  205. package/src/weaviate/client.ts +51 -36
  206. package/src/weaviate/schema.ts +11 -256
  207. package/src/weaviate/space-schema.spec.ts +24 -24
  208. package/src/weaviate/space-schema.ts +18 -6
@@ -0,0 +1,180 @@
1
+ import { ghostConfigTool, handleGhostConfig } from './ghost-config.js';
2
+ import * as ghostConfigService from '../services/ghost-config.service';
3
+ import { DEFAULT_GHOST_CONFIG } from '../types/ghost-config.js';
4
+
5
+ jest.mock('../services/ghost-config.service', () => ({
6
+ getGhostConfig: jest.fn(),
7
+ setGhostConfigFields: jest.fn(),
8
+ setUserTrust: jest.fn(),
9
+ removeUserTrust: jest.fn(),
10
+ blockUser: jest.fn(),
11
+ unblockUser: jest.fn(),
12
+ validateGhostConfigUpdate: jest.fn(),
13
+ }));
14
+
15
+ const mockGetGhostConfig = ghostConfigService.getGhostConfig as jest.MockedFunction<typeof ghostConfigService.getGhostConfig>;
16
+ const mockSetGhostConfigFields = ghostConfigService.setGhostConfigFields as jest.MockedFunction<typeof ghostConfigService.setGhostConfigFields>;
17
+ const mockSetUserTrust = ghostConfigService.setUserTrust as jest.MockedFunction<typeof ghostConfigService.setUserTrust>;
18
+ const mockRemoveUserTrust = ghostConfigService.removeUserTrust as jest.MockedFunction<typeof ghostConfigService.removeUserTrust>;
19
+ const mockBlockUser = ghostConfigService.blockUser as jest.MockedFunction<typeof ghostConfigService.blockUser>;
20
+ const mockUnblockUser = ghostConfigService.unblockUser as jest.MockedFunction<typeof ghostConfigService.unblockUser>;
21
+ const mockValidate = ghostConfigService.validateGhostConfigUpdate as jest.MockedFunction<typeof ghostConfigService.validateGhostConfigUpdate>;
22
+
23
+ describe('remember_ghost_config', () => {
24
+ const userId = 'test-user-1';
25
+
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ });
29
+
30
+ describe('tool definition', () => {
31
+ it('has correct name', () => {
32
+ expect(ghostConfigTool.name).toBe('remember_ghost_config');
33
+ });
34
+
35
+ it('requires action parameter', () => {
36
+ expect(ghostConfigTool.inputSchema.required).toContain('action');
37
+ });
38
+
39
+ it('has all action options', () => {
40
+ const actionProp = (ghostConfigTool.inputSchema.properties as any).action;
41
+ expect(actionProp.enum).toEqual(['get', 'set', 'set_trust', 'remove_trust', 'block', 'unblock']);
42
+ });
43
+ });
44
+
45
+ describe('get action', () => {
46
+ it('returns current ghost config', async () => {
47
+ mockGetGhostConfig.mockResolvedValue({ ...DEFAULT_GHOST_CONFIG });
48
+
49
+ const result = await handleGhostConfig({ action: 'get' }, userId);
50
+ const parsed = JSON.parse(result);
51
+
52
+ expect(parsed.success).toBe(true);
53
+ expect(parsed.config).toEqual(DEFAULT_GHOST_CONFIG);
54
+ expect(parsed.trust_tier_guide).toBeDefined();
55
+ });
56
+ });
57
+
58
+ describe('set action', () => {
59
+ it('updates ghost config', async () => {
60
+ const updated = { ...DEFAULT_GHOST_CONFIG, enabled: true };
61
+ mockSetGhostConfigFields.mockResolvedValue(updated);
62
+
63
+ const result = await handleGhostConfig({ action: 'set', enabled: true }, userId);
64
+ const parsed = JSON.parse(result);
65
+
66
+ expect(parsed.success).toBe(true);
67
+ expect(parsed.updated_fields).toContain('enabled');
68
+ expect(mockValidate).toHaveBeenCalled();
69
+ expect(mockSetGhostConfigFields).toHaveBeenCalledWith(userId, { enabled: true });
70
+ });
71
+
72
+ it('throws when no fields provided', async () => {
73
+ await expect(handleGhostConfig({ action: 'set' }, userId))
74
+ .rejects.toThrow('No fields to update');
75
+ });
76
+
77
+ it('updates multiple fields', async () => {
78
+ const updated = { ...DEFAULT_GHOST_CONFIG, enabled: true, default_friend_trust: 0.5 };
79
+ mockSetGhostConfigFields.mockResolvedValue(updated);
80
+
81
+ const result = await handleGhostConfig({
82
+ action: 'set',
83
+ enabled: true,
84
+ default_friend_trust: 0.5,
85
+ }, userId);
86
+ const parsed = JSON.parse(result);
87
+
88
+ expect(parsed.updated_fields).toHaveLength(2);
89
+ });
90
+ });
91
+
92
+ describe('set_trust action', () => {
93
+ it('sets per-user trust', async () => {
94
+ mockSetUserTrust.mockResolvedValue(undefined);
95
+
96
+ const result = await handleGhostConfig({
97
+ action: 'set_trust',
98
+ target_user_id: 'friend-1',
99
+ trust_level: 0.75,
100
+ }, userId);
101
+ const parsed = JSON.parse(result);
102
+
103
+ expect(parsed.success).toBe(true);
104
+ expect(parsed.trust_level).toBe(0.75);
105
+ expect(mockSetUserTrust).toHaveBeenCalledWith(userId, 'friend-1', 0.75);
106
+ });
107
+
108
+ it('throws without target_user_id', async () => {
109
+ await expect(handleGhostConfig({ action: 'set_trust', trust_level: 0.5 }, userId))
110
+ .rejects.toThrow('target_user_id is required');
111
+ });
112
+
113
+ it('throws without trust_level', async () => {
114
+ await expect(handleGhostConfig({ action: 'set_trust', target_user_id: 'friend-1' }, userId))
115
+ .rejects.toThrow('trust_level is required');
116
+ });
117
+ });
118
+
119
+ describe('remove_trust action', () => {
120
+ it('removes per-user trust override', async () => {
121
+ mockRemoveUserTrust.mockResolvedValue(undefined);
122
+
123
+ const result = await handleGhostConfig({
124
+ action: 'remove_trust',
125
+ target_user_id: 'friend-1',
126
+ }, userId);
127
+ const parsed = JSON.parse(result);
128
+
129
+ expect(parsed.success).toBe(true);
130
+ expect(mockRemoveUserTrust).toHaveBeenCalledWith(userId, 'friend-1');
131
+ });
132
+
133
+ it('throws without target_user_id', async () => {
134
+ await expect(handleGhostConfig({ action: 'remove_trust' }, userId))
135
+ .rejects.toThrow('target_user_id is required');
136
+ });
137
+ });
138
+
139
+ describe('block action', () => {
140
+ it('blocks a user', async () => {
141
+ mockBlockUser.mockResolvedValue(undefined);
142
+
143
+ const result = await handleGhostConfig({
144
+ action: 'block',
145
+ target_user_id: 'bad-user',
146
+ }, userId);
147
+ const parsed = JSON.parse(result);
148
+
149
+ expect(parsed.success).toBe(true);
150
+ expect(mockBlockUser).toHaveBeenCalledWith(userId, 'bad-user');
151
+ });
152
+
153
+ it('throws without target_user_id', async () => {
154
+ await expect(handleGhostConfig({ action: 'block' }, userId))
155
+ .rejects.toThrow('target_user_id is required');
156
+ });
157
+ });
158
+
159
+ describe('unblock action', () => {
160
+ it('unblocks a user', async () => {
161
+ mockUnblockUser.mockResolvedValue(undefined);
162
+
163
+ const result = await handleGhostConfig({
164
+ action: 'unblock',
165
+ target_user_id: 'bad-user',
166
+ }, userId);
167
+ const parsed = JSON.parse(result);
168
+
169
+ expect(parsed.success).toBe(true);
170
+ expect(mockUnblockUser).toHaveBeenCalledWith(userId, 'bad-user');
171
+ });
172
+ });
173
+
174
+ describe('invalid action', () => {
175
+ it('throws on unknown action', async () => {
176
+ await expect(handleGhostConfig({ action: 'invalid' as any }, userId))
177
+ .rejects.toThrow('Unknown action');
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,230 @@
1
+ /**
2
+ * remember_ghost_config tool
3
+ *
4
+ * Manage ghost/persona configuration: enable/disable ghost,
5
+ * set trust defaults, manage per-user trust, block/unblock users.
6
+ */
7
+
8
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
+ import { handleToolError } from '../utils/error-handler.js';
10
+ import { createDebugLogger } from '../utils/debug.js';
11
+ import type { AuthContext } from '../types/auth.js';
12
+ import type { TrustEnforcementMode } from '../types/ghost-config.js';
13
+ import {
14
+ getGhostConfig,
15
+ setGhostConfigFields,
16
+ setUserTrust,
17
+ removeUserTrust,
18
+ blockUser,
19
+ unblockUser,
20
+ validateGhostConfigUpdate,
21
+ } from '../services/ghost-config.service.js';
22
+
23
+ type GhostConfigAction = 'get' | 'set' | 'set_trust' | 'remove_trust' | 'block' | 'unblock';
24
+
25
+ export const ghostConfigTool: Tool = {
26
+ name: 'remember_ghost_config',
27
+ description: `Manage ghost/persona configuration. Controls who can interact with your ghost and at what trust level.
28
+
29
+ Actions:
30
+ - get: View current ghost configuration
31
+ - set: Update ghost settings (enabled, trust defaults, enforcement mode)
32
+ - set_trust: Set a per-user trust level override (0-1)
33
+ - remove_trust: Remove a per-user trust override (revert to default)
34
+ - block: Block a user from ghost access entirely
35
+ - unblock: Unblock a previously blocked user
36
+
37
+ Trust levels control what information your ghost can share:
38
+ - 0.0: Existence only ("A memory exists about this")
39
+ - 0.25: Metadata only (tags, type, dates — no content)
40
+ - 0.5: Summary only (AI-generated summary, no raw content)
41
+ - 0.75: Partial access (content with sensitive fields redacted)
42
+ - 1.0: Full access (all content revealed)
43
+
44
+ Ghost is disabled by default. Enable it to allow others to chat with your AI representation.`,
45
+ inputSchema: {
46
+ type: 'object',
47
+ properties: {
48
+ action: {
49
+ type: 'string',
50
+ enum: ['get', 'set', 'set_trust', 'remove_trust', 'block', 'unblock'],
51
+ description: 'Action to perform',
52
+ },
53
+ // For 'set' action
54
+ enabled: {
55
+ type: 'boolean',
56
+ description: 'Enable/disable ghost conversations (for "set" action)',
57
+ },
58
+ public_ghost_enabled: {
59
+ type: 'boolean',
60
+ description: 'Allow non-friends to chat with ghost (for "set" action)',
61
+ },
62
+ default_friend_trust: {
63
+ type: 'number',
64
+ description: 'Default trust level for friends (0-1, for "set" action)',
65
+ minimum: 0,
66
+ maximum: 1,
67
+ },
68
+ default_public_trust: {
69
+ type: 'number',
70
+ description: 'Default trust level for strangers (0-1, for "set" action)',
71
+ minimum: 0,
72
+ maximum: 1,
73
+ },
74
+ enforcement_mode: {
75
+ type: 'string',
76
+ enum: ['query', 'prompt', 'hybrid'],
77
+ description: 'Trust enforcement mode (for "set" action). "query" (default) is most secure.',
78
+ },
79
+ // For 'set_trust' / 'remove_trust' / 'block' / 'unblock' actions
80
+ target_user_id: {
81
+ type: 'string',
82
+ description: 'Target user ID (for set_trust, remove_trust, block, unblock)',
83
+ },
84
+ trust_level: {
85
+ type: 'number',
86
+ description: 'Trust level to assign (0-1, for "set_trust" action)',
87
+ minimum: 0,
88
+ maximum: 1,
89
+ },
90
+ },
91
+ required: ['action'],
92
+ },
93
+ };
94
+
95
+ interface GhostConfigArgs {
96
+ action: GhostConfigAction;
97
+ enabled?: boolean;
98
+ public_ghost_enabled?: boolean;
99
+ default_friend_trust?: number;
100
+ default_public_trust?: number;
101
+ enforcement_mode?: TrustEnforcementMode;
102
+ target_user_id?: string;
103
+ trust_level?: number;
104
+ }
105
+
106
+ /**
107
+ * Handle remember_ghost_config tool
108
+ */
109
+ export async function handleGhostConfig(
110
+ args: GhostConfigArgs,
111
+ userId: string,
112
+ authContext?: AuthContext
113
+ ): Promise<string> {
114
+ const debug = createDebugLogger({ tool: 'remember_ghost_config', userId, operation: args.action });
115
+ try {
116
+ debug.info('Tool invoked');
117
+ debug.trace('Arguments', { args });
118
+
119
+ switch (args.action) {
120
+ case 'get': {
121
+ const config = await getGhostConfig(userId);
122
+ return JSON.stringify({
123
+ success: true,
124
+ config,
125
+ trust_tier_guide: {
126
+ '0.0': 'Existence only',
127
+ '0.25': 'Metadata only (default friend trust)',
128
+ '0.5': 'Summary only',
129
+ '0.75': 'Partial access',
130
+ '1.0': 'Full access',
131
+ },
132
+ }, null, 2);
133
+ }
134
+
135
+ case 'set': {
136
+ const updates: any = {};
137
+ if (args.enabled !== undefined) updates.enabled = args.enabled;
138
+ if (args.public_ghost_enabled !== undefined) updates.public_ghost_enabled = args.public_ghost_enabled;
139
+ if (args.default_friend_trust !== undefined) updates.default_friend_trust = args.default_friend_trust;
140
+ if (args.default_public_trust !== undefined) updates.default_public_trust = args.default_public_trust;
141
+ if (args.enforcement_mode !== undefined) updates.enforcement_mode = args.enforcement_mode;
142
+
143
+ if (Object.keys(updates).length === 0) {
144
+ throw new Error('No fields to update. Provide at least one of: enabled, public_ghost_enabled, default_friend_trust, default_public_trust, enforcement_mode');
145
+ }
146
+
147
+ validateGhostConfigUpdate(updates);
148
+ const config = await setGhostConfigFields(userId, updates);
149
+
150
+ return JSON.stringify({
151
+ success: true,
152
+ message: 'Ghost configuration updated',
153
+ updated_fields: Object.keys(updates),
154
+ config,
155
+ }, null, 2);
156
+ }
157
+
158
+ case 'set_trust': {
159
+ if (!args.target_user_id) {
160
+ throw new Error('target_user_id is required for set_trust action');
161
+ }
162
+ if (args.trust_level === undefined) {
163
+ throw new Error('trust_level is required for set_trust action');
164
+ }
165
+
166
+ await setUserTrust(userId, args.target_user_id, args.trust_level);
167
+
168
+ return JSON.stringify({
169
+ success: true,
170
+ message: `Trust level for ${args.target_user_id} set to ${args.trust_level}`,
171
+ target_user_id: args.target_user_id,
172
+ trust_level: args.trust_level,
173
+ }, null, 2);
174
+ }
175
+
176
+ case 'remove_trust': {
177
+ if (!args.target_user_id) {
178
+ throw new Error('target_user_id is required for remove_trust action');
179
+ }
180
+
181
+ await removeUserTrust(userId, args.target_user_id);
182
+
183
+ return JSON.stringify({
184
+ success: true,
185
+ message: `Trust override for ${args.target_user_id} removed (reverted to default)`,
186
+ target_user_id: args.target_user_id,
187
+ }, null, 2);
188
+ }
189
+
190
+ case 'block': {
191
+ if (!args.target_user_id) {
192
+ throw new Error('target_user_id is required for block action');
193
+ }
194
+
195
+ await blockUser(userId, args.target_user_id);
196
+
197
+ return JSON.stringify({
198
+ success: true,
199
+ message: `${args.target_user_id} blocked from ghost access`,
200
+ target_user_id: args.target_user_id,
201
+ }, null, 2);
202
+ }
203
+
204
+ case 'unblock': {
205
+ if (!args.target_user_id) {
206
+ throw new Error('target_user_id is required for unblock action');
207
+ }
208
+
209
+ await unblockUser(userId, args.target_user_id);
210
+
211
+ return JSON.stringify({
212
+ success: true,
213
+ message: `${args.target_user_id} unblocked from ghost access`,
214
+ target_user_id: args.target_user_id,
215
+ }, null, 2);
216
+ }
217
+
218
+ default:
219
+ throw new Error(`Unknown action: ${args.action}. Valid actions: get, set, set_trust, remove_trust, block, unblock`);
220
+ }
221
+ } catch (error) {
222
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
223
+ handleToolError(error, {
224
+ toolName: 'remember_ghost_config',
225
+ operation: args.action,
226
+ userId,
227
+ });
228
+ throw error; // handleToolError may not throw
229
+ }
230
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Tests for remember_moderate tool.
3
+ */
4
+
5
+ import { moderateTool, handleModerate } from './moderate.js';
6
+ import type { AuthContext, GroupPermissions } from '../types/auth.js';
7
+
8
+ // ─── Mocks ───────────────────────────────────────────────────
9
+
10
+ const mockUpdate = jest.fn().mockResolvedValue(undefined);
11
+
12
+ jest.mock('../weaviate/client.js', () => ({
13
+ getWeaviateClient: jest.fn(() => ({
14
+ collections: {
15
+ get: jest.fn().mockReturnValue({
16
+ data: { update: jest.fn().mockResolvedValue(undefined) },
17
+ }),
18
+ },
19
+ })),
20
+ fetchMemoryWithAllProperties: jest.fn(),
21
+ }));
22
+
23
+ jest.mock('../weaviate/space-schema.js', () => ({
24
+ ensurePublicCollection: jest.fn(),
25
+ }));
26
+
27
+ jest.mock('../utils/logger.js', () => ({
28
+ logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
29
+ }));
30
+
31
+ jest.mock('../utils/debug.js', () => ({
32
+ createDebugLogger: jest.fn(() => ({
33
+ info: jest.fn(),
34
+ debug: jest.fn(),
35
+ trace: jest.fn(),
36
+ error: jest.fn(),
37
+ time: jest.fn((_: string, fn: () => any) => fn()),
38
+ })),
39
+ }));
40
+
41
+ jest.mock('../collections/dot-notation.js', () => ({
42
+ CollectionType: { GROUPS: 'groups' },
43
+ getCollectionName: jest.fn((_: string, id: string) => `Memory_groups_${id}`),
44
+ }));
45
+
46
+ jest.mock('../utils/error-handler.js', () => ({
47
+ handleToolError: jest.fn(() => '{"success":false,"error":"internal"}'),
48
+ }));
49
+
50
+ import { getWeaviateClient, fetchMemoryWithAllProperties } from '../weaviate/client.js';
51
+ import { ensurePublicCollection } from '../weaviate/space-schema.js';
52
+
53
+ const mockFetchMemory = fetchMemoryWithAllProperties as jest.MockedFunction<any>;
54
+ const mockGetWeaviateClient = getWeaviateClient as jest.MockedFunction<any>;
55
+ const mockEnsurePublicCollection = ensurePublicCollection as jest.MockedFunction<any>;
56
+
57
+ // ─── Helpers ─────────────────────────────────────────────────
58
+
59
+ const BASE_PERMISSIONS: GroupPermissions = {
60
+ can_read: true,
61
+ can_publish: true,
62
+ can_revise: false,
63
+ can_propose: false,
64
+ can_overwrite: false,
65
+ can_comment: true,
66
+ can_retract_own: true,
67
+ can_retract_any: false,
68
+ can_manage_members: false,
69
+ can_moderate: false,
70
+ };
71
+
72
+ function makeModeratorAuth(groupId: string): AuthContext {
73
+ return {
74
+ accessToken: 'tok',
75
+ credentials: {
76
+ user_id: 'moderator-1',
77
+ group_memberships: [{
78
+ group_id: groupId,
79
+ permissions: { ...BASE_PERMISSIONS, can_moderate: true },
80
+ }],
81
+ },
82
+ };
83
+ }
84
+
85
+ function makeNonModeratorAuth(): AuthContext {
86
+ return {
87
+ accessToken: 'tok',
88
+ credentials: {
89
+ user_id: 'user-1',
90
+ group_memberships: [{
91
+ group_id: 'some-group',
92
+ permissions: { ...BASE_PERMISSIONS, can_moderate: false },
93
+ }],
94
+ },
95
+ };
96
+ }
97
+
98
+ const PUBLISHED_MEMORY = {
99
+ properties: {
100
+ content: 'Published memory',
101
+ moderation_status: 'pending',
102
+ author_id: 'author-1',
103
+ },
104
+ };
105
+
106
+ // ─── Tests ───────────────────────────────────────────────────
107
+
108
+ describe('moderateTool definition', () => {
109
+ it('has correct tool name', () => {
110
+ expect(moderateTool.name).toBe('remember_moderate');
111
+ });
112
+
113
+ it('requires memory_id and action', () => {
114
+ expect(moderateTool.inputSchema.required).toEqual(['memory_id', 'action']);
115
+ });
116
+
117
+ it('has action enum with approve, reject, remove', () => {
118
+ const props = moderateTool.inputSchema.properties as Record<string, any>;
119
+ expect(props.action.enum).toEqual(['approve', 'reject', 'remove']);
120
+ });
121
+ });
122
+
123
+ describe('handleModerate', () => {
124
+ let groupUpdate: jest.Mock;
125
+ let spaceUpdate: jest.Mock;
126
+
127
+ beforeEach(() => {
128
+ jest.clearAllMocks();
129
+
130
+ groupUpdate = jest.fn().mockResolvedValue(undefined);
131
+ spaceUpdate = jest.fn().mockResolvedValue(undefined);
132
+
133
+ mockGetWeaviateClient.mockReturnValue({
134
+ collections: {
135
+ get: jest.fn().mockReturnValue({
136
+ data: { update: groupUpdate },
137
+ }),
138
+ },
139
+ });
140
+
141
+ mockEnsurePublicCollection.mockResolvedValue({
142
+ data: { update: spaceUpdate },
143
+ });
144
+
145
+ mockFetchMemory.mockResolvedValue(PUBLISHED_MEMORY);
146
+ });
147
+
148
+ it('returns error when no space_id or group_id provided', async () => {
149
+ const result = JSON.parse(
150
+ await handleModerate({ memory_id: 'mem-1', action: 'approve' }, 'mod-1', makeModeratorAuth('g1'))
151
+ );
152
+ expect(result.success).toBe(false);
153
+ expect(result.error).toBe('Missing destination');
154
+ });
155
+
156
+ it('returns permission error for non-moderator on group', async () => {
157
+ const result = JSON.parse(
158
+ await handleModerate(
159
+ { memory_id: 'mem-1', action: 'approve', group_id: 'team-1' },
160
+ 'user-1',
161
+ makeNonModeratorAuth()
162
+ )
163
+ );
164
+ expect(result.success).toBe(false);
165
+ expect(result.error).toBe('Permission denied');
166
+ });
167
+
168
+ it('returns permission error for non-moderator on space', async () => {
169
+ const result = JSON.parse(
170
+ await handleModerate(
171
+ { memory_id: 'mem-1', action: 'approve', space_id: 'public' },
172
+ 'user-1',
173
+ makeNonModeratorAuth()
174
+ )
175
+ );
176
+ expect(result.success).toBe(false);
177
+ expect(result.error).toBe('Permission denied');
178
+ });
179
+
180
+ it('returns error when memory not found', async () => {
181
+ mockFetchMemory.mockResolvedValue(null);
182
+
183
+ const result = JSON.parse(
184
+ await handleModerate(
185
+ { memory_id: 'missing', action: 'approve', group_id: 'g1' },
186
+ 'mod-1',
187
+ makeModeratorAuth('g1')
188
+ )
189
+ );
190
+ expect(result.success).toBe(false);
191
+ expect(result.error).toBe('Memory not found');
192
+ });
193
+
194
+ it('approves a memory in a group', async () => {
195
+ const result = JSON.parse(
196
+ await handleModerate(
197
+ { memory_id: 'mem-1', action: 'approve', group_id: 'g1' },
198
+ 'mod-1',
199
+ makeModeratorAuth('g1')
200
+ )
201
+ );
202
+ expect(result.success).toBe(true);
203
+ expect(result.moderation_status).toBe('approved');
204
+ expect(result.moderated_by).toBe('mod-1');
205
+ expect(result.moderated_at).toBeDefined();
206
+
207
+ expect(groupUpdate).toHaveBeenCalledWith({
208
+ id: 'mem-1',
209
+ properties: expect.objectContaining({
210
+ moderation_status: 'approved',
211
+ moderated_by: 'mod-1',
212
+ }),
213
+ });
214
+ });
215
+
216
+ it('rejects a memory in a group', async () => {
217
+ const result = JSON.parse(
218
+ await handleModerate(
219
+ { memory_id: 'mem-1', action: 'reject', group_id: 'g1', reason: 'Spam' },
220
+ 'mod-1',
221
+ makeModeratorAuth('g1')
222
+ )
223
+ );
224
+ expect(result.success).toBe(true);
225
+ expect(result.moderation_status).toBe('rejected');
226
+ expect(result.reason).toBe('Spam');
227
+ });
228
+
229
+ it('removes a memory in a group', async () => {
230
+ const result = JSON.parse(
231
+ await handleModerate(
232
+ { memory_id: 'mem-1', action: 'remove', group_id: 'g1' },
233
+ 'mod-1',
234
+ makeModeratorAuth('g1')
235
+ )
236
+ );
237
+ expect(result.success).toBe(true);
238
+ expect(result.moderation_status).toBe('removed');
239
+ });
240
+
241
+ it('moderates a memory in a space', async () => {
242
+ const result = JSON.parse(
243
+ await handleModerate(
244
+ { memory_id: 'mem-1', action: 'approve', space_id: 'public' },
245
+ 'mod-1',
246
+ makeModeratorAuth('some-group')
247
+ )
248
+ );
249
+ expect(result.success).toBe(true);
250
+ expect(result.moderation_status).toBe('approved');
251
+ expect(result.location).toBe('space:public');
252
+
253
+ expect(spaceUpdate).toHaveBeenCalledWith({
254
+ id: 'mem-1',
255
+ properties: expect.objectContaining({
256
+ moderation_status: 'approved',
257
+ moderated_by: 'mod-1',
258
+ }),
259
+ });
260
+ });
261
+
262
+ it('sets moderated_at to a valid ISO date', async () => {
263
+ const before = new Date().toISOString();
264
+
265
+ const result = JSON.parse(
266
+ await handleModerate(
267
+ { memory_id: 'mem-1', action: 'approve', group_id: 'g1' },
268
+ 'mod-1',
269
+ makeModeratorAuth('g1')
270
+ )
271
+ );
272
+
273
+ const after = new Date().toISOString();
274
+ expect(result.moderated_at >= before).toBe(true);
275
+ expect(result.moderated_at <= after).toBe(true);
276
+ });
277
+ });