@prmichaelsen/remember-mcp 3.15.7 → 3.16.2

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 (49) hide show
  1. package/.github/workflows/publish.yml +55 -0
  2. package/CHANGELOG.md +20 -0
  3. package/agent/design/local.unified-internal-memory-tools.md +325 -0
  4. package/agent/milestones/milestone-20-unified-internal-memory-tools.md +58 -0
  5. package/agent/progress.yaml +115 -1
  6. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-212-add-internal-context-type.md +54 -0
  7. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-213-update-server-factory-internal-context.md +117 -0
  8. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-214-create-tag-builder-utility.md +50 -0
  9. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-215-create-unified-internal-memory-tools.md +65 -0
  10. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-216-update-default-search-filters.md +46 -0
  11. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-217-delete-standalone-ghost-tools.md +46 -0
  12. package/agent/tasks/milestone-20-unified-internal-memory-tools/task-218-add-tests-unified-internal-tools.md +66 -0
  13. package/dist/e2e-helpers.d.ts +1 -1
  14. package/dist/server-factory.d.ts +17 -41
  15. package/dist/server-factory.js +420 -149
  16. package/dist/server.js +202 -20
  17. package/dist/tools/{create-ghost-memory.d.ts → create-internal-memory.d.ts} +7 -9
  18. package/dist/tools/internal-tools.spec.d.ts +2 -0
  19. package/dist/tools/{query-ghost-memory.d.ts → query-internal-memory.d.ts} +6 -6
  20. package/dist/tools/{search-ghost-memory-by.d.ts → search-internal-memory-by.d.ts} +6 -6
  21. package/dist/tools/{search-ghost-memory.d.ts → search-internal-memory.d.ts} +6 -6
  22. package/dist/tools/{update-ghost-memory.d.ts → update-internal-memory.d.ts} +6 -6
  23. package/dist/types/auth.d.ts +22 -8
  24. package/dist/utils/internal-tags.d.ts +14 -0
  25. package/dist/utils/internal-tags.spec.d.ts +2 -0
  26. package/package.json +2 -3
  27. package/src/e2e-helpers.ts +4 -2
  28. package/src/ghost-persona.e2e.ts +18 -17
  29. package/src/server-factory.ts +117 -55
  30. package/src/tools/create-internal-memory.ts +105 -0
  31. package/src/tools/find-similar.ts +2 -2
  32. package/src/tools/internal-tools.spec.ts +312 -0
  33. package/src/tools/query-internal-memory.ts +73 -0
  34. package/src/tools/query-memory.ts +15 -12
  35. package/src/tools/search-by.spec.ts +6 -2
  36. package/src/tools/search-by.ts +6 -6
  37. package/src/tools/{search-ghost-memory-by.ts → search-internal-memory-by.ts} +34 -27
  38. package/src/tools/search-internal-memory.ts +87 -0
  39. package/src/tools/search-memory.ts +15 -12
  40. package/src/tools/search-space.ts +1 -0
  41. package/src/tools/{update-ghost-memory.ts → update-internal-memory.ts} +23 -17
  42. package/src/types/auth.ts +22 -8
  43. package/src/utils/internal-tags.spec.ts +104 -0
  44. package/src/utils/internal-tags.ts +46 -0
  45. package/dist/tools/ghost-tools.spec.d.ts +0 -2
  46. package/src/tools/create-ghost-memory.ts +0 -103
  47. package/src/tools/ghost-tools.spec.ts +0 -361
  48. package/src/tools/query-ghost-memory.ts +0 -63
  49. package/src/tools/search-ghost-memory.ts +0 -73
