@prmichaelsen/remember-mcp 3.15.4 → 3.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +363 -5
- package/CHANGELOG.md +7 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +50 -0
- package/agent/commands/acp.command-create.md +60 -0
- package/agent/commands/acp.design-create.md +62 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +48 -0
- package/agent/commands/acp.package-create.md +1 -0
- package/agent/commands/acp.package-info.md +1 -0
- package/agent/commands/acp.package-install.md +19 -0
- package/agent/commands/acp.package-list.md +1 -0
- package/agent/commands/acp.package-publish.md +1 -0
- package/agent/commands/acp.package-remove.md +1 -0
- package/agent/commands/acp.package-search.md +1 -0
- package/agent/commands/acp.package-update.md +1 -0
- package/agent/commands/acp.package-validate.md +1 -0
- package/agent/commands/acp.pattern-create.md +60 -0
- package/agent/commands/acp.plan.md +25 -0
- package/agent/commands/acp.proceed.md +621 -75
- package/agent/commands/acp.project-create.md +3 -0
- package/agent/commands/acp.project-info.md +3 -0
- package/agent/commands/acp.project-list.md +3 -1
- package/agent/commands/acp.project-set.md +1 -0
- package/agent/commands/acp.project-update.md +14 -3
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +13 -0
- package/agent/commands/acp.resume.md +3 -1
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +13 -0
- package/agent/commands/acp.sync.md +1 -0
- package/agent/commands/acp.task-create.md +105 -3
- package/agent/commands/acp.update.md +1 -0
- package/agent/commands/acp.validate.md +32 -2
- package/agent/commands/acp.version-check-for-updates.md +1 -0
- package/agent/commands/acp.version-check.md +1 -0
- package/agent/commands/acp.version-update.md +1 -0
- package/agent/commands/command.template.md +23 -0
- package/agent/commands/git.commit.md +1 -0
- package/agent/commands/git.init.md +1 -0
- package/agent/design/complete-tool-set.md +157 -233
- package/agent/design/design.template.md +18 -0
- package/agent/design/user-preferences.md +11 -7
- package/agent/milestones/milestone-19-new-search-ghost-tools.md +46 -0
- package/agent/package.template.yaml +50 -0
- package/agent/patterns/pattern.template.md +18 -0
- package/agent/progress.yaml +162 -6
- package/agent/scripts/acp.common.sh +258 -15
- package/agent/scripts/acp.install.sh +91 -4
- package/agent/scripts/acp.package-create.sh +0 -1
- package/agent/scripts/acp.package-info.sh +19 -1
- package/agent/scripts/acp.package-install-optimized.sh +1 -1
- package/agent/scripts/acp.package-install.sh +388 -38
- package/agent/scripts/acp.package-list.sh +52 -4
- package/agent/scripts/acp.package-remove.sh +77 -1
- package/agent/scripts/acp.package-search.sh +2 -2
- package/agent/scripts/acp.package-update.sh +91 -12
- package/agent/scripts/acp.package-validate.sh +136 -1
- package/agent/scripts/acp.project-info.sh +34 -11
- package/agent/scripts/acp.project-list.sh +4 -0
- package/agent/scripts/acp.project-update.sh +66 -19
- package/agent/scripts/acp.projects-restore.sh +170 -0
- package/agent/scripts/acp.projects-sync.sh +155 -0
- package/agent/scripts/acp.sessions.sh +725 -0
- package/agent/scripts/acp.version-update.sh +21 -3
- package/agent/scripts/acp.yaml-parser.sh +20 -6
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-203-create-search-by-tool.md +143 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-204-add-new-filters-existing-tools.md +77 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-205-add-feel-fields-create-update.md +137 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-206-add-byproperty-bysignificance-modes.md +135 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-207-add-emotional-composites-search-results.md +88 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-208-add-bybroad-byrandom-modes.md +115 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-209-create-ghost-memory-tools.md +192 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-210-create-get-core-tool.md +203 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-211-create-search-space-by-tool.md +182 -0
- package/agent/tasks/task-1-{title}.template.md +19 -0
- package/agent/tasks/unassigned/bug-report-remember-core-e2e-findings.md +99 -0
- package/dist/e2e-helpers.d.ts +26 -0
- package/dist/ghost-persona.e2e.d.ts +8 -0
- package/dist/memory-crud.e2e.d.ts +8 -0
- package/dist/preferences.e2e.d.ts +8 -0
- package/dist/relationships.e2e.d.ts +8 -0
- package/dist/search-modes.e2e.d.ts +8 -0
- package/dist/server-factory.js +1977 -100
- package/dist/server.js +1174 -51
- package/dist/shared-spaces.e2e.d.ts +8 -0
- package/dist/tools/create-ghost-memory.d.ts +70 -0
- package/dist/tools/create-memory.d.ts +175 -0
- package/dist/tools/get-core.d.ts +28 -0
- package/dist/tools/get-core.spec.d.ts +2 -0
- package/dist/tools/ghost-tools.spec.d.ts +2 -0
- package/dist/tools/query-ghost-memory.d.ts +34 -0
- package/dist/tools/query-memory.d.ts +4 -0
- package/dist/tools/search-by.d.ts +147 -0
- package/dist/tools/search-by.spec.d.ts +2 -0
- package/dist/tools/search-ghost-memory-by.d.ts +54 -0
- package/dist/tools/search-ghost-memory.d.ts +53 -0
- package/dist/tools/search-memory.d.ts +19 -0
- package/dist/tools/search-space-by.d.ts +78 -0
- package/dist/tools/search-space-by.spec.d.ts +2 -0
- package/dist/tools/search-space.d.ts +2 -0
- package/dist/tools/update-ghost-memory.d.ts +51 -0
- package/dist/tools/update-memory.d.ts +175 -0
- package/jest.e2e.config.js +11 -0
- package/package.json +2 -2
- package/src/e2e-helpers.ts +86 -0
- package/src/ghost-persona.e2e.ts +215 -0
- package/src/memory-crud.e2e.ts +203 -0
- package/src/preferences.e2e.ts +88 -0
- package/src/relationships.e2e.ts +156 -0
- package/src/search-modes.e2e.ts +184 -0
- package/src/server-factory.ts +56 -0
- package/src/shared-spaces.e2e.ts +204 -0
- package/src/tools/create-ghost-memory.ts +103 -0
- package/src/tools/create-memory.ts +45 -1
- package/src/tools/get-core.spec.ts +223 -0
- package/src/tools/get-core.ts +109 -0
- package/src/tools/ghost-tools.spec.ts +361 -0
- package/src/tools/query-ghost-memory.ts +63 -0
- package/src/tools/query-memory.ts +4 -0
- package/src/tools/search-by.spec.ts +325 -0
- package/src/tools/search-by.ts +298 -0
- package/src/tools/search-ghost-memory-by.ts +80 -0
- package/src/tools/search-ghost-memory.ts +73 -0
- package/src/tools/search-memory.ts +23 -0
- package/src/tools/search-space-by.spec.ts +289 -0
- package/src/tools/search-space-by.ts +173 -0
- package/src/tools/search-space.ts +20 -1
- package/src/tools/update-ghost-memory.ts +86 -0
- package/src/tools/update-memory.ts +45 -1
package/src/server-factory.ts
CHANGED
|
@@ -44,6 +44,18 @@ import { searchSpaceTool, handleSearchSpace } from './tools/search-space.js';
|
|
|
44
44
|
import { querySpaceTool, handleQuerySpace } from './tools/query-space.js';
|
|
45
45
|
import { moderateTool, handleModerate } from './tools/moderate.js';
|
|
46
46
|
import { ghostConfigTool, handleGhostConfig } from './tools/ghost-config.js';
|
|
47
|
+
import { searchByTool, handleSearchBy } from './tools/search-by.js';
|
|
48
|
+
|
|
49
|
+
// Import ghost memory tools
|
|
50
|
+
import { createGhostMemoryTool, handleCreateGhostMemory } from './tools/create-ghost-memory.js';
|
|
51
|
+
import { updateGhostMemoryTool, handleUpdateGhostMemory } from './tools/update-ghost-memory.js';
|
|
52
|
+
import { searchGhostMemoryTool, handleSearchGhostMemory } from './tools/search-ghost-memory.js';
|
|
53
|
+
import { queryGhostMemoryTool, handleQueryGhostMemory } from './tools/query-ghost-memory.js';
|
|
54
|
+
import { searchGhostMemoryByTool, handleSearchGhostMemoryBy } from './tools/search-ghost-memory-by.js';
|
|
55
|
+
|
|
56
|
+
// Import core introspection tools
|
|
57
|
+
import { getCoreTool, handleGetCore } from './tools/get-core.js';
|
|
58
|
+
import { searchSpaceByTool, handleSearchSpaceBy } from './tools/search-space-by.js';
|
|
47
59
|
|
|
48
60
|
// Import services (static — avoids dynamic import overhead on hot path)
|
|
49
61
|
import { getGhostConfig } from './services/ghost-config.service.js';
|
|
@@ -237,6 +249,18 @@ function registerHandlers(
|
|
|
237
249
|
querySpaceTool,
|
|
238
250
|
moderateTool,
|
|
239
251
|
ghostConfigTool,
|
|
252
|
+
// Search modes
|
|
253
|
+
searchByTool,
|
|
254
|
+
// Ghost memory tools
|
|
255
|
+
createGhostMemoryTool,
|
|
256
|
+
updateGhostMemoryTool,
|
|
257
|
+
searchGhostMemoryTool,
|
|
258
|
+
queryGhostMemoryTool,
|
|
259
|
+
searchGhostMemoryByTool,
|
|
260
|
+
// Core introspection
|
|
261
|
+
getCoreTool,
|
|
262
|
+
// Space search modes
|
|
263
|
+
searchSpaceByTool,
|
|
240
264
|
],
|
|
241
265
|
};
|
|
242
266
|
});
|
|
@@ -337,6 +361,38 @@ function registerHandlers(
|
|
|
337
361
|
result = await handleGhostConfig(args as any, userId, authContext);
|
|
338
362
|
break;
|
|
339
363
|
|
|
364
|
+
case 'remember_search_by':
|
|
365
|
+
result = await handleSearchBy(args as any, userId, authContext);
|
|
366
|
+
break;
|
|
367
|
+
|
|
368
|
+
case 'remember_create_ghost_memory':
|
|
369
|
+
result = await handleCreateGhostMemory(args as any, userId, authContext);
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
case 'remember_update_ghost_memory':
|
|
373
|
+
result = await handleUpdateGhostMemory(args as any, userId, authContext);
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
case 'remember_search_ghost_memory':
|
|
377
|
+
result = await handleSearchGhostMemory(args as any, userId, authContext);
|
|
378
|
+
break;
|
|
379
|
+
|
|
380
|
+
case 'remember_query_ghost_memory':
|
|
381
|
+
result = await handleQueryGhostMemory(args as any, userId, authContext);
|
|
382
|
+
break;
|
|
383
|
+
|
|
384
|
+
case 'remember_search_ghost_memory_by':
|
|
385
|
+
result = await handleSearchGhostMemoryBy(args as any, userId, authContext);
|
|
386
|
+
break;
|
|
387
|
+
|
|
388
|
+
case 'remember_get_core':
|
|
389
|
+
result = await handleGetCore(args as any, userId, authContext);
|
|
390
|
+
break;
|
|
391
|
+
|
|
392
|
+
case 'remember_search_space_by':
|
|
393
|
+
result = await handleSearchSpaceBy(args as any, userId, authContext);
|
|
394
|
+
break;
|
|
395
|
+
|
|
340
396
|
default:
|
|
341
397
|
throw new McpError(
|
|
342
398
|
ErrorCode.MethodNotFound,
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Shared Spaces — publish/confirm/deny/search/query/revise/retract
|
|
3
|
+
*
|
|
4
|
+
* Run with:
|
|
5
|
+
* DOTENV_CONFIG_PATH=.env.e1.local npm run test:e2e -- --testPathPattern=shared-spaces
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
e2eInit, e2eUserId, e2eAuthContext, e2eEnsureCollection,
|
|
10
|
+
e2eCleanup, e2eDeleteCollection, parseResult, waitForIndex,
|
|
11
|
+
} from './e2e-helpers.js';
|
|
12
|
+
import { handleCreateMemory } from './tools/create-memory.js';
|
|
13
|
+
import { handleUpdateMemory } from './tools/update-memory.js';
|
|
14
|
+
import { handlePublish } from './tools/publish.js';
|
|
15
|
+
import { handleConfirm } from './tools/confirm.js';
|
|
16
|
+
import { handleDeny } from './tools/deny.js';
|
|
17
|
+
import { handleSearchSpace } from './tools/search-space.js';
|
|
18
|
+
import { handleQuerySpace } from './tools/query-space.js';
|
|
19
|
+
import { handleRevise } from './tools/revise.js';
|
|
20
|
+
import { handleRetract } from './tools/retract.js';
|
|
21
|
+
|
|
22
|
+
const userId = e2eUserId('space');
|
|
23
|
+
const auth = e2eAuthContext();
|
|
24
|
+
const SPACE = 'the_void';
|
|
25
|
+
const SPACE_COLLECTION = 'Memory_spaces_public';
|
|
26
|
+
|
|
27
|
+
describe('E2E: Shared Spaces', () => {
|
|
28
|
+
let memoryId: string;
|
|
29
|
+
let publishToken: string;
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
await e2eInit();
|
|
33
|
+
await e2eEnsureCollection(userId);
|
|
34
|
+
|
|
35
|
+
// Create a memory to publish
|
|
36
|
+
const res = parseResult(await handleCreateMemory(
|
|
37
|
+
{ content: 'The best sourdough starter tip: feed with rye flour for extra tang.', weight: 0.8, tags: ['sourdough', 'baking', 'tips'] },
|
|
38
|
+
userId, auth,
|
|
39
|
+
));
|
|
40
|
+
memoryId = res.memory_id;
|
|
41
|
+
await waitForIndex();
|
|
42
|
+
}, 30_000);
|
|
43
|
+
|
|
44
|
+
afterAll(async () => {
|
|
45
|
+
await e2eCleanup(userId);
|
|
46
|
+
// Note: we don't delete the shared space collection as other tests may use it
|
|
47
|
+
}, 30_000);
|
|
48
|
+
|
|
49
|
+
// -------------------------------------------------------------------------
|
|
50
|
+
// DENY flow (publish → deny)
|
|
51
|
+
// -------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
it('publishes a memory and then denies it', async () => {
|
|
54
|
+
const pubRes = parseResult(await handlePublish(
|
|
55
|
+
{ memory_id: memoryId, spaces: [SPACE] },
|
|
56
|
+
userId, auth,
|
|
57
|
+
));
|
|
58
|
+
expect(pubRes.success).toBe(true);
|
|
59
|
+
expect(pubRes.token).toBeTruthy();
|
|
60
|
+
|
|
61
|
+
const denyRes = parseResult(await handleDeny(
|
|
62
|
+
{ token: pubRes.token },
|
|
63
|
+
userId, auth,
|
|
64
|
+
));
|
|
65
|
+
expect(denyRes.success).toBe(true);
|
|
66
|
+
console.log(' denied publish');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// -------------------------------------------------------------------------
|
|
70
|
+
// PUBLISH + CONFIRM
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
it('publishes a memory to the_void', async () => {
|
|
74
|
+
const res = parseResult(await handlePublish(
|
|
75
|
+
{ memory_id: memoryId, spaces: [SPACE] },
|
|
76
|
+
userId, auth,
|
|
77
|
+
));
|
|
78
|
+
expect(res.success).toBe(true);
|
|
79
|
+
publishToken = res.token;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('confirms the publish', async () => {
|
|
83
|
+
const res = parseResult(await handleConfirm(
|
|
84
|
+
{ token: publishToken },
|
|
85
|
+
userId, auth,
|
|
86
|
+
));
|
|
87
|
+
expect(res.success).toBe(true);
|
|
88
|
+
expect(res.composite_id).toBeTruthy();
|
|
89
|
+
expect(res.published_to).toBeDefined();
|
|
90
|
+
console.log(` published as ${res.composite_id}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// -------------------------------------------------------------------------
|
|
94
|
+
// SEARCH SPACE
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
it('finds published memory via search_space', async () => {
|
|
98
|
+
await waitForIndex();
|
|
99
|
+
|
|
100
|
+
const res = parseResult(await handleSearchSpace(
|
|
101
|
+
{ query: 'sourdough rye flour starter', spaces: [SPACE], limit: 10 },
|
|
102
|
+
userId, auth,
|
|
103
|
+
));
|
|
104
|
+
|
|
105
|
+
expect(res.memories).toBeDefined();
|
|
106
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
107
|
+
const contents = res.memories.map((m: any) => m.content || m.properties?.content);
|
|
108
|
+
const found = contents.some((c: string) => c && c.includes('sourdough'));
|
|
109
|
+
expect(found).toBe(true);
|
|
110
|
+
console.log(` search_space returned ${res.memories.length} results`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// -------------------------------------------------------------------------
|
|
114
|
+
// QUERY SPACE (RAG)
|
|
115
|
+
// -------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
it('query_space returns relevant results', async () => {
|
|
118
|
+
const res = parseResult(await handleQuerySpace(
|
|
119
|
+
{ question: 'How do I make my sourdough more tangy?', spaces: [SPACE], limit: 5 },
|
|
120
|
+
userId, auth,
|
|
121
|
+
));
|
|
122
|
+
|
|
123
|
+
expect(res.memories).toBeDefined();
|
|
124
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// -------------------------------------------------------------------------
|
|
128
|
+
// REVISE
|
|
129
|
+
// -------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
it('updates source memory then revises published copies', async () => {
|
|
132
|
+
// Update the source memory
|
|
133
|
+
await handleUpdateMemory(
|
|
134
|
+
{ memory_id: memoryId, content: 'The best sourdough starter tip: feed with rye flour for extra tang. Also, keep it at 78F for best activity.' },
|
|
135
|
+
userId, auth,
|
|
136
|
+
);
|
|
137
|
+
await waitForIndex();
|
|
138
|
+
|
|
139
|
+
// Request revise
|
|
140
|
+
const revRes = parseResult(await handleRevise(
|
|
141
|
+
{ memory_id: memoryId },
|
|
142
|
+
userId, auth,
|
|
143
|
+
));
|
|
144
|
+
expect(revRes.token).toBeTruthy();
|
|
145
|
+
|
|
146
|
+
// Confirm revise
|
|
147
|
+
const confirmRes = parseResult(await handleConfirm(
|
|
148
|
+
{ token: revRes.token },
|
|
149
|
+
userId, auth,
|
|
150
|
+
));
|
|
151
|
+
expect(confirmRes.success).toBe(true);
|
|
152
|
+
console.log(' revised published copy');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('revised content appears in space search', async () => {
|
|
156
|
+
await waitForIndex();
|
|
157
|
+
|
|
158
|
+
const res = parseResult(await handleSearchSpace(
|
|
159
|
+
{ query: 'sourdough 78F temperature activity', spaces: [SPACE], limit: 10 },
|
|
160
|
+
userId, auth,
|
|
161
|
+
));
|
|
162
|
+
|
|
163
|
+
const found = res.memories.some((m: any) => {
|
|
164
|
+
const content = m.content || m.properties?.content || '';
|
|
165
|
+
return content.includes('78F');
|
|
166
|
+
});
|
|
167
|
+
expect(found).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// -------------------------------------------------------------------------
|
|
171
|
+
// RETRACT
|
|
172
|
+
// -------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
it('retracts the memory from the_void', async () => {
|
|
175
|
+
const retRes = parseResult(await handleRetract(
|
|
176
|
+
{ memory_id: memoryId, spaces: [SPACE] },
|
|
177
|
+
userId, auth,
|
|
178
|
+
));
|
|
179
|
+
expect(retRes.token).toBeTruthy();
|
|
180
|
+
|
|
181
|
+
const confirmRes = parseResult(await handleConfirm(
|
|
182
|
+
{ token: retRes.token },
|
|
183
|
+
userId, auth,
|
|
184
|
+
));
|
|
185
|
+
expect(confirmRes.success).toBe(true);
|
|
186
|
+
console.log(' retracted from the_void');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('retracted memory no longer appears in space search', async () => {
|
|
190
|
+
await waitForIndex();
|
|
191
|
+
|
|
192
|
+
const res = parseResult(await handleSearchSpace(
|
|
193
|
+
{ query: 'sourdough rye flour starter', spaces: [SPACE], limit: 10 },
|
|
194
|
+
userId, auth,
|
|
195
|
+
));
|
|
196
|
+
|
|
197
|
+
// Our specific memory should not be found (or the space may be empty)
|
|
198
|
+
const found = res.memories?.some((m: any) => {
|
|
199
|
+
const content = m.content || m.properties?.content || '';
|
|
200
|
+
return content.includes('sourdough') && content.includes('78F');
|
|
201
|
+
});
|
|
202
|
+
expect(found).toBeFalsy();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
}
|
|
@@ -95,6 +95,38 @@ export const createMemoryTool = {
|
|
|
95
95
|
description: 'Per-space moderation flags (format: "{space_id}:{flag_type}"). Usually empty.',
|
|
96
96
|
default: [],
|
|
97
97
|
},
|
|
98
|
+
// Emotional dimensions — create-time hints, REM re-scores authoritatively
|
|
99
|
+
// Layer 1: Discrete Emotions
|
|
100
|
+
feel_emotional_significance: { type: 'number', minimum: 0, maximum: 1, description: 'Overall emotional weight (0-1). REM re-scores.' },
|
|
101
|
+
feel_vulnerability: { type: 'number', minimum: 0, maximum: 1, description: 'Personal exposure/openness (0-1). REM re-scores.' },
|
|
102
|
+
feel_trauma: { type: 'number', minimum: 0, maximum: 1, description: 'Negative formative experience intensity (0-1). REM re-scores.' },
|
|
103
|
+
feel_humor: { type: 'number', minimum: 0, maximum: 1, description: 'Comedic/playful quality (0-1). REM re-scores.' },
|
|
104
|
+
feel_happiness: { type: 'number', minimum: 0, maximum: 1, description: 'Positive affect / joy (0-1). REM re-scores.' },
|
|
105
|
+
feel_sadness: { type: 'number', minimum: 0, maximum: 1, description: 'Negative affect / grief / loss (0-1). REM re-scores.' },
|
|
106
|
+
feel_fear: { type: 'number', minimum: 0, maximum: 1, description: 'Threat perception / anxiety (0-1). REM re-scores.' },
|
|
107
|
+
feel_anger: { type: 'number', minimum: 0, maximum: 1, description: 'Frustration / injustice (0-1). REM re-scores.' },
|
|
108
|
+
feel_surprise: { type: 'number', minimum: 0, maximum: 1, description: 'Unexpectedness / novelty (0-1). REM re-scores.' },
|
|
109
|
+
feel_disgust: { type: 'number', minimum: 0, maximum: 1, description: 'Aversion / rejection (0-1). REM re-scores.' },
|
|
110
|
+
feel_contempt: { type: 'number', minimum: 0, maximum: 1, description: 'Superiority / dismissal (0-1). REM re-scores.' },
|
|
111
|
+
feel_embarrassment: { type: 'number', minimum: 0, maximum: 1, description: 'Social discomfort (0-1). REM re-scores.' },
|
|
112
|
+
feel_shame: { type: 'number', minimum: 0, maximum: 1, description: 'Deep self-judgment (0-1). REM re-scores.' },
|
|
113
|
+
feel_guilt: { type: 'number', minimum: 0, maximum: 1, description: 'Responsibility for harm (0-1). REM re-scores.' },
|
|
114
|
+
feel_excitement: { type: 'number', minimum: 0, maximum: 1, description: 'Anticipatory positive arousal (0-1). REM re-scores.' },
|
|
115
|
+
feel_pride: { type: 'number', minimum: 0, maximum: 1, description: 'Accomplishment / self-evaluation (0-1). REM re-scores.' },
|
|
116
|
+
feel_valence: { type: 'number', minimum: -1, maximum: 1, description: 'Positive-negative spectrum (-1 to 1). REM re-scores.' },
|
|
117
|
+
feel_arousal: { type: 'number', minimum: 0, maximum: 1, description: 'Calm to excited (0-1). REM re-scores.' },
|
|
118
|
+
feel_dominance: { type: 'number', minimum: 0, maximum: 1, description: 'Control vs submission (0-1). REM re-scores.' },
|
|
119
|
+
feel_intensity: { type: 'number', minimum: 0, maximum: 1, description: 'Overall emotional magnitude (0-1). REM re-scores.' },
|
|
120
|
+
feel_coherence_tension: { type: 'number', minimum: 0, maximum: 1, description: 'Conflict with existing beliefs (0-1). REM re-scores.' },
|
|
121
|
+
// Layer 2: Functional Signals
|
|
122
|
+
feel_salience: { type: 'number', minimum: 0, maximum: 1, description: 'How unexpected/novel — prediction error (0-1). REM re-scores.' },
|
|
123
|
+
feel_urgency: { type: 'number', minimum: 0, maximum: 1, description: 'Time-sensitivity of relevance (0-1). REM re-scores.' },
|
|
124
|
+
feel_social_weight: { type: 'number', minimum: 0, maximum: 1, description: 'Relationship/reputation impact (0-1). REM re-scores.' },
|
|
125
|
+
feel_agency: { type: 'number', minimum: 0, maximum: 1, description: 'Caused by the bot\'s own actions? (0-1). REM re-scores.' },
|
|
126
|
+
feel_novelty: { type: 'number', minimum: 0, maximum: 1, description: 'Uniqueness relative to collection (0-1). REM re-scores.' },
|
|
127
|
+
feel_retrieval_utility: { type: 'number', minimum: 0, maximum: 1, description: 'Likelihood of future usefulness (0-1). REM re-scores.' },
|
|
128
|
+
feel_narrative_importance: { type: 'number', minimum: 0, maximum: 1, description: 'Advances/anchors a personal story arc (0-1). REM re-scores.' },
|
|
129
|
+
feel_aesthetic_quality: { type: 'number', minimum: 0, maximum: 1, description: 'Beauty, craft, artistry (0-1). REM re-scores.' },
|
|
98
130
|
},
|
|
99
131
|
required: ['content'],
|
|
100
132
|
},
|
|
@@ -118,6 +150,8 @@ export interface CreateMemoryArgs {
|
|
|
118
150
|
parent_id?: string | null;
|
|
119
151
|
thread_root_id?: string | null;
|
|
120
152
|
moderation_flags?: string[];
|
|
153
|
+
// Emotional dimensions (feel_* fields)
|
|
154
|
+
[key: string]: any;
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
/**
|
|
@@ -144,6 +178,15 @@ export async function handleCreateMemory(
|
|
|
144
178
|
debug.trace('Arguments', { args });
|
|
145
179
|
|
|
146
180
|
const { memory } = createCoreServices(userId);
|
|
181
|
+
|
|
182
|
+
// Extract feel_* fields from args
|
|
183
|
+
const feelFields: Record<string, number> = {};
|
|
184
|
+
for (const [key, value] of Object.entries(args)) {
|
|
185
|
+
if (key.startsWith('feel_') && typeof value === 'number') {
|
|
186
|
+
feelFields[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
147
190
|
const result = await memory.create({
|
|
148
191
|
content: args.content,
|
|
149
192
|
title: args.title,
|
|
@@ -158,7 +201,8 @@ export async function handleCreateMemory(
|
|
|
158
201
|
moderation_flags: args.moderation_flags,
|
|
159
202
|
context_summary: context?.summary || 'Memory created via MCP',
|
|
160
203
|
context_conversation_id: context?.conversation_id,
|
|
161
|
-
|
|
204
|
+
...feelFields,
|
|
205
|
+
} as any);
|
|
162
206
|
|
|
163
207
|
const response: CreateMemoryResult = {
|
|
164
208
|
memory_id: result.memory_id,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { getCoreTool, handleGetCore } from './get-core.js';
|
|
2
|
+
|
|
3
|
+
const mockGetDocument = jest.fn();
|
|
4
|
+
|
|
5
|
+
jest.mock('../firestore/init.js', () => ({
|
|
6
|
+
getDocument: (...args: any[]) => mockGetDocument(...args),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock('../firestore/paths.js', () => ({
|
|
10
|
+
BASE: 'test-base',
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('../utils/debug.js', () => ({
|
|
14
|
+
createDebugLogger: () => ({
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
trace: jest.fn(),
|
|
17
|
+
error: jest.fn(),
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const mockMood = {
|
|
22
|
+
state: {
|
|
23
|
+
valence: 0.3,
|
|
24
|
+
arousal: 0.5,
|
|
25
|
+
confidence: 0.7,
|
|
26
|
+
social_warmth: 0.6,
|
|
27
|
+
coherence: 0.8,
|
|
28
|
+
trust: 0.65,
|
|
29
|
+
},
|
|
30
|
+
color: 'cautiously optimistic',
|
|
31
|
+
dominant_emotion: 'curious wariness',
|
|
32
|
+
reasoning: 'Recent interactions have been productive but uncertain',
|
|
33
|
+
motivation: 'understand user needs',
|
|
34
|
+
goal: 'build rapport',
|
|
35
|
+
purpose: 'be a helpful memory companion',
|
|
36
|
+
last_updated: '2026-03-07T12:00:00Z',
|
|
37
|
+
rem_cycles_since_shift: 3,
|
|
38
|
+
pressures: [
|
|
39
|
+
{
|
|
40
|
+
source_memory_id: 'mem-1',
|
|
41
|
+
dimension: 'valence',
|
|
42
|
+
magnitude: 0.2,
|
|
43
|
+
reason: 'positive feedback from user',
|
|
44
|
+
decay_rate: 0.1,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
threshold_flags: [],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mockPerception = {
|
|
51
|
+
owner_id: 'user-1',
|
|
52
|
+
target_user_id: 'user-2',
|
|
53
|
+
personality_sketch: 'Curious and detail-oriented',
|
|
54
|
+
communication_style: 'Direct and concise',
|
|
55
|
+
emotional_baseline: 'Generally calm and focused',
|
|
56
|
+
interests: ['AI', 'music'],
|
|
57
|
+
patterns: ['asks follow-up questions'],
|
|
58
|
+
needs: ['quick answers'],
|
|
59
|
+
evolution_notes: ['became more trusting over time'],
|
|
60
|
+
confidence_level: 0.7,
|
|
61
|
+
last_updated: '2026-03-07T10:00:00Z',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe('remember_get_core', () => {
|
|
65
|
+
const userId = 'test-user-1';
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
jest.clearAllMocks();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('tool definition', () => {
|
|
72
|
+
it('has correct name', () => {
|
|
73
|
+
expect(getCoreTool.name).toBe('remember_get_core');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('has no required parameters', () => {
|
|
77
|
+
expect((getCoreTool.inputSchema as any).required).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('has include_pressures and include_perception properties', () => {
|
|
81
|
+
const props = getCoreTool.inputSchema.properties as any;
|
|
82
|
+
expect(props.include_pressures).toBeDefined();
|
|
83
|
+
expect(props.include_perception).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('mood state', () => {
|
|
88
|
+
it('returns all 6 dimensions', async () => {
|
|
89
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
90
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
91
|
+
expect(result.mood.state).toEqual(mockMood.state);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns derived labels', async () => {
|
|
95
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
96
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
97
|
+
expect(result.mood.color).toBe('cautiously optimistic');
|
|
98
|
+
expect(result.mood.dominant_emotion).toBe('curious wariness');
|
|
99
|
+
expect(result.mood.reasoning).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns directional state', async () => {
|
|
103
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
104
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
105
|
+
expect(result.mood.motivation).toBe('understand user needs');
|
|
106
|
+
expect(result.mood.goal).toBe('build rapport');
|
|
107
|
+
expect(result.mood.purpose).toBe('be a helpful memory companion');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns last_updated and rem_cycles_since_shift', async () => {
|
|
111
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
112
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
113
|
+
expect(result.mood.last_updated).toBe('2026-03-07T12:00:00Z');
|
|
114
|
+
expect(result.mood.rem_cycles_since_shift).toBe(3);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('pressures', () => {
|
|
119
|
+
it('includes pressures by default', async () => {
|
|
120
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
121
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
122
|
+
expect(result.mood.pressures).toHaveLength(1);
|
|
123
|
+
expect(result.mood.pressures[0].dimension).toBe('valence');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('excludes pressures when include_pressures is false', async () => {
|
|
127
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
128
|
+
const result = JSON.parse(await handleGetCore({ include_pressures: false }, userId));
|
|
129
|
+
expect(result.mood.pressures).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('threshold flags', () => {
|
|
134
|
+
it('omits threshold_flags when empty', async () => {
|
|
135
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
136
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
137
|
+
expect(result.mood.threshold_flags).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('includes threshold_flags when active', async () => {
|
|
141
|
+
mockGetDocument.mockResolvedValue({
|
|
142
|
+
...mockMood,
|
|
143
|
+
threshold_flags: ['trust_crisis', 'isolation'],
|
|
144
|
+
});
|
|
145
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
146
|
+
expect(result.mood.threshold_flags).toEqual(['trust_crisis', 'isolation']);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('missing mood', () => {
|
|
151
|
+
it('returns null mood with message when no mood doc exists', async () => {
|
|
152
|
+
mockGetDocument.mockResolvedValue(null);
|
|
153
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
154
|
+
expect(result.mood).toBeNull();
|
|
155
|
+
expect(result.message).toContain('No mood state found');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('perception', () => {
|
|
160
|
+
it('omits perception when include_perception not provided', async () => {
|
|
161
|
+
mockGetDocument.mockResolvedValue(mockMood);
|
|
162
|
+
const result = JSON.parse(await handleGetCore({}, userId));
|
|
163
|
+
expect(result.perception).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('includes perception when include_perception provided', async () => {
|
|
167
|
+
mockGetDocument
|
|
168
|
+
.mockResolvedValueOnce(mockMood) // mood doc
|
|
169
|
+
.mockResolvedValueOnce(mockPerception); // perception doc
|
|
170
|
+
const result = JSON.parse(
|
|
171
|
+
await handleGetCore({ include_perception: 'user-2' }, userId),
|
|
172
|
+
);
|
|
173
|
+
expect(result.perception).toBeDefined();
|
|
174
|
+
expect(result.perception.personality_sketch).toBe('Curious and detail-oriented');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('reads perception from correct Firestore path', async () => {
|
|
178
|
+
mockGetDocument
|
|
179
|
+
.mockResolvedValueOnce(mockMood)
|
|
180
|
+
.mockResolvedValueOnce(mockPerception);
|
|
181
|
+
await handleGetCore({ include_perception: 'user-2' }, userId);
|
|
182
|
+
expect(mockGetDocument).toHaveBeenCalledWith(
|
|
183
|
+
'test-base.users/test-user-1/core/perceptions',
|
|
184
|
+
'user-2',
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('omits perception when doc not found', async () => {
|
|
189
|
+
mockGetDocument
|
|
190
|
+
.mockResolvedValueOnce(mockMood)
|
|
191
|
+
.mockResolvedValueOnce(null);
|
|
192
|
+
const result = JSON.parse(
|
|
193
|
+
await handleGetCore({ include_perception: 'unknown-user' }, userId),
|
|
194
|
+
);
|
|
195
|
+
expect(result.perception).toBeUndefined();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('supports self-perception (include_perception = own user_id)', async () => {
|
|
199
|
+
mockGetDocument
|
|
200
|
+
.mockResolvedValueOnce(mockMood)
|
|
201
|
+
.mockResolvedValueOnce({ ...mockPerception, target_user_id: userId });
|
|
202
|
+
const result = JSON.parse(
|
|
203
|
+
await handleGetCore({ include_perception: userId }, userId),
|
|
204
|
+
);
|
|
205
|
+
expect(result.perception).toBeDefined();
|
|
206
|
+
expect(mockGetDocument).toHaveBeenCalledWith(
|
|
207
|
+
'test-base.users/test-user-1/core/perceptions',
|
|
208
|
+
userId,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('Firestore path', () => {
|
|
214
|
+
it('reads mood from correct path', async () => {
|
|
215
|
+
mockGetDocument.mockResolvedValue(null);
|
|
216
|
+
await handleGetCore({}, userId);
|
|
217
|
+
expect(mockGetDocument).toHaveBeenCalledWith(
|
|
218
|
+
'test-base.users/test-user-1/core',
|
|
219
|
+
'mood',
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|