@prmichaelsen/remember-mcp 3.20.1 → 4.0.1
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/CHANGELOG.md +19 -0
- package/agent/milestones/milestone-23-trust-level-protection.md +122 -0
- package/agent/progress.yaml +96 -3
- 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/dist/server-factory.js +151 -47
- package/dist/server.js +150 -42
- package/dist/services/trust-validator.d.ts +16 -14
- 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/package.json +2 -2
- package/src/server-factory.ts +6 -0
- 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/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/publish.ts +2 -0
- 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 +109 -0
- package/src/tools/retract.ts +2 -0
- package/src/tools/revise.ts +2 -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
|
@@ -2,107 +2,113 @@ import { validateTrustAssignment, suggestTrustLevel } from './trust-validator.js
|
|
|
2
2
|
|
|
3
3
|
describe('validateTrustAssignment', () => {
|
|
4
4
|
it('accepts trust levels in valid range', () => {
|
|
5
|
-
expect(validateTrustAssignment(
|
|
6
|
-
expect(validateTrustAssignment(
|
|
7
|
-
expect(validateTrustAssignment(
|
|
5
|
+
expect(validateTrustAssignment(1).valid).toBe(true);
|
|
6
|
+
expect(validateTrustAssignment(3).valid).toBe(true);
|
|
7
|
+
expect(validateTrustAssignment(5).valid).toBe(true);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
it('rejects trust levels below
|
|
11
|
-
const result = validateTrustAssignment(
|
|
10
|
+
it('rejects trust levels below 1', () => {
|
|
11
|
+
const result = validateTrustAssignment(0);
|
|
12
12
|
expect(result.valid).toBe(false);
|
|
13
|
-
expect(result.warning).toContain('
|
|
13
|
+
expect(result.warning).toContain('0');
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it('rejects trust levels above
|
|
17
|
-
const result = validateTrustAssignment(
|
|
16
|
+
it('rejects trust levels above 5', () => {
|
|
17
|
+
const result = validateTrustAssignment(6);
|
|
18
18
|
expect(result.valid).toBe(false);
|
|
19
|
-
expect(result.warning).toContain('
|
|
19
|
+
expect(result.warning).toContain('6');
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('
|
|
23
|
-
const result = validateTrustAssignment(
|
|
22
|
+
it('rejects non-integer trust levels', () => {
|
|
23
|
+
const result = validateTrustAssignment(2.5);
|
|
24
|
+
expect(result.valid).toBe(false);
|
|
25
|
+
expect(result.warning).toContain('2.5');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('warns for trust >= 4 (RESTRICTED)', () => {
|
|
29
|
+
const result = validateTrustAssignment(4);
|
|
24
30
|
expect(result.valid).toBe(true);
|
|
25
31
|
expect(result.warning).toContain('very restrictive');
|
|
26
32
|
});
|
|
27
33
|
|
|
28
|
-
it('warns for trust
|
|
29
|
-
const result = validateTrustAssignment(
|
|
34
|
+
it('warns for trust 5 (SECRET)', () => {
|
|
35
|
+
const result = validateTrustAssignment(5);
|
|
30
36
|
expect(result.valid).toBe(true);
|
|
31
37
|
expect(result.warning).toContain('very restrictive');
|
|
32
38
|
});
|
|
33
39
|
|
|
34
|
-
it('does not warn for trust
|
|
35
|
-
expect(validateTrustAssignment(
|
|
36
|
-
expect(validateTrustAssignment(
|
|
37
|
-
expect(validateTrustAssignment(
|
|
40
|
+
it('does not warn for trust <= 3', () => {
|
|
41
|
+
expect(validateTrustAssignment(1).warning).toBeUndefined();
|
|
42
|
+
expect(validateTrustAssignment(2).warning).toBeUndefined();
|
|
43
|
+
expect(validateTrustAssignment(3).warning).toBeUndefined();
|
|
38
44
|
});
|
|
39
45
|
});
|
|
40
46
|
|
|
41
47
|
describe('suggestTrustLevel', () => {
|
|
42
48
|
describe('tag overrides', () => {
|
|
43
|
-
it('returns
|
|
44
|
-
expect(suggestTrustLevel('note', ['private'])).toBe(
|
|
49
|
+
it('returns 5 (SECRET) for private tag', () => {
|
|
50
|
+
expect(suggestTrustLevel('note', ['private'])).toBe(5);
|
|
45
51
|
});
|
|
46
52
|
|
|
47
|
-
it('returns
|
|
48
|
-
expect(suggestTrustLevel('note', ['Secret'])).toBe(
|
|
53
|
+
it('returns 5 (SECRET) for secret tag', () => {
|
|
54
|
+
expect(suggestTrustLevel('note', ['Secret'])).toBe(5);
|
|
49
55
|
});
|
|
50
56
|
|
|
51
|
-
it('returns 1
|
|
52
|
-
expect(suggestTrustLevel('journal', ['public'])).toBe(1
|
|
57
|
+
it('returns 1 (PUBLIC) for public tag', () => {
|
|
58
|
+
expect(suggestTrustLevel('journal', ['public'])).toBe(1);
|
|
53
59
|
});
|
|
54
60
|
|
|
55
61
|
it('tag override takes priority over content type', () => {
|
|
56
|
-
// journal normally suggests
|
|
57
|
-
expect(suggestTrustLevel('journal', ['private'])).toBe(
|
|
62
|
+
// journal normally suggests 4, but 'private' overrides to 5
|
|
63
|
+
expect(suggestTrustLevel('journal', ['private'])).toBe(5);
|
|
58
64
|
});
|
|
59
65
|
});
|
|
60
66
|
|
|
61
67
|
describe('content type suggestions', () => {
|
|
62
|
-
it('suggests
|
|
63
|
-
expect(suggestTrustLevel('journal')).toBe(
|
|
64
|
-
expect(suggestTrustLevel('memory')).toBe(
|
|
65
|
-
expect(suggestTrustLevel('event')).toBe(
|
|
68
|
+
it('suggests 4 (RESTRICTED) for personal types', () => {
|
|
69
|
+
expect(suggestTrustLevel('journal')).toBe(4);
|
|
70
|
+
expect(suggestTrustLevel('memory')).toBe(4);
|
|
71
|
+
expect(suggestTrustLevel('event')).toBe(4);
|
|
66
72
|
});
|
|
67
73
|
|
|
68
|
-
it('suggests
|
|
69
|
-
expect(suggestTrustLevel('system')).toBe(
|
|
70
|
-
expect(suggestTrustLevel('audit')).toBe(
|
|
71
|
-
expect(suggestTrustLevel('action')).toBe(
|
|
72
|
-
expect(suggestTrustLevel('history')).toBe(
|
|
74
|
+
it('suggests 3 (CONFIDENTIAL) for system types', () => {
|
|
75
|
+
expect(suggestTrustLevel('system')).toBe(3);
|
|
76
|
+
expect(suggestTrustLevel('audit')).toBe(3);
|
|
77
|
+
expect(suggestTrustLevel('action')).toBe(3);
|
|
78
|
+
expect(suggestTrustLevel('history')).toBe(3);
|
|
73
79
|
});
|
|
74
80
|
|
|
75
|
-
it('suggests
|
|
76
|
-
expect(suggestTrustLevel('invoice')).toBe(
|
|
77
|
-
expect(suggestTrustLevel('contract')).toBe(
|
|
81
|
+
it('suggests 3 (CONFIDENTIAL) for business types', () => {
|
|
82
|
+
expect(suggestTrustLevel('invoice')).toBe(3);
|
|
83
|
+
expect(suggestTrustLevel('contract')).toBe(3);
|
|
78
84
|
});
|
|
79
85
|
|
|
80
|
-
it('suggests
|
|
81
|
-
expect(suggestTrustLevel('email')).toBe(
|
|
82
|
-
expect(suggestTrustLevel('conversation')).toBe(
|
|
83
|
-
expect(suggestTrustLevel('meeting')).toBe(
|
|
86
|
+
it('suggests 3 (CONFIDENTIAL) for communication types', () => {
|
|
87
|
+
expect(suggestTrustLevel('email')).toBe(3);
|
|
88
|
+
expect(suggestTrustLevel('conversation')).toBe(3);
|
|
89
|
+
expect(suggestTrustLevel('meeting')).toBe(3);
|
|
84
90
|
});
|
|
85
91
|
|
|
86
|
-
it('suggests
|
|
87
|
-
expect(suggestTrustLevel('ghost')).toBe(
|
|
92
|
+
it('suggests 4 (RESTRICTED) for ghost type', () => {
|
|
93
|
+
expect(suggestTrustLevel('ghost')).toBe(4);
|
|
88
94
|
});
|
|
89
95
|
|
|
90
|
-
it('suggests
|
|
91
|
-
expect(suggestTrustLevel('note')).toBe(
|
|
92
|
-
expect(suggestTrustLevel('code')).toBe(
|
|
93
|
-
expect(suggestTrustLevel('article')).toBe(
|
|
94
|
-
expect(suggestTrustLevel('recipe')).toBe(
|
|
95
|
-
expect(suggestTrustLevel('bookmark')).toBe(
|
|
96
|
+
it('suggests 2 (INTERNAL) for general/creative types', () => {
|
|
97
|
+
expect(suggestTrustLevel('note')).toBe(2);
|
|
98
|
+
expect(suggestTrustLevel('code')).toBe(2);
|
|
99
|
+
expect(suggestTrustLevel('article')).toBe(2);
|
|
100
|
+
expect(suggestTrustLevel('recipe')).toBe(2);
|
|
101
|
+
expect(suggestTrustLevel('bookmark')).toBe(2);
|
|
96
102
|
});
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
describe('edge cases', () => {
|
|
100
106
|
it('handles empty tags array', () => {
|
|
101
|
-
expect(suggestTrustLevel('note', [])).toBe(
|
|
107
|
+
expect(suggestTrustLevel('note', [])).toBe(2);
|
|
102
108
|
});
|
|
103
109
|
|
|
104
110
|
it('handles undefined tags', () => {
|
|
105
|
-
expect(suggestTrustLevel('note')).toBe(
|
|
111
|
+
expect(suggestTrustLevel('note')).toBe(2);
|
|
106
112
|
});
|
|
107
113
|
});
|
|
108
114
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Trust validator — validation and suggestions for trust-sensitive operations.
|
|
3
3
|
*
|
|
4
|
+
* Uses integer TrustLevel 1–5 scale (higher = more confidential).
|
|
5
|
+
* Aligned with remember-core's trust-validator.service.
|
|
6
|
+
*
|
|
4
7
|
* See agent/design/local.ghost-persona-system.md
|
|
5
8
|
*/
|
|
6
9
|
|
|
@@ -16,21 +19,20 @@ export interface TrustValidationResult {
|
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Validate a trust level assignment.
|
|
19
|
-
* Warns if trust
|
|
22
|
+
* Warns if trust >= 4 (Restricted/Secret — very restrictive).
|
|
20
23
|
* Returns invalid for out-of-range values.
|
|
21
24
|
*
|
|
22
|
-
* @param trustLevel - The trust level being assigned (
|
|
23
|
-
* @param content - Optional content for context-aware validation
|
|
25
|
+
* @param trustLevel - The trust level being assigned (1-5 integer)
|
|
24
26
|
*/
|
|
25
|
-
export function validateTrustAssignment(trustLevel: number
|
|
26
|
-
if (trustLevel <
|
|
27
|
-
return { valid: false, warning: `Trust level must be between
|
|
27
|
+
export function validateTrustAssignment(trustLevel: number): TrustValidationResult {
|
|
28
|
+
if (!Number.isInteger(trustLevel) || trustLevel < 1 || trustLevel > 5) {
|
|
29
|
+
return { valid: false, warning: `Trust level must be an integer between 1 and 5, got ${trustLevel}` };
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
if (trustLevel
|
|
32
|
+
if (trustLevel >= 4) {
|
|
31
33
|
return {
|
|
32
34
|
valid: true,
|
|
33
|
-
warning: `Trust level ${trustLevel} is very restrictive — most accessors will
|
|
35
|
+
warning: `Trust level ${trustLevel} is very restrictive — most accessors will have limited visibility. Consider level 2 (INTERNAL) or 3 (CONFIDENTIAL) for broader access.`,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -40,31 +42,31 @@ export function validateTrustAssignment(trustLevel: number, content?: string): T
|
|
|
40
42
|
/**
|
|
41
43
|
* Suggest an appropriate trust level based on content type and tags.
|
|
42
44
|
*
|
|
43
|
-
* Guidelines:
|
|
44
|
-
* -
|
|
45
|
-
* -
|
|
46
|
-
* - Business (invoice, contract):
|
|
47
|
-
* - Communication (email, conversation):
|
|
48
|
-
* -
|
|
49
|
-
* - Default:
|
|
45
|
+
* Guidelines (1-5 integer scale):
|
|
46
|
+
* - Personal (journal, memory, event): RESTRICTED (4) — close contacts only
|
|
47
|
+
* - System/audit/action: CONFIDENTIAL (3) — trusted friends
|
|
48
|
+
* - Business (invoice, contract): CONFIDENTIAL (3)
|
|
49
|
+
* - Communication (email, conversation): CONFIDENTIAL (3)
|
|
50
|
+
* - Ghost conversations: RESTRICTED (4)
|
|
51
|
+
* - Default: INTERNAL (2) — conservative
|
|
50
52
|
*
|
|
51
53
|
* Tag overrides:
|
|
52
|
-
* - 'private' or 'secret':
|
|
53
|
-
* - 'public':
|
|
54
|
+
* - 'private' or 'secret': SECRET (5)
|
|
55
|
+
* - 'public': PUBLIC (1)
|
|
54
56
|
*
|
|
55
57
|
* @param contentType - The type of content
|
|
56
58
|
* @param tags - Optional tags that may affect suggestion
|
|
57
|
-
* @returns Suggested trust level (
|
|
59
|
+
* @returns Suggested trust level (1-5)
|
|
58
60
|
*/
|
|
59
61
|
export function suggestTrustLevel(contentType: ContentType, tags?: string[]): number {
|
|
60
62
|
// Tag-based overrides take priority
|
|
61
63
|
if (tags && tags.length > 0) {
|
|
62
64
|
const lowerTags = tags.map(t => t.toLowerCase());
|
|
63
65
|
if (lowerTags.includes('private') || lowerTags.includes('secret')) {
|
|
64
|
-
return
|
|
66
|
+
return 5; // SECRET
|
|
65
67
|
}
|
|
66
68
|
if (lowerTags.includes('public')) {
|
|
67
|
-
return 1
|
|
69
|
+
return 1; // PUBLIC
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -74,32 +76,32 @@ export function suggestTrustLevel(contentType: ContentType, tags?: string[]): nu
|
|
|
74
76
|
case 'journal':
|
|
75
77
|
case 'memory':
|
|
76
78
|
case 'event':
|
|
77
|
-
return
|
|
79
|
+
return 4; // RESTRICTED
|
|
78
80
|
|
|
79
81
|
// System/internal
|
|
80
82
|
case 'system':
|
|
81
83
|
case 'audit':
|
|
82
84
|
case 'action':
|
|
83
85
|
case 'history':
|
|
84
|
-
return
|
|
86
|
+
return 3; // CONFIDENTIAL
|
|
85
87
|
|
|
86
88
|
// Business
|
|
87
89
|
case 'invoice':
|
|
88
90
|
case 'contract':
|
|
89
|
-
return
|
|
91
|
+
return 3; // CONFIDENTIAL
|
|
90
92
|
|
|
91
93
|
// Communication
|
|
92
94
|
case 'email':
|
|
93
95
|
case 'conversation':
|
|
94
96
|
case 'meeting':
|
|
95
|
-
return
|
|
97
|
+
return 3; // CONFIDENTIAL
|
|
96
98
|
|
|
97
99
|
// Ghost conversations — private by default
|
|
98
100
|
case 'ghost':
|
|
99
|
-
return
|
|
101
|
+
return 4; // RESTRICTED
|
|
100
102
|
|
|
101
103
|
// Default — conservative
|
|
102
104
|
default:
|
|
103
|
-
return
|
|
105
|
+
return 2; // INTERNAL
|
|
104
106
|
}
|
|
105
107
|
}
|
|
@@ -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
|
{
|