@@ -0,0 +1,87 @@
1
+ /**
2
+ * remember_search_internal_memory tool
3
+ * Wraps search_memory with auto-scoped content type and ghost source filters.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { handleSearchMemory } from './search-memory.js';
10
+ import { buildInternalTags } from '../utils/internal-tags.js';
11
+
12
+ export const searchInternalMemoryTool = {
13
+ name: 'remember_search_internal_memory',
14
+ description: `Search internal memories (ghost or agent) using hybrid semantic + keyword search.
15
+
16
+ Automatically scoped to the current session's content type and ghost source.
17
+ In ghost mode, only shows memories from the current ghost conversation
18
+ (e.g., only alice's ghost memories, not carol's).`,
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ query: { type: 'string', description: 'Search query' },
23
+ alpha: { type: 'number', minimum: 0, maximum: 1, description: 'Semantic vs keyword balance. Default: 0.7' },
24
+ tags: { type: 'array', items: { type: 'string' }, description: 'Additional tag filters' },
25
+ limit: { type: 'number', description: 'Max results. Default: 10' },
26
+ offset: { type: 'number' },
27
+ deleted_filter: { type: 'string', enum: ['exclude', 'include', 'only'] },
28
+ },
29
+ required: ['query'],
30
+ },
31
+ };
32
+
33
+ export interface SearchInternalMemoryArgs {
34
+ query: string;
35
+ alpha?: number;
36
+ tags?: string[];
37
+ limit?: number;
38
+ offset?: number;
39
+ deleted_filter?: 'exclude' | 'include' | 'only';
40
+ }
41
+
42
+ export async function handleSearchInternalMemory(
43
+ args: SearchInternalMemoryArgs,
44
+ userId: string,
45
+ authContext?: AuthContext
46
+ ): Promise<string> {
47
+ const debug = createDebugLogger({ tool: 'remember_search_internal_memory', userId, operation: 'search internal memories' });
48
+ try {
49
+ debug.info('Tool invoked');
50
+
51
+ const ctx = authContext?.internalContext;
52
+ if (!ctx) {
53
+ return JSON.stringify({ error: 'Internal context required. X-Internal-Type header must be set.' });
54
+ }
55
+
56
+ // Build scope tags for ghost source isolation (no override allowed)
57
+ const allTags = buildInternalTags(authContext!);
58
+ // Scope tags = everything except the base 'ghost' or 'agent' tag (those are covered by type filter)
59
+ const scopeTags = allTags.filter(t => t !== 'ghost' && t !== 'agent');
60
+ const userTags = args.tags ?? [];
61
+ const mergedTags = [...new Set([...scopeTags, ...userTags])];
62
+
63
+ // Delegate to search_memory with type + scope filters
64
+ return await handleSearchMemory(
65
+ {
66
+ query: args.query,
67
+ alpha: args.alpha,
68
+ limit: args.limit,
69
+ offset: args.offset,
70
+ filters: {
71
+ types: [ctx.type] as any,
72
+ tags: mergedTags.length > 0 ? mergedTags : undefined,
73
+ },
74
+ deleted_filter: args.deleted_filter,
75
+ },
76
+ userId,
77
+ authContext
78
+ );
79
+ } catch (error) {
80
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
81
+ handleToolError(error, {
82
+ toolName: 'remember_search_internal_memory',
83
+ operation: 'search internal memories',
84
+ userId,
85
+ });
86
+ }
87
+ }
@@ -155,13 +155,13 @@ export async function handleSearchMemory(
155
155
  userId: string,
156
156
  authContext?: AuthContext
157
157
  ): Promise<string> {
158
- const ghostMode = authContext?.ghostMode;
158
+ const internalContext = authContext?.internalContext;
159
159
  // In ghost mode, search the ghost owner's collection instead of the caller's
160
- const searchUserId = ghostMode?.owner_user_id ?? userId;
161
- const debug = createDebugLogger({ tool: 'remember_search_memory', userId: searchUserId, operation: ghostMode ? 'ghost search' : 'search memory' });
160
+ const searchUserId = internalContext?.owner_user_id ?? userId;
161
+ const debug = createDebugLogger({ tool: 'remember_search_memory', userId: searchUserId, operation: internalContext ? 'internal search' : 'search memory' });
162
162
  try {
163
163
  debug.info('Tool invoked');
164
- debug.trace('Arguments', { args, ghostMode: !!ghostMode });
164
+ debug.trace('Arguments', { args, internalContext: !!internalContext });
165
165
  // Validate query is not empty
166
166
  if (!args.query || args.query.trim() === '') {
167
167
  throw new Error('Query cannot be empty');
@@ -173,7 +173,7 @@ export async function handleSearchMemory(
173
173
  userId: searchUserId,
174
174
  query: args.query,
175
175
  includeRelationships,
176
- ghostMode: !!ghostMode,
176
+ internalContext: !!internalContext,
177
177
  });
178
178
 
179
179
  const collection = getMemoryCollection(searchUserId);
@@ -185,8 +185,8 @@ export async function handleSearchMemory(
185
185
  const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || 'exclude');
186
186
 
187
187
  // Build trust filter for ghost mode (resolved server-side, never from tool args)
188
- const trustFilter = ghostMode
189
- ? buildTrustFilter(collection, ghostMode.accessor_trust_level)
188
+ const trustFilter = internalContext?.accessor_trust_level != null
189
+ ? buildTrustFilter(collection, internalContext.accessor_trust_level)
190
190
  : null;
191
191
 
192
192
  // Build filters using v3 API
@@ -195,14 +195,17 @@ export async function handleSearchMemory(
195
195
  ? buildCombinedSearchFilters(collection, args.filters)
196
196
  : buildMemoryOnlyFilters(collection, args.filters);
197
197
 
198
- // Exclude ghost memories by default (unless explicitly searching for them)
198
+ // Exclude ghost and agent memories by default (unless explicitly searching for them)
199
199
  const hasExplicitTypeFilter = args.filters?.types && args.filters.types.length > 0;
200
- const ghostExclusionFilter = !hasExplicitTypeFilter
201
- ? collection.filter.byProperty('content_type').notEqual('ghost')
200
+ const internalExclusionFilter = !hasExplicitTypeFilter
201
+ ? combineFiltersWithAnd([
202
+ collection.filter.byProperty('content_type').notEqual('ghost'),
203
+ collection.filter.byProperty('content_type').notEqual('agent'),
204
+ ])
202
205
  : null;
203
206
 
204
- // Combine deleted filter, trust filter, ghost exclusion, and search filters
205
- const combinedFilters = combineFiltersWithAnd([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter(f => f !== null));
207
+ // Combine deleted filter, trust filter, internal exclusion, and search filters
208
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, trustFilter, internalExclusionFilter, searchFilters].filter(f => f !== null));
206
209
 
207
210
  // Build search options (native offset handled by Weaviate)
208
211
  const searchOptions: any = {
@@ -161,6 +161,7 @@ export function buildBaseFilters(collection: any, args: SearchSpaceArgs): any[]
161
161
  if (args.content_type) filterList.push(collection.filter.byProperty('content_type').equal(args.content_type));
162
162
  if (!args.include_comments && !args.content_type) filterList.push(collection.filter.byProperty('content_type').notEqual('comment'));
163
163
  if (!args.content_type) filterList.push(collection.filter.byProperty('content_type').notEqual('ghost'));
164
+ if (!args.content_type) filterList.push(collection.filter.byProperty('content_type').notEqual('agent'));
164
165
  if (args.tags && args.tags.length > 0) args.tags.forEach(tag => filterList.push(collection.filter.byProperty('tags').containsAny([tag])));
165
166
  if (args.min_weight !== undefined) filterList.push(collection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
166
167
  if (args.max_weight !== undefined) filterList.push(collection.filter.byProperty('weight').lessOrEqual(args.max_weight));
@@ -1,6 +1,6 @@
1
1
  /**
2
- * remember_update_ghost_memory tool
3
- * Updates a ghost memory after validating content_type
2
+ * remember_update_internal_memory tool
3
+ * Updates a ghost or agent memory after validating content_type matches session context.
4
4
  */
