@prmichaelsen/remember-mcp 3.19.3 → 4.0.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.
- package/AGENT.md +10 -3
- package/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/agent/commands/acp.artifact-glossary.md +530 -0
- package/agent/commands/acp.artifact-reference.md +591 -0
- package/agent/commands/acp.artifact-research.md +594 -0
- package/agent/commands/acp.audit.md +345 -0
- package/agent/commands/acp.clarification-address.md +185 -88
- package/agent/commands/acp.clarification-capture.md +44 -44
- package/agent/commands/acp.clarification-create.md +41 -42
- package/agent/commands/acp.command-create.md +49 -49
- package/agent/commands/acp.design-create.md +53 -35
- package/agent/commands/acp.design-reference.md +42 -42
- package/agent/commands/acp.handoff.md +35 -35
- package/agent/commands/acp.index.md +47 -47
- package/agent/commands/acp.init.md +105 -69
- package/agent/commands/acp.package-create.md +41 -41
- package/agent/commands/acp.package-info.md +40 -40
- package/agent/commands/acp.package-install.md +48 -48
- package/agent/commands/acp.package-list.md +40 -40
- package/agent/commands/acp.package-publish.md +62 -62
- package/agent/commands/acp.package-remove.md +41 -41
- package/agent/commands/acp.package-search.md +48 -48
- package/agent/commands/acp.package-update.md +50 -50
- package/agent/commands/acp.package-validate.md +52 -52
- package/agent/commands/acp.pattern-create.md +61 -43
- package/agent/commands/acp.plan.md +70 -47
- package/agent/commands/acp.proceed.md +188 -66
- package/agent/commands/acp.project-create.md +42 -42
- package/agent/commands/acp.project-info.md +46 -46
- package/agent/commands/acp.project-list.md +41 -41
- package/agent/commands/acp.project-remove.md +36 -36
- package/agent/commands/acp.project-set.md +33 -33
- package/agent/commands/acp.project-update.md +57 -57
- package/agent/commands/acp.projects-restore.md +37 -37
- package/agent/commands/acp.projects-sync.md +39 -39
- package/agent/commands/acp.report.md +50 -50
- package/agent/commands/acp.resume.md +36 -36
- package/agent/commands/acp.sessions.md +46 -46
- package/agent/commands/acp.status.md +43 -43
- package/agent/commands/acp.sync.md +109 -56
- package/agent/commands/acp.task-create.md +51 -49
- package/agent/commands/acp.update.md +66 -45
- package/agent/commands/acp.validate.md +110 -52
- package/agent/commands/acp.version-check-for-updates.md +40 -40
- package/agent/commands/acp.version-check.md +36 -36
- package/agent/commands/acp.version-update.md +43 -43
- package/agent/commands/command.template.md +40 -40
- package/agent/commands/git.commit.md +28 -28
- package/agent/commands/git.init.md +48 -48
- package/agent/design/design.template.md +9 -9
- package/agent/design/local.admin-debugging-tools.md +242 -0
- package/agent/design/requirements.template.md +8 -8
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/index/local.main.yaml +48 -0
- package/agent/manifest.yaml +64 -0
- package/agent/milestones/milestone-1-{title}.template.md +8 -8
- package/agent/milestones/milestone-22-admin-debugging-tools.md +61 -0
- package/agent/milestones/milestone-23-trust-level-protection.md +122 -0
- package/agent/patterns/pattern.template.md +22 -22
- package/agent/progress.template.yaml +13 -3
- package/agent/progress.yaml +173 -3
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.project-update.sh +5 -6
- package/agent/tasks/milestone-22-admin-debugging-tools/task-520-admin-gate-infrastructure.md +99 -0
- package/agent/tasks/milestone-22-admin-debugging-tools/task-521-schema-and-collection-tools.md +108 -0
- package/agent/tasks/milestone-22-admin-debugging-tools/task-522-memory-inspection-tools.md +120 -0
- package/agent/tasks/milestone-22-admin-debugging-tools/task-523-user-inspection-tools.md +126 -0
- package/agent/tasks/milestone-22-admin-debugging-tools/task-524-health-and-drift-tools.md +120 -0
- package/agent/tasks/milestone-23-trust-level-protection/task-525-remove-trust-from-create-update.md +69 -0
- package/agent/tasks/milestone-23-trust-level-protection/task-526-add-request-set-trust-level-tool.md +108 -0
- package/agent/tasks/milestone-23-trust-level-protection/task-527-update-confirm-deny-secret-token.md +60 -0
- package/agent/tasks/milestone-23-trust-level-protection/task-528-update-trust-scale-references.md +73 -0
- package/agent/tasks/milestone-23-trust-level-protection/task-529-version-bump-and-release.md +87 -0
- package/agent/tasks/task-1-{title}.template.md +18 -18
- package/dist/server-factory.js +779 -87
- package/dist/server.js +141 -41
- package/dist/services/trust-validator.d.ts +16 -14
- package/dist/tools/admin-collection-stats.d.ts +24 -0
- package/dist/tools/admin-detect-weaviate-drift.d.ts +26 -0
- package/dist/tools/admin-get-weaviate-schema.d.ts +24 -0
- package/dist/tools/admin-health-drift.spec.d.ts +5 -0
- package/dist/tools/admin-health.d.ts +15 -0
- package/dist/tools/admin-inspect-memory.d.ts +29 -0
- package/dist/tools/admin-inspect-user.d.ts +73 -0
- package/dist/tools/admin-inspect-user.spec.d.ts +5 -0
- package/dist/tools/admin-list-collections.d.ts +23 -0
- package/dist/tools/admin-memory-inspection.spec.d.ts +7 -0
- package/dist/tools/admin-schema-collection.spec.d.ts +8 -0
- package/dist/tools/admin-search-across-users.d.ts +42 -0
- package/dist/tools/confirm.d.ts +1 -0
- package/dist/tools/confirm.spec.d.ts +5 -0
- package/dist/tools/create-internal-memory.d.ts +0 -7
- package/dist/tools/create-memory.d.ts +0 -7
- package/dist/tools/deny.d.ts +1 -0
- package/dist/tools/deny.spec.d.ts +5 -0
- package/dist/tools/query-memory.d.ts +2 -0
- package/dist/tools/request-set-trust-level.d.ts +32 -0
- package/dist/tools/request-set-trust-level.spec.d.ts +2 -0
- package/dist/tools/search-memory.d.ts +2 -0
- package/dist/tools/update-internal-memory.d.ts +0 -6
- package/dist/tools/update-memory.d.ts +0 -7
- package/dist/utils/admin.d.ts +21 -0
- package/dist/utils/admin.spec.d.ts +2 -0
- package/package.json +2 -2
- package/src/server-factory.ts +137 -42
- package/src/server.ts +6 -0
- package/src/services/trust-validator.spec.ts +57 -51
- package/src/services/trust-validator.ts +28 -26
- package/src/tools/admin-collection-stats.ts +67 -0
- package/src/tools/admin-detect-weaviate-drift.ts +110 -0
- package/src/tools/admin-get-weaviate-schema.ts +68 -0
- package/src/tools/admin-health-drift.spec.ts +193 -0
- package/src/tools/admin-health.ts +88 -0
- package/src/tools/admin-inspect-memory.ts +86 -0
- package/src/tools/admin-inspect-user.spec.ts +130 -0
- package/src/tools/admin-inspect-user.ts +148 -0
- package/src/tools/admin-list-collections.ts +73 -0
- package/src/tools/admin-memory-inspection.spec.ts +206 -0
- package/src/tools/admin-schema-collection.spec.ts +167 -0
- package/src/tools/admin-search-across-users.ts +104 -0
- package/src/tools/confirm.spec.ts +108 -0
- package/src/tools/confirm.ts +24 -1
- package/src/tools/create-internal-memory.ts +0 -3
- package/src/tools/create-memory.spec.ts +6 -2
- package/src/tools/create-memory.ts +1 -9
- package/src/tools/deny.spec.ts +59 -0
- package/src/tools/deny.ts +6 -1
- package/src/tools/ghost-config.ts +19 -19
- package/src/tools/query-memory.ts +4 -2
- package/src/tools/request-set-trust-level.spec.ts +87 -0
- package/src/tools/request-set-trust-level.ts +107 -0
- package/src/tools/search-memory.ts +4 -2
- package/src/tools/update-internal-memory.ts +0 -3
- package/src/tools/update-memory.ts +0 -8
- package/src/types/memory.ts +1 -1
- package/src/utils/admin.spec.ts +70 -0
- package/src/utils/admin.ts +27 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for admin schema and collection tools:
|
|
3
|
+
* - remember_admin_get_weaviate_schema
|
|
4
|
+
* - remember_admin_list_collections
|
|
5
|
+
* - remember_admin_collection_stats
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { handleAdminGetWeaviateSchema } from './admin-get-weaviate-schema.js';
|
|
9
|
+
import { handleAdminListCollections } from './admin-list-collections.js';
|
|
10
|
+
import { handleAdminCollectionStats } from './admin-collection-stats.js';
|
|
11
|
+
|
|
12
|
+
// Mock the Weaviate client
|
|
13
|
+
const mockGet = jest.fn();
|
|
14
|
+
const mockListAll = jest.fn();
|
|
15
|
+
const mockConfigGet = jest.fn();
|
|
16
|
+
const mockLength = jest.fn();
|
|
17
|
+
|
|
18
|
+
jest.mock('../weaviate/client.js', () => ({
|
|
19
|
+
getWeaviateClient: () => ({
|
|
20
|
+
collections: {
|
|
21
|
+
get: (name: string) => {
|
|
22
|
+
mockGet(name);
|
|
23
|
+
return {
|
|
24
|
+
config: { get: mockConfigGet },
|
|
25
|
+
length: mockLength,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
listAll: mockListAll,
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const sampleConfig = {
|
|
34
|
+
properties: [
|
|
35
|
+
{ name: 'content', dataType: 'text', description: 'Memory content', indexFilterable: true, indexSearchable: true, tokenization: 'word' },
|
|
36
|
+
{ name: 'weight', dataType: 'number', description: 'Significance', indexFilterable: true, indexSearchable: false, tokenization: null },
|
|
37
|
+
],
|
|
38
|
+
vectorizers: [{ name: 'default', type: 'text2vec-openai' }],
|
|
39
|
+
generative: null,
|
|
40
|
+
multiTenancy: { enabled: false },
|
|
41
|
+
replication: { factor: 1 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe('remember_admin_get_weaviate_schema', () => {
|
|
45
|
+
const originalEnv = process.env.ADMIN_USER_IDS;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
process.env.ADMIN_USER_IDS = 'admin_user';
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
mockConfigGet.mockResolvedValue(sampleConfig);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
if (originalEnv !== undefined) {
|
|
55
|
+
process.env.ADMIN_USER_IDS = originalEnv;
|
|
56
|
+
} else {
|
|
57
|
+
delete process.env.ADMIN_USER_IDS;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns schema for admin user', async () => {
|
|
62
|
+
const result = await handleAdminGetWeaviateSchema(
|
|
63
|
+
{ collection_name: 'Memory_users_test' },
|
|
64
|
+
'admin_user'
|
|
65
|
+
);
|
|
66
|
+
const parsed = JSON.parse(result);
|
|
67
|
+
expect(parsed.collection_name).toBe('Memory_users_test');
|
|
68
|
+
expect(parsed.properties).toHaveLength(2);
|
|
69
|
+
expect(parsed.properties[0].name).toBe('content');
|
|
70
|
+
expect(parsed.properties[1].name).toBe('weight');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns permission error for non-admin user', async () => {
|
|
74
|
+
const result = await handleAdminGetWeaviateSchema(
|
|
75
|
+
{ collection_name: 'Memory_users_test' },
|
|
76
|
+
'regular_user'
|
|
77
|
+
);
|
|
78
|
+
const parsed = JSON.parse(result);
|
|
79
|
+
expect(parsed.isError).toBe(true);
|
|
80
|
+
expect(parsed.content[0].text).toContain('Permission denied');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('remember_admin_list_collections', () => {
|
|
85
|
+
const originalEnv = process.env.ADMIN_USER_IDS;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
process.env.ADMIN_USER_IDS = 'admin_user';
|
|
89
|
+
jest.clearAllMocks();
|
|
90
|
+
mockListAll.mockResolvedValue([
|
|
91
|
+
{ name: 'Memory_users_abc123' },
|
|
92
|
+
{ name: 'Memory_users_def456' },
|
|
93
|
+
{ name: 'Memory_spaces_public' },
|
|
94
|
+
{ name: 'Memory_groups_team1' },
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
if (originalEnv !== undefined) {
|
|
100
|
+
process.env.ADMIN_USER_IDS = originalEnv;
|
|
101
|
+
} else {
|
|
102
|
+
delete process.env.ADMIN_USER_IDS;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('lists all collections with type categorization', async () => {
|
|
107
|
+
const result = await handleAdminListCollections({}, 'admin_user');
|
|
108
|
+
const parsed = JSON.parse(result);
|
|
109
|
+
expect(parsed.total).toBe(4);
|
|
110
|
+
expect(parsed.collections[0]).toEqual({ name: 'Memory_users_abc123', type: 'user' });
|
|
111
|
+
expect(parsed.collections[2]).toEqual({ name: 'Memory_spaces_public', type: 'space' });
|
|
112
|
+
expect(parsed.collections[3]).toEqual({ name: 'Memory_groups_team1', type: 'group' });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('filters collections by prefix', async () => {
|
|
116
|
+
const result = await handleAdminListCollections({ filter: 'Memory_users_' }, 'admin_user');
|
|
117
|
+
const parsed = JSON.parse(result);
|
|
118
|
+
expect(parsed.total).toBe(2);
|
|
119
|
+
expect(parsed.collections.every((c: any) => c.type === 'user')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('returns permission error for non-admin user', async () => {
|
|
123
|
+
const result = await handleAdminListCollections({}, 'regular_user');
|
|
124
|
+
const parsed = JSON.parse(result);
|
|
125
|
+
expect(parsed.isError).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('remember_admin_collection_stats', () => {
|
|
130
|
+
const originalEnv = process.env.ADMIN_USER_IDS;
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
process.env.ADMIN_USER_IDS = 'admin_user';
|
|
134
|
+
jest.clearAllMocks();
|
|
135
|
+
mockConfigGet.mockResolvedValue(sampleConfig);
|
|
136
|
+
mockLength.mockResolvedValue(42);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
if (originalEnv !== undefined) {
|
|
141
|
+
process.env.ADMIN_USER_IDS = originalEnv;
|
|
142
|
+
} else {
|
|
143
|
+
delete process.env.ADMIN_USER_IDS;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns stats for admin user', async () => {
|
|
148
|
+
const result = await handleAdminCollectionStats(
|
|
149
|
+
{ collection_name: 'Memory_users_test' },
|
|
150
|
+
'admin_user'
|
|
151
|
+
);
|
|
152
|
+
const parsed = JSON.parse(result);
|
|
153
|
+
expect(parsed.collection_name).toBe('Memory_users_test');
|
|
154
|
+
expect(parsed.object_count).toBe(42);
|
|
155
|
+
expect(parsed.property_count).toBe(2);
|
|
156
|
+
expect(parsed.properties).toEqual(['content', 'weight']);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('returns permission error for non-admin user', async () => {
|
|
160
|
+
const result = await handleAdminCollectionStats(
|
|
161
|
+
{ collection_name: 'Memory_users_test' },
|
|
162
|
+
'regular_user'
|
|
163
|
+
);
|
|
164
|
+
const parsed = JSON.parse(result);
|
|
165
|
+
expect(parsed.isError).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_admin_search_across_users tool
|
|
3
|
+
* Searches memories across multiple user tenants.
|
|
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 { isAdmin, adminPermissionError } from '../utils/admin.js';
|
|
10
|
+
import { createCoreServices } from '../core-services.js';
|
|
11
|
+
|
|
12
|
+
export const adminSearchAcrossUsersTool = {
|
|
13
|
+
name: 'remember_admin_search_across_users',
|
|
14
|
+
description: `[Admin] Search memories across multiple user tenants.
|
|
15
|
+
|
|
16
|
+
Results include which user each memory belongs to.
|
|
17
|
+
Requires explicit user_id array — no "all users" search.
|
|
18
|
+
Requires admin access (ADMIN_USER_IDS).`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
user_ids: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: { type: 'string' },
|
|
25
|
+
description: 'User IDs to search across (required, 1 or more)',
|
|
26
|
+
},
|
|
27
|
+
query: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Search query (hybrid search)',
|
|
30
|
+
},
|
|
31
|
+
limit: {
|
|
32
|
+
type: 'number',
|
|
33
|
+
description: 'Max results across all users. Default: 10',
|
|
34
|
+
},
|
|
35
|
+
content_type: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Optional content type filter',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['user_ids', 'query'],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export interface AdminSearchAcrossUsersArgs {
|
|
45
|
+
user_ids: string[];
|
|
46
|
+
query: string;
|
|
47
|
+
limit?: number;
|
|
48
|
+
content_type?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function handleAdminSearchAcrossUsers(
|
|
52
|
+
args: AdminSearchAcrossUsersArgs,
|
|
53
|
+
userId: string,
|
|
54
|
+
_authContext?: AuthContext
|
|
55
|
+
): Promise<string> {
|
|
56
|
+
const debug = createDebugLogger({ tool: 'remember_admin_search_across_users', userId, operation: 'search across users' });
|
|
57
|
+
try {
|
|
58
|
+
if (!isAdmin(userId)) {
|
|
59
|
+
return JSON.stringify(adminPermissionError());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!args.user_ids || args.user_ids.length === 0) {
|
|
63
|
+
return JSON.stringify({ error: 'user_ids array is required and must not be empty' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
debug.info('Tool invoked', { user_ids: args.user_ids, query: args.query, limit: args.limit });
|
|
67
|
+
|
|
68
|
+
const limit = args.limit ?? 10;
|
|
69
|
+
const allResults: Array<{ user_id: string; memory: any; score?: number }> = [];
|
|
70
|
+
const warnings: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (const targetUserId of args.user_ids) {
|
|
73
|
+
try {
|
|
74
|
+
const services = createCoreServices(targetUserId);
|
|
75
|
+
const searchResult = await services.memory.search({
|
|
76
|
+
query: args.query,
|
|
77
|
+
limit,
|
|
78
|
+
filters: args.content_type ? { types: [args.content_type as any] } : undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
for (const memory of searchResult.memories) {
|
|
82
|
+
allResults.push({
|
|
83
|
+
user_id: targetUserId,
|
|
84
|
+
memory,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
warnings.push(`User ${targetUserId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Sort by relevance (first results from each user are highest relevance)
|
|
93
|
+
// and limit to requested total
|
|
94
|
+
const limited = allResults.slice(0, limit);
|
|
95
|
+
|
|
96
|
+
return JSON.stringify({
|
|
97
|
+
total: limited.length,
|
|
98
|
+
results: limited,
|
|
99
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
100
|
+
}, null, 2);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return handleToolError(error, { toolName: 'remember_admin_search_across_users', userId, operation: 'search across users' });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for confirm tool — secret_token passthrough and set_trust_level handling.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { handleConfirm } from './confirm.js';
|
|
6
|
+
|
|
7
|
+
// ─── Mocks ──────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
jest.mock('../core-services.js', () => ({
|
|
10
|
+
createCoreServices: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('../weaviate/client.js', () => ({
|
|
14
|
+
getWeaviateClient: jest.fn(),
|
|
15
|
+
getMemoryCollectionName: jest.fn((userId: string) => `Memory_users_${userId}`),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('../utils/debug.js', () => ({
|
|
19
|
+
createDebugLogger: jest.fn(() => ({
|
|
20
|
+
info: jest.fn(),
|
|
21
|
+
debug: jest.fn(),
|
|
22
|
+
trace: jest.fn(),
|
|
23
|
+
error: jest.fn(),
|
|
24
|
+
time: jest.fn((_: string, fn: () => any) => fn()),
|
|
25
|
+
})),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
jest.mock('../utils/error-handler.js', () => ({
|
|
29
|
+
handleToolError: jest.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import { createCoreServices } from '../core-services.js';
|
|
33
|
+
const mockCreateCoreServices = createCoreServices as jest.MockedFunction<any>;
|
|
34
|
+
|
|
35
|
+
// ─── Tests ───────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
describe('confirm tool', () => {
|
|
38
|
+
let mockConfirm: jest.Mock;
|
|
39
|
+
let mockValidateToken: jest.Mock;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
mockConfirm = jest.fn();
|
|
44
|
+
mockValidateToken = jest.fn();
|
|
45
|
+
|
|
46
|
+
mockCreateCoreServices.mockReturnValue({
|
|
47
|
+
space: { confirm: mockConfirm },
|
|
48
|
+
token: { validateToken: mockValidateToken, confirmRequest: jest.fn() },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('passes secret_token through to space.confirm when provided', async () => {
|
|
53
|
+
mockValidateToken.mockResolvedValue({ action: 'publish_memory' });
|
|
54
|
+
mockConfirm.mockResolvedValue({
|
|
55
|
+
action: 'publish_memory',
|
|
56
|
+
success: true,
|
|
57
|
+
composite_id: 'u1.m1',
|
|
58
|
+
published_to: ['spaces: public'],
|
|
59
|
+
space_ids: ['public'],
|
|
60
|
+
group_ids: [],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await handleConfirm({ token: 'tok-1', secret_token: 'sec-abc' }, 'user-1');
|
|
64
|
+
|
|
65
|
+
expect(mockConfirm).toHaveBeenCalledWith({ token: 'tok-1', secret_token: 'sec-abc' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('works without secret_token (backward compatible)', async () => {
|
|
69
|
+
mockValidateToken.mockResolvedValue({ action: 'publish_memory' });
|
|
70
|
+
mockConfirm.mockResolvedValue({
|
|
71
|
+
action: 'publish_memory',
|
|
72
|
+
success: true,
|
|
73
|
+
composite_id: 'u1.m1',
|
|
74
|
+
published_to: ['spaces: public'],
|
|
75
|
+
space_ids: ['public'],
|
|
76
|
+
group_ids: [],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await handleConfirm({ token: 'tok-1' }, 'user-1');
|
|
80
|
+
|
|
81
|
+
expect(mockConfirm).toHaveBeenCalledWith({ token: 'tok-1', secret_token: undefined });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('handles set_trust_level action via memory.confirmSetTrustLevel', async () => {
|
|
85
|
+
const mockConfirmSetTrustLevel = jest.fn().mockResolvedValue({
|
|
86
|
+
memory_id: 'mem-1',
|
|
87
|
+
previous_trust_level: 'normal',
|
|
88
|
+
new_trust_level: 'core',
|
|
89
|
+
updated_at: '2026-03-20T00:00:00Z',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
mockValidateToken.mockResolvedValue({ action: 'set_trust_level' });
|
|
93
|
+
mockCreateCoreServices.mockReturnValue({
|
|
94
|
+
space: { confirm: mockConfirm },
|
|
95
|
+
token: { validateToken: mockValidateToken, confirmRequest: jest.fn() },
|
|
96
|
+
memory: { confirmSetTrustLevel: mockConfirmSetTrustLevel },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = JSON.parse(await handleConfirm({ token: 'tok-trust' }, 'user-1') as string);
|
|
100
|
+
|
|
101
|
+
expect(result.success).toBe(true);
|
|
102
|
+
expect(result.memory_id).toBe('mem-1');
|
|
103
|
+
expect(result.previous_trust_level).toBe('normal');
|
|
104
|
+
expect(result.new_trust_level).toBe('core');
|
|
105
|
+
expect(result.message).toBe('Trust level changed from normal to core');
|
|
106
|
+
expect(mockConfirmSetTrustLevel).toHaveBeenCalledWith('tok-trust');
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/tools/confirm.ts
CHANGED
|
@@ -53,6 +53,10 @@ Violating these requirements bypasses user consent and is a security violation.`
|
|
|
53
53
|
type: 'string',
|
|
54
54
|
description: 'The confirmation token from the action tool',
|
|
55
55
|
},
|
|
56
|
+
secret_token: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'HMAC secret token for guard-protected operations. Only required when confirmation guard is enabled on the server.',
|
|
59
|
+
},
|
|
56
60
|
},
|
|
57
61
|
required: ['token'],
|
|
58
62
|
},
|
|
@@ -60,6 +64,7 @@ Violating these requirements bypasses user consent and is a security violation.`
|
|
|
60
64
|
|
|
61
65
|
interface ConfirmArgs {
|
|
62
66
|
token: string;
|
|
67
|
+
secret_token?: string;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
@@ -140,8 +145,26 @@ export async function handleConfirm(
|
|
|
140
145
|
);
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
// Handle set_trust_level via MemoryService
|
|
149
|
+
if (request.action === 'set_trust_level') {
|
|
150
|
+
const { memory } = createCoreServices(userId);
|
|
151
|
+
const result = await memory.confirmSetTrustLevel(args.token);
|
|
152
|
+
return JSON.stringify(
|
|
153
|
+
{
|
|
154
|
+
success: true,
|
|
155
|
+
memory_id: result.memory_id,
|
|
156
|
+
previous_trust_level: result.previous_trust_level,
|
|
157
|
+
new_trust_level: result.new_trust_level,
|
|
158
|
+
updated_at: result.updated_at,
|
|
159
|
+
message: `Trust level changed from ${result.previous_trust_level} to ${result.new_trust_level}`,
|
|
160
|
+
},
|
|
161
|
+
null,
|
|
162
|
+
2
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
// Delegate publish/retract/revise to core SpaceService
|
|
144
|
-
const result = await space.confirm({ token: args.token });
|
|
167
|
+
const result = await space.confirm({ token: args.token, secret_token: args.secret_token });
|
|
145
168
|
|
|
146
169
|
// Format response based on action type
|
|
147
170
|
if (result.action === 'retract_memory') {
|
|
@@ -29,7 +29,6 @@ export const createInternalMemoryTool = {
|
|
|
29
29
|
title: { type: 'string', description: 'Optional title' },
|
|
30
30
|
tags: { type: 'array', items: { type: 'string' }, description: 'Additional tags (internal tags added automatically)' },
|
|
31
31
|
weight: { type: 'number', minimum: 0, maximum: 1, description: 'Significance (0-1)' },
|
|
32
|
-
trust: { type: 'number', minimum: 0, maximum: 1, description: 'Trust level (0-1)' },
|
|
33
32
|
feel_salience: { type: 'number', minimum: 0, maximum: 1 },
|
|
34
33
|
feel_social_weight: { type: 'number', minimum: 0, maximum: 1 },
|
|
35
34
|
feel_narrative_importance: { type: 'number', minimum: 0, maximum: 1 },
|
|
@@ -43,7 +42,6 @@ export interface CreateInternalMemoryArgs {
|
|
|
43
42
|
title?: string;
|
|
44
43
|
tags?: string[];
|
|
45
44
|
weight?: number;
|
|
46
|
-
trust?: number;
|
|
47
45
|
[key: string]: any;
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -81,7 +79,6 @@ export async function handleCreateInternalMemory(
|
|
|
81
79
|
title: args.title,
|
|
82
80
|
type: ctx.type as any,
|
|
83
81
|
weight: args.weight,
|
|
84
|
-
trust: args.trust,
|
|
85
82
|
tags: mergedTags,
|
|
86
83
|
context_summary: `Internal memory created via MCP (${ctx.type})`,
|
|
87
84
|
...feelFields,
|
|
@@ -114,13 +114,17 @@ describe('updateMemoryTool definition', () => {
|
|
|
114
114
|
expect(required).not.toContain('group_ids');
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
it('has optional content, title, type, weight,
|
|
117
|
+
it('has optional content, title, type, weight, tags properties', () => {
|
|
118
118
|
const props = updateMemoryTool.inputSchema.properties as Record<string, any>;
|
|
119
119
|
expect(props.content).toBeDefined();
|
|
120
120
|
expect(props.title).toBeDefined();
|
|
121
121
|
expect(props.type).toBeDefined();
|
|
122
122
|
expect(props.weight).toBeDefined();
|
|
123
|
-
expect(props.trust).toBeDefined();
|
|
124
123
|
expect(props.tags).toBeDefined();
|
|
125
124
|
});
|
|
125
|
+
|
|
126
|
+
it('does not expose trust in schema', () => {
|
|
127
|
+
const props = updateMemoryTool.inputSchema.properties as Record<string, any>;
|
|
128
|
+
expect(props.trust).toBeUndefined();
|
|
129
|
+
});
|
|
126
130
|
});
|
|
@@ -18,7 +18,7 @@ export const createMemoryTool = {
|
|
|
18
18
|
description: `Create a new memory with optional template.
|
|
19
19
|
|
|
20
20
|
Memories can store any type of information: notes, events, people, recipes, etc.
|
|
21
|
-
Each memory has a weight (significance 0-1)
|
|
21
|
+
Each memory has a weight (significance 0-1). Trust defaults to SECRET (level 5) and can be changed via remember_request_set_trust_level.
|
|
22
22
|
Location and context are automatically captured from the request.
|
|
23
23
|
|
|
24
24
|
**IMPORTANT - Content vs Summary**:
|
|
@@ -56,12 +56,6 @@ export const createMemoryTool = {
|
|
|
56
56
|
minimum: 0,
|
|
57
57
|
maximum: 1,
|
|
58
58
|
},
|
|
59
|
-
trust: {
|
|
60
|
-
type: 'number',
|
|
61
|
-
description: 'Access control level (0-1, default: 0.25)',
|
|
62
|
-
minimum: 0,
|
|
63
|
-
maximum: 1,
|
|
64
|
-
},
|
|
65
59
|
tags: {
|
|
66
60
|
type: 'array',
|
|
67
61
|
items: { type: 'string' },
|
|
@@ -149,7 +143,6 @@ export interface CreateMemoryArgs {
|
|
|
149
143
|
title?: string;
|
|
150
144
|
type?: ContentType;
|
|
151
145
|
weight?: number;
|
|
152
|
-
trust?: number;
|
|
153
146
|
tags?: string[];
|
|
154
147
|
references?: string[];
|
|
155
148
|
template_id?: string;
|
|
@@ -201,7 +194,6 @@ export async function handleCreateMemory(
|
|
|
201
194
|
title: args.title,
|
|
202
195
|
type: args.type,
|
|
203
196
|
weight: args.weight,
|
|
204
|
-
trust: args.trust,
|
|
205
197
|
tags: args.tags,
|
|
206
198
|
references: args.references,
|
|
207
199
|
template_id: args.template_id,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for deny tool — secret_token passthrough.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { handleDeny } from './deny.js';
|
|
6
|
+
|
|
7
|
+
// ─── Mocks ──────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
jest.mock('../core-services.js', () => ({
|
|
10
|
+
createCoreServices: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('../utils/debug.js', () => ({
|
|
14
|
+
createDebugLogger: jest.fn(() => ({
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
debug: jest.fn(),
|
|
17
|
+
trace: jest.fn(),
|
|
18
|
+
error: jest.fn(),
|
|
19
|
+
time: jest.fn((_: string, fn: () => any) => fn()),
|
|
20
|
+
})),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock('../utils/error-handler.js', () => ({
|
|
24
|
+
handleToolError: jest.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import { createCoreServices } from '../core-services.js';
|
|
28
|
+
const mockCreateCoreServices = createCoreServices as jest.MockedFunction<any>;
|
|
29
|
+
|
|
30
|
+
// ─── Tests ───────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
describe('deny tool', () => {
|
|
33
|
+
let mockDeny: jest.Mock;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
mockDeny = jest.fn();
|
|
38
|
+
|
|
39
|
+
mockCreateCoreServices.mockReturnValue({
|
|
40
|
+
space: { deny: mockDeny },
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('passes secret_token through to space.deny when provided', async () => {
|
|
45
|
+
mockDeny.mockResolvedValue({ success: true });
|
|
46
|
+
|
|
47
|
+
await handleDeny({ token: 'tok-1', secret_token: 'sec-abc' }, 'user-1');
|
|
48
|
+
|
|
49
|
+
expect(mockDeny).toHaveBeenCalledWith({ token: 'tok-1', secret_token: 'sec-abc' });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('works without secret_token (backward compatible)', async () => {
|
|
53
|
+
mockDeny.mockResolvedValue({ success: true });
|
|
54
|
+
|
|
55
|
+
await handleDeny({ token: 'tok-1' }, 'user-1');
|
|
56
|
+
|
|
57
|
+
expect(mockDeny).toHaveBeenCalledWith({ token: 'tok-1', secret_token: undefined });
|
|
58
|
+
});
|
|
59
|
+
});
|
package/src/tools/deny.ts
CHANGED
|
@@ -45,6 +45,10 @@ This ensures proper user consent workflow is followed.`,
|
|
|
45
45
|
type: 'string',
|
|
46
46
|
description: 'The confirmation token from the action tool',
|
|
47
47
|
},
|
|
48
|
+
secret_token: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'HMAC secret token for guard-protected operations. Only required when confirmation guard is enabled on the server.',
|
|
51
|
+
},
|
|
48
52
|
},
|
|
49
53
|
required: ['token'],
|
|
50
54
|
},
|
|
@@ -52,6 +56,7 @@ This ensures proper user consent workflow is followed.`,
|
|
|
52
56
|
|
|
53
57
|
interface DenyArgs {
|
|
54
58
|
token: string;
|
|
59
|
+
secret_token?: string;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/**
|
|
@@ -68,7 +73,7 @@ export async function handleDeny(
|
|
|
68
73
|
debug.trace('Arguments', { args });
|
|
69
74
|
|
|
70
75
|
const { space } = createCoreServices(userId);
|
|
71
|
-
const result = await space.deny({ token: args.token });
|
|
76
|
+
const result = await space.deny({ token: args.token, secret_token: args.secret_token });
|
|
72
77
|
|
|
73
78
|
return JSON.stringify(
|
|
74
79
|
{
|
|
@@ -29,17 +29,17 @@ export const ghostConfigTool: Tool = {
|
|
|
29
29
|
Actions:
|
|
30
30
|
- get: View current ghost configuration
|
|
31
31
|
- set: Update ghost settings (enabled, trust defaults, enforcement mode)
|
|
32
|
-
- set_trust: Set a per-user trust level override (
|
|
32
|
+
- set_trust: Set a per-user trust level override (1-5 integer)
|
|
33
33
|
- remove_trust: Remove a per-user trust override (revert to default)
|
|
34
34
|
- block: Block a user from ghost access entirely
|
|
35
35
|
- unblock: Unblock a previously blocked user
|
|
36
36
|
|
|
37
|
-
Trust levels control what information your ghost can share:
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
37
|
+
Trust levels (1-5 integer scale) control what information your ghost can share:
|
|
38
|
+
- 1 (PUBLIC): Full access (all content revealed)
|
|
39
|
+
- 2 (INTERNAL): Partial access (content with sensitive fields redacted)
|
|
40
|
+
- 3 (CONFIDENTIAL): Summary only (AI-generated summary, no raw content)
|
|
41
|
+
- 4 (RESTRICTED): Metadata only (tags, type, dates — no content)
|
|
42
|
+
- 5 (SECRET): Existence only ("A memory exists about this")
|
|
43
43
|
|
|
44
44
|
Ghost is disabled by default. Enable it to allow others to chat with your AI representation.`,
|
|
45
45
|
inputSchema: {
|
|
@@ -60,16 +60,16 @@ Ghost is disabled by default. Enable it to allow others to chat with your AI rep
|
|
|
60
60
|
description: 'Allow non-friends to chat with ghost (for "set" action)',
|
|
61
61
|
},
|
|
62
62
|
default_friend_trust: {
|
|
63
|
-
type: '
|
|
64
|
-
description: 'Default trust level for friends (
|
|
65
|
-
minimum:
|
|
66
|
-
maximum:
|
|
63
|
+
type: 'integer',
|
|
64
|
+
description: 'Default trust level for friends (1-5 integer, for "set" action)',
|
|
65
|
+
minimum: 1,
|
|
66
|
+
maximum: 5,
|
|
67
67
|
},
|
|
68
68
|
default_public_trust: {
|
|
69
|
-
type: '
|
|
70
|
-
description: 'Default trust level for strangers (
|
|
71
|
-
minimum:
|
|
72
|
-
maximum:
|
|
69
|
+
type: 'integer',
|
|
70
|
+
description: 'Default trust level for strangers (1-5 integer, for "set" action)',
|
|
71
|
+
minimum: 1,
|
|
72
|
+
maximum: 5,
|
|
73
73
|
},
|
|
74
74
|
enforcement_mode: {
|
|
75
75
|
type: 'string',
|
|
@@ -82,10 +82,10 @@ Ghost is disabled by default. Enable it to allow others to chat with your AI rep
|
|
|
82
82
|
description: 'Target user ID (for set_trust, remove_trust, block, unblock)',
|
|
83
83
|
},
|
|
84
84
|
trust_level: {
|
|
85
|
-
type: '
|
|
86
|
-
description: 'Trust level to assign (
|
|
87
|
-
minimum:
|
|
88
|
-
maximum:
|
|
85
|
+
type: 'integer',
|
|
86
|
+
description: 'Trust level to assign (1-5 integer, for "set_trust" action)',
|
|
87
|
+
minimum: 1,
|
|
88
|
+
maximum: 5,
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
required: ['action'],
|
|
@@ -91,8 +91,10 @@ export const queryMemoryTool = {
|
|
|
91
91
|
description: 'Minimum weight (0-1)',
|
|
92
92
|
},
|
|
93
93
|
trust_min: {
|
|
94
|
-
type: '
|
|
95
|
-
description: 'Minimum trust level (
|
|
94
|
+
type: 'integer',
|
|
95
|
+
description: 'Minimum trust level (1-5: 1=PUBLIC, 2=INTERNAL, 3=CONFIDENTIAL, 4=RESTRICTED, 5=SECRET)',
|
|
96
|
+
minimum: 1,
|
|
97
|
+
maximum: 5,
|
|
96
98
|
},
|
|
97
99
|
date_from: {
|
|
98
100
|
type: 'string',
|