5
5
 
6
6
  import { handleToolError } from '../utils/error-handler.js';
@@ -9,13 +9,14 @@ import type { AuthContext } from '../types/auth.js';
9
9
  import { createCoreServices } from '../core-services.js';
10
10
  import { getMemoryCollection } from '../weaviate/schema.js';
11
11
 
12
- export const updateGhostMemoryTool = {
13
- name: 'remember_update_ghost_memory',
14
- description: 'Update a ghost memory. Only works on memories with content_type: ghost.',
12
+ export const updateInternalMemoryTool = {
13
+ name: 'remember_update_internal_memory',
14
+ description: `Update an internal memory (ghost or agent). Only works on memories matching
15
+ the current session's content type. Use remember_update_memory for regular memories.`,
15
16
  inputSchema: {
16
17
  type: 'object',
17
18
  properties: {
18
- memory_id: { type: 'string', description: 'Ghost memory ID to update' },
19
+ memory_id: { type: 'string', description: 'Memory ID to update' },
19
20
  content: { type: 'string' },
20
21
  title: { type: 'string' },
21
22
  tags: { type: 'array', items: { type: 'string' } },
@@ -26,7 +27,7 @@ export const updateGhostMemoryTool = {
26
27
  },
27
28
  };
28
29
 
29
- export interface UpdateGhostMemoryArgs {
30
+ export interface UpdateInternalMemoryArgs {
30
31
  memory_id: string;
31
32
  content?: string;
32
33
  title?: string;
@@ -35,25 +36,30 @@ export interface UpdateGhostMemoryArgs {
35
36
  trust?: number;
36
37
  }
37
38
 
38
- export async function handleUpdateGhostMemory(
39
- args: UpdateGhostMemoryArgs,
39
+ export async function handleUpdateInternalMemory(
40
+ args: UpdateInternalMemoryArgs,
40
41
  userId: string,
41
42
  authContext?: AuthContext
42
43
  ): Promise<string> {
43
- const debug = createDebugLogger({ tool: 'remember_update_ghost_memory', userId, operation: 'update ghost memory' });
44
+ const debug = createDebugLogger({ tool: 'remember_update_internal_memory', userId, operation: 'update internal memory' });
44
45
  try {
45
46
  debug.info('Tool invoked');
46
- debug.trace('Arguments', { args });
47
47
 
48
- // Validate the memory is a ghost memory
48
+ const ctx = authContext?.internalContext;
49
+ if (!ctx) {
50
+ return JSON.stringify({ error: 'Internal context required. X-Internal-Type header must be set.' });
51
+ }
52
+
53
+ // Validate the memory matches the current content type
49
54
  const collection = getMemoryCollection(userId);
50
55
  const existing = await collection.query.fetchObjectById(args.memory_id);
51
56
  if (!existing) {
52
57
  return JSON.stringify({ error: `Memory ${args.memory_id} not found` });
53
58
  }
54
- if ((existing.properties as any).content_type !== 'ghost') {
59
+ const existingType = (existing.properties as any).content_type;
60
+ if (existingType !== ctx.type) {
55
61
  return JSON.stringify({
56
- error: `Memory ${args.memory_id} is not a ghost memory (content_type: ${(existing.properties as any).content_type}). Use remember_update_memory for non-ghost memories.`,
62
+ error: `Memory ${args.memory_id} is content_type: ${existingType}, but current session is ${ctx.type}. Use remember_update_memory for non-internal memories.`,
57
63
  });
58
64
  }
59
65
 
@@ -72,13 +78,13 @@ export async function handleUpdateGhostMemory(
72
78
  updated_at: result.updated_at,
73
79
  version: result.version,
74
80
  updated_fields: result.updated_fields,
75
- message: `Ghost memory updated successfully. Updated fields: ${result.updated_fields.join(', ')}`,
81
+ message: `${ctx.type} memory updated successfully. Updated fields: ${result.updated_fields.join(', ')}`,
76
82
  }, null, 2);
77
83
  } catch (error) {
78
84
  debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
79
85
  handleToolError(error, {
80
- toolName: 'remember_update_ghost_memory',
81
- operation: 'update ghost memory',
86
+ toolName: 'remember_update_internal_memory',
87
+ operation: 'update internal memory',
82
88
  userId,
83
89
  memoryId: args.memory_id,
84
90
  });
package/src/types/auth.ts CHANGED
@@ -28,21 +28,35 @@ export interface UserCredentials {
28
28
  group_memberships: GroupMembership[];
29
29
  }
30
30
 
31
- /** Ghost conversation context — resolved server-side, never from tool args */
32
- export interface GhostModeContext {
33
- /** The ghost owner's user ID (whose memories are being searched) */
34
- owner_user_id: string;
35
- /** The accessor's user ID (who is chatting with the ghost) */
31
+ /**
32
+ * Internal context for ghost/agent sessions — resolved server-side from
33
+ * platform HTTP headers, never from tool args.
34
+ *
35
+ * Replaces the former GhostModeContext. All ghost identity, trust, and
36
+ * agent context is unified here.
37
+ */
38
+ export interface InternalContext {
39
+ /** Whether this is a ghost or agent session */
40
+ type: 'ghost' | 'agent';
41
+ /** Ghost sub-type (required when type is 'ghost') */
42
+ ghost_type?: 'user' | 'space' | 'group';
43
+ /** Space ID (space ghosts only) */
44
+ ghost_space?: string;
45
+ /** Group ID (group ghosts only) */
46
+ ghost_group?: string;
47
+ /** The ghost owner's user ID (user ghosts — whose memories are searched) */
48
+ owner_user_id?: string;
49
+ /** The accessor's user ID (who is conversing) */
36
50
  accessor_user_id: string;
37
51
  /** Resolved trust level (looked up from GhostConfig, not user-supplied) */
38
- accessor_trust_level: number;
52
+ accessor_trust_level?: number;
39
53
  }
40
54
 
41
55
  export interface AuthContext {
42
56
  accessToken: string | null;
43
57
  credentials: UserCredentials | null;
44
- /** Present when the server is running in ghost conversation mode */
45
- ghostMode?: GhostModeContext;
58
+ /** Present when the server is running in ghost or agent mode */
59
+ internalContext?: InternalContext;
46
60
  }
47
61
 
48
62
  export type WriteMode = 'owner_only' | 'group_editors' | 'anyone';
@@ -0,0 +1,104 @@
1
+ import { buildInternalTags } from './internal-tags.js';
2
+ import type { AuthContext } from '../types/auth.js';
3
+
4
+ describe('buildInternalTags', () => {
5
+ const baseAuth: AuthContext = { accessToken: null, credentials: null };
6
+
7
+ it('returns empty array when no internalContext', () => {
8
+ expect(buildInternalTags(baseAuth)).toEqual([]);
9
+ });
10
+
11
+ it('returns ["agent"] for agent context', () => {
12
+ const auth: AuthContext = {
13
+ ...baseAuth,
14
+ internalContext: { type: 'agent', accessor_user_id: 'bob' },
15
+ };
16
+ expect(buildInternalTags(auth)).toEqual(['agent']);
17
+ });
18
+
19
+ it('returns correct tags for user ghost', () => {
20
+ const auth: AuthContext = {
21
+ ...baseAuth,
22
+ internalContext: {
23
+ type: 'ghost',
24
+ ghost_type: 'user',
25
+ owner_user_id: 'alice',
26
+ accessor_user_id: 'bob',
27
+ },
28
+ };
29
+ expect(buildInternalTags(auth)).toEqual([
30
+ 'ghost',
31
+ 'ghost_type:user',
32
+ 'ghost_owner:user:alice',
33
+ ]);
34
+ });
35
+
36
+ it('returns correct tags for space ghost', () => {
37
+ const auth: AuthContext = {
38
+ ...baseAuth,
39
+ internalContext: {
40
+ type: 'ghost',
41
+ ghost_type: 'space',
42
+ ghost_space: 'music-lovers',
43
+ accessor_user_id: 'bob',
44
+ },
45
+ };
46
+ expect(buildInternalTags(auth)).toEqual([
47
+ 'ghost',
48
+ 'ghost_type:space',
49
+ 'ghost_owner:space:music-lovers',
50
+ ]);
51
+ });
52
+
53
+ it('returns correct tags for group ghost', () => {
54
+ const auth: AuthContext = {
55
+ ...baseAuth,
56
+ internalContext: {
57
+ type: 'ghost',
58
+ ghost_type: 'group',
59
+ ghost_group: 'band-mates',
60
+ accessor_user_id: 'bob',
61
+ },
62
+ };
63
+ expect(buildInternalTags(auth)).toEqual([
64
+ 'ghost',
65
+ 'ghost_type:group',
66
+ 'ghost_owner:group:band-mates',
67
+ ]);
68
+ });
69
+
70
+ it('returns ["ghost"] only when ghost_type is missing', () => {
71
+ const auth: AuthContext = {
72
+ ...baseAuth,
73
+ internalContext: {
74
+ type: 'ghost',
75
+ accessor_user_id: 'bob',
76
+ },
77
+ };
78
+ expect(buildInternalTags(auth)).toEqual(['ghost']);
79
+ });
80
+
81
+ it('returns user ghost tags without owner when owner_user_id is missing', () => {
82
+ const auth: AuthContext = {
83
+ ...baseAuth,
84
+ internalContext: {
85
+ type: 'ghost',
86
+ ghost_type: 'user',
87
+ accessor_user_id: 'bob',
88
+ },
89
+ };
90
+ expect(buildInternalTags(auth)).toEqual(['ghost', 'ghost_type:user']);
91
+ });
92
+
93
+ it('returns space ghost tags without owner when ghost_space is missing', () => {
94
+ const auth: AuthContext = {
95
+ ...baseAuth,
96
+ internalContext: {
97
+ type: 'ghost',
98
+ ghost_type: 'space',
99
+ accessor_user_id: 'bob',
100
+ },
101
+ };
102
+ expect(buildInternalTags(auth)).toEqual(['ghost', 'ghost_type:space']);
103
+ });
104
+ });
@@ -0,0 +1,46 @@
1
+ import type { AuthContext } from '../types/auth.js';
2
+
3
+ /**
4
+ * Build tags for internal (ghost/agent) memories based on the current
5
+ * session's InternalContext. These tags implement ghost source isolation
6
+ * so memories from different ghost conversations are distinguishable.
7
+ *
8
+ * Tag scheme:
9
+ * User ghost (alice): ['ghost', 'ghost_type:user', 'ghost_owner:user:alice']
10
+ * Space ghost (music-lovers): ['ghost', 'ghost_type:space', 'ghost_owner:space:music-lovers']
11
+ * Group ghost (band-mates): ['ghost', 'ghost_type:group', 'ghost_owner:group:band-mates']
12
+ * Agent: ['agent']
13
+ */
14
+ export function buildInternalTags(authContext: AuthContext): string[] {
15
+ const ctx = authContext.internalContext;
16
+ if (!ctx) return [];
17
+
18
+ if (ctx.type === 'agent') {
19
+ return ['agent'];
20
+ }
21
+
22
+ const tags = ['ghost'];
23
+
24
+ switch (ctx.ghost_type) {
25
+ case 'user':
26
+ tags.push('ghost_type:user');
27
+ if (ctx.owner_user_id) {
28
+ tags.push(`ghost_owner:user:${ctx.owner_user_id}`);
29
+ }
30
+ break;
31
+ case 'space':
32
+ tags.push('ghost_type:space');
33
+ if (ctx.ghost_space) {
34
+ tags.push(`ghost_owner:space:${ctx.ghost_space}`);
35
+ }
36
+ break;
37
+ case 'group':
38
+ tags.push('ghost_type:group');
39
+ if (ctx.ghost_group) {
40
+ tags.push(`ghost_owner:group:${ctx.ghost_group}`);
41
+ }
42
+ break;
43
+ }
44
+
45
+ return tags;
46
+ }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=ghost-tools.spec.d.ts.map
@@ -1,103 +0,0 @@
1
- /**
2
- * remember_create_ghost_memory tool
3
- * Creates a ghost memory with hardcoded content_type and ghost-specific tags
4
- */
5
-
6
- import { handleToolError } from '../utils/error-handler.js';
7
- import { createDebugLogger } from '../utils/debug.js';
8
- import type { AuthContext } from '../types/auth.js';
9
- import { createCoreServices } from '../core-services.js';
10
-
11
- export const createGhostMemoryTool = {
12
- name: 'remember_create_ghost_memory',
13
- description: `Create a ghost memory (cross-user interaction record).
14
-
15
- Ghost memories track what happened during ghost conversations — observations,
16
- impressions, and insights about the accessor. Automatically sets content_type
17
- to 'ghost' and adds ghost-specific tags.
18
-
19
- Ghost memories are excluded from default searches. They are only visible when
20
- explicitly searching with content_type: 'ghost' or using ghost memory tools.`,
21
- inputSchema: {
22
- type: 'object',
23
- properties: {
24
- content: { type: 'string', description: 'Ghost memory content' },
25
- title: { type: 'string', description: 'Optional title' },
26
- tags: { type: 'array', items: { type: 'string' }, description: 'Additional tags (ghost-specific tags added automatically)' },
27
- weight: { type: 'number', minimum: 0, maximum: 1, description: 'Significance (0-1)' },
28
- trust: { type: 'number', minimum: 0, maximum: 1, description: 'Trust level (0-1)' },
29
- feel_salience: { type: 'number', minimum: 0, maximum: 1, description: 'How unexpected/novel (0-1)' },
30
- feel_social_weight: { type: 'number', minimum: 0, maximum: 1, description: 'Relationship/reputation impact (0-1)' },
31
- feel_narrative_importance: { type: 'number', minimum: 0, maximum: 1, description: 'Story arc importance (0-1)' },
32
- },
33
- required: ['content'],
34
- },
35
- };
36
-
37
- export interface CreateGhostMemoryArgs {
38
- content: string;
39
- title?: string;
40
- tags?: string[];
41
- weight?: number;
42
- trust?: number;
43
- [key: string]: any;
44
- }
45
-
46
- export async function handleCreateGhostMemory(
47
- args: CreateGhostMemoryArgs,
48
- userId: string,
49
- authContext?: AuthContext
50
- ): Promise<string> {
51
- const debug = createDebugLogger({ tool: 'remember_create_ghost_memory', userId, operation: 'create ghost memory' });
52
- try {
53
- debug.info('Tool invoked');
54
- debug.trace('Arguments', { args });
55
-
56
- const { memory } = createCoreServices(userId);
57
-
58
- // Build ghost-specific tags
59
- const accessorUserId = authContext?.ghostMode?.accessor_user_id;
60
- const ghostTags = ['ghost'];
61
- if (accessorUserId) {
62
- ghostTags.push(`ghost:${accessorUserId}`);
63
- }
64
-
65
- // Merge user tags with ghost tags (avoid duplicates)
66
- const userTags = args.tags ?? [];
67
- const mergedTags = [...new Set([...ghostTags, ...userTags])];
68
-
69
- // Extract feel_* fields
70
- const feelFields: Record<string, number> = {};
71
- for (const [key, value] of Object.entries(args)) {
72
- if (key.startsWith('feel_') && typeof value === 'number') {
73
- feelFields[key] = value;
74
- }
75
- }
76
-
77
- const result = await memory.create({
78
- content: args.content,
79
- title: args.title,
80
- type: 'ghost' as any,
81
- weight: args.weight,
82
- trust: args.trust,
83
- tags: mergedTags,
84
- context_summary: 'Ghost memory created via MCP',
85
- ...feelFields,
86
- } as any);
87
-
88
- return JSON.stringify({
89
- memory_id: result.memory_id,
90
- created_at: result.created_at,
91
- content_type: 'ghost',
92
- tags: mergedTags,
93
- message: `Ghost memory created successfully with ID: ${result.memory_id}`,
94
- }, null, 2);
95
- } catch (error) {
96
- debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
97
- handleToolError(error, {
98
- toolName: 'remember_create_ghost_memory',
99
- operation: 'create ghost memory',
100
- userId,
101
- });
102
- }
103
- }