@raviolelabs/engram-mcp 0.2.2-staging.ce4e077 → 0.2.3
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/README.md +140 -134
- package/SKILL.md +33 -0
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +27 -1
- package/dist/core/db/index.js.map +1 -1
- package/dist/core/server/http.d.ts +3 -0
- package/dist/core/server/http.d.ts.map +1 -1
- package/dist/core/server/http.js +6 -0
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/mcp-handler.d.ts.map +1 -1
- package/dist/core/server/mcp-handler.js +28 -1
- package/dist/core/server/mcp-handler.js.map +1 -1
- package/dist/embeddings/index.d.ts +6 -0
- package/dist/embeddings/index.d.ts.map +1 -1
- package/dist/embeddings/index.js +31 -3
- package/dist/embeddings/index.js.map +1 -1
- package/dist/embeddings/providers/engram.d.ts +10 -0
- package/dist/embeddings/providers/engram.d.ts.map +1 -1
- package/dist/embeddings/providers/engram.js +35 -12
- package/dist/embeddings/providers/engram.js.map +1 -1
- package/dist/memory/core/store.d.ts.map +1 -1
- package/dist/memory/core/store.js +5 -4
- package/dist/memory/core/store.js.map +1 -1
- package/dist/memory/modules/team/keystore.d.ts +38 -0
- package/dist/memory/modules/team/keystore.d.ts.map +1 -0
- package/dist/memory/modules/team/keystore.js +117 -0
- package/dist/memory/modules/team/keystore.js.map +1 -0
- package/dist/memory/modules/team/tools.d.ts +10 -0
- package/dist/memory/modules/team/tools.d.ts.map +1 -0
- package/dist/memory/modules/team/tools.js +252 -0
- package/dist/memory/modules/team/tools.js.map +1 -0
- package/dist/memory/public/tools.d.ts.map +1 -1
- package/dist/memory/public/tools.js +33 -5
- package/dist/memory/public/tools.js.map +1 -1
- package/dist/sync/team-sync.d.ts +19 -0
- package/dist/sync/team-sync.d.ts.map +1 -0
- package/dist/sync/team-sync.js +80 -0
- package/dist/sync/team-sync.js.map +1 -0
- package/dist/tests/scope-encryption.test.d.ts +2 -0
- package/dist/tests/scope-encryption.test.d.ts.map +1 -0
- package/dist/tests/scope-encryption.test.js +71 -0
- package/dist/tests/scope-encryption.test.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/webapp/api/team.d.ts +7 -0
- package/dist/webapp/api/team.d.ts.map +1 -0
- package/dist/webapp/api/team.js +57 -0
- package/dist/webapp/api/team.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// src/memory/modules/team/tools.ts
|
|
2
|
+
// Workspace MCP tools: list_workspaces, create_workspace, leave_workspace,
|
|
3
|
+
// invite_to_workspace, accept_workspace_invite.
|
|
4
|
+
// These are registered alongside the public tools.
|
|
5
|
+
import sodium from 'libsodium-wrappers';
|
|
6
|
+
import { createLogger } from '../../../logger.js';
|
|
7
|
+
import { generateWorkspaceKey, wrapKeyForRecipient, unwrapAndStoreWorkspaceKey, deleteWorkspaceKey, getOrCreateX25519Keypair, getX25519PubkeyHex, } from './keystore.js';
|
|
8
|
+
const log = createLogger('team:tools');
|
|
9
|
+
export function buildWorkspaceTools(ctx) {
|
|
10
|
+
const cloudUrl = ctx.config.cloudBaseUrl
|
|
11
|
+
?? 'https://api.engram-mcp.com';
|
|
12
|
+
const dataDir = ctx.config.dataDir;
|
|
13
|
+
async function cloudFetch(path, init = {}) {
|
|
14
|
+
const jwt = await ctx.getJwt();
|
|
15
|
+
const headers = {
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
...(init.headers ?? {}),
|
|
18
|
+
};
|
|
19
|
+
if (jwt)
|
|
20
|
+
headers['Cookie'] = `engram_session=${jwt}`;
|
|
21
|
+
return fetch(`${cloudUrl}${path}`, { ...init, headers });
|
|
22
|
+
}
|
|
23
|
+
return [
|
|
24
|
+
// ── list_workspaces ───────────────────────────────────────────────────────
|
|
25
|
+
{
|
|
26
|
+
name: 'list_workspaces',
|
|
27
|
+
description: [
|
|
28
|
+
'Return all team workspaces the current user belongs to.',
|
|
29
|
+
'WHEN: you need workspace IDs for use with scope parameter, or to show the user their teams.',
|
|
30
|
+
'Returns personal workspace plus all team workspaces with name, role, and workspace_id.',
|
|
31
|
+
'RETURNS: { workspaces: [{ id, name, role, is_personal }] }.',
|
|
32
|
+
].join(' '),
|
|
33
|
+
inputSchema: { type: 'object', properties: {} },
|
|
34
|
+
handler: async () => {
|
|
35
|
+
const { getDb } = await import('../../../db/index.js');
|
|
36
|
+
const db = getDb();
|
|
37
|
+
const rows = db
|
|
38
|
+
.prepare('SELECT id, name, role, owner_email, joined_at FROM workspaces ORDER BY joined_at ASC')
|
|
39
|
+
.all();
|
|
40
|
+
return {
|
|
41
|
+
workspaces: [
|
|
42
|
+
{ id: 'personal', name: 'Personal', role: 'owner', is_personal: true },
|
|
43
|
+
...rows.map((r) => ({
|
|
44
|
+
id: r.id,
|
|
45
|
+
name: r.name,
|
|
46
|
+
role: r.role,
|
|
47
|
+
is_personal: false,
|
|
48
|
+
owner_email: r.owner_email,
|
|
49
|
+
joined_at: r.joined_at,
|
|
50
|
+
})),
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
// ── create_workspace ──────────────────────────────────────────────────────
|
|
56
|
+
{
|
|
57
|
+
name: 'create_workspace',
|
|
58
|
+
description: [
|
|
59
|
+
'Create a new team workspace. Generates a team master key locally, wraps it for the owner, and registers with engram-cloud.',
|
|
60
|
+
'WHEN: user wants to start a shared team memory space.',
|
|
61
|
+
'After creation, use invite_to_workspace to add members.',
|
|
62
|
+
'Requires an active cloud session (engram pair must have been run).',
|
|
63
|
+
'RETURNS: { workspace_id, name } or { error }.',
|
|
64
|
+
].join(' '),
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
name: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Display name for the workspace (e.g. "Acme Research").',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['name'],
|
|
74
|
+
},
|
|
75
|
+
handler: async (args) => {
|
|
76
|
+
const name = args.name.trim();
|
|
77
|
+
if (!name)
|
|
78
|
+
return { error: 'name_required' };
|
|
79
|
+
await sodium.ready;
|
|
80
|
+
const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
|
|
81
|
+
const ownerPubkeyHex = await getX25519PubkeyHex(dataDir);
|
|
82
|
+
if (!ownerPubkeyHex)
|
|
83
|
+
return { error: 'keypair_not_found', hint: 'Run engram pair first.' };
|
|
84
|
+
// Generate fresh team master key + wrap it for the owner
|
|
85
|
+
const teamKey = await generateWorkspaceKey();
|
|
86
|
+
const wrappedForOwner = await wrapKeyForRecipient(teamKey, ownerPubkeyHex);
|
|
87
|
+
// Create workspace on cloud
|
|
88
|
+
const res = await cloudFetch('/api/workspaces', {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: JSON.stringify({ name, wrapped_team_key: wrappedForOwner }),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
const err = await res.json().catch(() => ({}));
|
|
94
|
+
log.error('create_workspace cloud error', err);
|
|
95
|
+
return { error: err.error ?? `cloud_error_${res.status}`, message: err.message };
|
|
96
|
+
}
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
// Store team master key + register workspace locally
|
|
99
|
+
const { getDb } = await import('../../../db/index.js');
|
|
100
|
+
const db = getDb();
|
|
101
|
+
const now = new Date().toISOString();
|
|
102
|
+
db.prepare(`INSERT OR REPLACE INTO workspaces (id, name, role, joined_at) VALUES (?, ?, 'owner', ?)`).run(data.id, data.name, now);
|
|
103
|
+
// Persist decrypted team key
|
|
104
|
+
await unwrapAndStoreWorkspaceKey(dataDir, data.id, wrappedForOwner, keypair);
|
|
105
|
+
log.info(`Created workspace ${data.id} "${data.name}"`);
|
|
106
|
+
return { workspace_id: data.id, name: data.name };
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
// ── leave_workspace ───────────────────────────────────────────────────────
|
|
110
|
+
{
|
|
111
|
+
name: 'leave_workspace',
|
|
112
|
+
description: [
|
|
113
|
+
'Remove yourself from a team workspace. Deletes the local team key.',
|
|
114
|
+
'WHEN: user wants to leave a team or the workspace is being decommissioned.',
|
|
115
|
+
'Requires confirm: true to prevent accidental removal.',
|
|
116
|
+
'RETURNS: { ok: true } or { error }.',
|
|
117
|
+
].join(' '),
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
workspace_id: { type: 'string', description: 'Workspace id to leave.' },
|
|
122
|
+
confirm: {
|
|
123
|
+
type: 'boolean',
|
|
124
|
+
description: 'Must be true to confirm leaving. Prevents accidental removal.',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: ['workspace_id', 'confirm'],
|
|
128
|
+
},
|
|
129
|
+
handler: async (args) => {
|
|
130
|
+
if (!args.confirm) {
|
|
131
|
+
return { error: 'confirm_required', message: 'Pass confirm: true to leave the workspace.' };
|
|
132
|
+
}
|
|
133
|
+
const workspaceId = args.workspace_id;
|
|
134
|
+
// Remove from local SQLite registry
|
|
135
|
+
const { getDb } = await import('../../../db/index.js');
|
|
136
|
+
const db = getDb();
|
|
137
|
+
db.prepare('DELETE FROM workspaces WHERE id = ?').run(workspaceId);
|
|
138
|
+
// Delete local team key
|
|
139
|
+
await deleteWorkspaceKey(dataDir, workspaceId);
|
|
140
|
+
log.info(`Left workspace ${workspaceId}`);
|
|
141
|
+
return { ok: true };
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
// ── invite_to_workspace ───────────────────────────────────────────────────
|
|
145
|
+
{
|
|
146
|
+
name: 'invite_to_workspace',
|
|
147
|
+
description: [
|
|
148
|
+
'Invite a user to a team workspace by email. Fetches their X25519 pubkey from cloud, wraps the team key for them, and sends an invitation email.',
|
|
149
|
+
'WHEN: workspace owner or admin wants to add a new member.',
|
|
150
|
+
'The invitee must have a paired engram-mcp device (their pubkey must be registered).',
|
|
151
|
+
'RETURNS: { ok: true, invitation_id } or { error }.',
|
|
152
|
+
].join(' '),
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
workspace_id: { type: 'string', description: 'Workspace id (from list_workspaces).' },
|
|
157
|
+
email: { type: 'string', description: 'Email address of the invitee.' },
|
|
158
|
+
role: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
enum: ['member', 'admin'],
|
|
161
|
+
description: 'Role to assign. Default: "member".',
|
|
162
|
+
default: 'member',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ['workspace_id', 'email'],
|
|
166
|
+
},
|
|
167
|
+
handler: async (args) => {
|
|
168
|
+
const workspaceId = args.workspace_id;
|
|
169
|
+
const email = args.email.trim().toLowerCase();
|
|
170
|
+
const role = args.role ?? 'member';
|
|
171
|
+
await sodium.ready;
|
|
172
|
+
const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
|
|
173
|
+
// Load local team key
|
|
174
|
+
const { loadWorkspaceKey } = await import('./keystore.js');
|
|
175
|
+
const teamKey = await loadWorkspaceKey(dataDir, workspaceId);
|
|
176
|
+
if (!teamKey) {
|
|
177
|
+
return { error: 'team_key_not_found', message: 'You do not have a local key for this workspace. Are you the owner?' };
|
|
178
|
+
}
|
|
179
|
+
// Fetch invitee's pubkey from cloud
|
|
180
|
+
const pubkeyRes = await cloudFetch(`/api/users/pubkey?email=${encodeURIComponent(email)}`);
|
|
181
|
+
if (!pubkeyRes.ok) {
|
|
182
|
+
const err = await pubkeyRes.json().catch(() => ({}));
|
|
183
|
+
return { error: err.error ?? 'pubkey_fetch_failed', message: err.message };
|
|
184
|
+
}
|
|
185
|
+
const { x25519_pubkey: inviteePubkeyHex } = await pubkeyRes.json();
|
|
186
|
+
// Wrap team key for invitee
|
|
187
|
+
const wrappedForInvitee = await wrapKeyForRecipient(teamKey, inviteePubkeyHex);
|
|
188
|
+
// Send invitation
|
|
189
|
+
const invRes = await cloudFetch(`/api/workspaces/${workspaceId}/invite`, {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
body: JSON.stringify({ email, role, wrapped_team_key: wrappedForInvitee }),
|
|
192
|
+
});
|
|
193
|
+
if (!invRes.ok) {
|
|
194
|
+
const err = await invRes.json().catch(() => ({}));
|
|
195
|
+
return { error: err.error ?? 'invite_failed', message: err.message };
|
|
196
|
+
}
|
|
197
|
+
const data = await invRes.json();
|
|
198
|
+
log.info(`Invited ${email} to workspace ${workspaceId}`);
|
|
199
|
+
return { ok: true, invitation_id: data.invitation_id };
|
|
200
|
+
void keypair; // keypair loaded to ensure keys are ready — not used directly for wrapping
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
// ── accept_workspace_invite ───────────────────────────────────────────────
|
|
204
|
+
{
|
|
205
|
+
name: 'accept_workspace_invite',
|
|
206
|
+
description: [
|
|
207
|
+
'Accept a team workspace invitation using the token from the invitation email.',
|
|
208
|
+
'Decrypts the wrapped team key using your local X25519 private key and stores it.',
|
|
209
|
+
'WHEN: you received an invitation link and want to join a team workspace.',
|
|
210
|
+
'RETURNS: { ok: true, workspace_id } or { error }.',
|
|
211
|
+
].join(' '),
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
token: { type: 'string', description: 'Invitation token from the email link.' },
|
|
216
|
+
},
|
|
217
|
+
required: ['token'],
|
|
218
|
+
},
|
|
219
|
+
handler: async (args) => {
|
|
220
|
+
const token = args.token;
|
|
221
|
+
await sodium.ready;
|
|
222
|
+
const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
|
|
223
|
+
const res = await cloudFetch('/api/workspaces/invitations/accept', {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
body: JSON.stringify({ token }),
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
const err = await res.json().catch(() => ({}));
|
|
229
|
+
return { error: err.error ?? 'accept_failed', message: err.message };
|
|
230
|
+
}
|
|
231
|
+
const data = await res.json();
|
|
232
|
+
if (data.wrapped_team_key) {
|
|
233
|
+
await unwrapAndStoreWorkspaceKey(dataDir, data.workspace_id, data.wrapped_team_key, keypair);
|
|
234
|
+
}
|
|
235
|
+
// Register workspace locally
|
|
236
|
+
const workspaceInfoRes = await cloudFetch('/api/workspaces');
|
|
237
|
+
if (workspaceInfoRes.ok) {
|
|
238
|
+
const { workspaces } = await workspaceInfoRes.json();
|
|
239
|
+
const ws = workspaces.find((w) => w.id === data.workspace_id);
|
|
240
|
+
if (ws) {
|
|
241
|
+
const { getDb } = await import('../../../db/index.js');
|
|
242
|
+
const db = getDb();
|
|
243
|
+
db.prepare(`INSERT OR REPLACE INTO workspaces (id, name, role, joined_at) VALUES (?, ?, ?, ?)`).run(ws.id, ws.name, ws.role, ws.joined_at);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
log.info(`Accepted invite for workspace ${data.workspace_id}`);
|
|
247
|
+
return { ok: true, workspace_id: data.workspace_id };
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../src/memory/modules/team/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/memory/public/tools.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/memory/public/tools.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,EAAE,CAmgD9F"}
|
|
@@ -30,6 +30,7 @@ export function buildPublicTools(store, config) {
|
|
|
30
30
|
description: [
|
|
31
31
|
'Store a memory (note, audio transcript, conversation, document) for later semantic retrieval.',
|
|
32
32
|
'INPUTS: content (required, free text). Optionally provide title (improves retrieval — 3-7 words), tags (array of strings — topics/people/projects this memory is about), type (default "notes"; use "conversations" for dialog turns, or any custom type), and properties (object — any extra key/value metadata).',
|
|
33
|
+
'SCOPE: use scope="personal" (default) for private memories, or scope="workspace:<id>" to store in a team workspace (get id from list_workspaces).',
|
|
33
34
|
'WHEN: call after user shares anything worth remembering (preferences, facts, decisions, exchanges). Always include title + 2-5 tags so it surfaces in future recall.',
|
|
34
35
|
'WIKILINKS: mention related memories by [[id]] or [[title]] in content — edges are auto-extracted.',
|
|
35
36
|
'IDEMPOTENT on (content_hash, type): calling twice with identical content returns the same id with {created: false}.',
|
|
@@ -58,6 +59,11 @@ export function buildPublicTools(store, config) {
|
|
|
58
59
|
description: 'Memory type: "notes" (default), "conversations", or any custom type.',
|
|
59
60
|
default: 'notes',
|
|
60
61
|
},
|
|
62
|
+
scope: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'Visibility scope: "personal" (default, private to you) or "workspace:<id>" for a team workspace.',
|
|
65
|
+
default: 'personal',
|
|
66
|
+
},
|
|
61
67
|
properties: {
|
|
62
68
|
type: 'object',
|
|
63
69
|
description: 'Optional extra metadata: source_url, author, sentiment, action_required, custom fields.',
|
|
@@ -70,6 +76,7 @@ export function buildPublicTools(store, config) {
|
|
|
70
76
|
const title = args.title;
|
|
71
77
|
const tags = args.tags;
|
|
72
78
|
const type = args.type ?? 'notes';
|
|
79
|
+
const scope = args.scope ?? 'personal';
|
|
73
80
|
const extraProps = args.properties ?? {};
|
|
74
81
|
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
75
82
|
// Idempotency: check for existing memory with same content_hash + type
|
|
@@ -87,6 +94,7 @@ export function buildPublicTools(store, config) {
|
|
|
87
94
|
const item = {
|
|
88
95
|
id: ulid(),
|
|
89
96
|
type,
|
|
97
|
+
scope,
|
|
90
98
|
source_id: `manual:${Date.now()}`,
|
|
91
99
|
content,
|
|
92
100
|
content_hash: contentHash,
|
|
@@ -125,11 +133,12 @@ export function buildPublicTools(store, config) {
|
|
|
125
133
|
description: [
|
|
126
134
|
'Retrieve past memories by semantic similarity to a query.',
|
|
127
135
|
'INPUTS: query (required, short topic — NOT the full user question), optional types (array to restrict scope), optional limit (default 10, max 50).',
|
|
136
|
+
'SCOPE: use scope="personal" (default) to search only personal memories, scope="workspace:<id>" for a specific team workspace, or scope="all" to search across personal + all joined workspaces.',
|
|
128
137
|
'WHEN: you need to surface past information on a topic. Use recall instead of recent when you have a specific subject in mind.',
|
|
129
138
|
'TIP: extract the topic noun from the user message. "what did I say about alice?" → query: "alice".',
|
|
130
139
|
'ANTI-LOOP: if results is empty, the answer is genuinely "no matching memories" — DO NOT call recall again with the same query.',
|
|
131
140
|
'Try a different query (synonym, broader topic, related entity) at most twice before telling the user "no matches".',
|
|
132
|
-
'RETURNS: array of { id, type, score, snippet, title, tags, created_at }.',
|
|
141
|
+
'RETURNS: array of { id, type, score, snippet, title, tags, created_at, scope }.',
|
|
133
142
|
].join(' '),
|
|
134
143
|
inputSchema: {
|
|
135
144
|
type: 'object',
|
|
@@ -143,6 +152,11 @@ export function buildPublicTools(store, config) {
|
|
|
143
152
|
items: { type: 'string' },
|
|
144
153
|
description: 'Restrict to these memory types (default: all). Faster when type is known.',
|
|
145
154
|
},
|
|
155
|
+
scope: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: '"personal" (default), "workspace:<id>", or "all" (personal + all workspaces).',
|
|
158
|
+
default: 'personal',
|
|
159
|
+
},
|
|
146
160
|
limit: {
|
|
147
161
|
type: 'number',
|
|
148
162
|
default: 10,
|
|
@@ -368,10 +382,11 @@ export function buildPublicTools(store, config) {
|
|
|
368
382
|
name: 'recent',
|
|
369
383
|
description: [
|
|
370
384
|
'Return the most recently created memories, sorted by created_at desc.',
|
|
385
|
+
'SCOPE: use scope="personal" (default) for private memories, scope="workspace:<id>" for team memories, or scope="all" for all scopes.',
|
|
371
386
|
'WHEN: start of a conversation — call recent({limit: 10}) to refresh context before answering.',
|
|
372
387
|
'Use recall instead of recent when you have a specific topic in mind.',
|
|
373
388
|
'ANTI-LOOP: returns at most limit items (default 20). If fewer returned, the store has fewer memories — DO NOT call again with a larger limit hoping for more.',
|
|
374
|
-
'RETURNS: array of { id, type, title, tags, snippet, created_at }.',
|
|
389
|
+
'RETURNS: array of { id, type, title, tags, snippet, created_at, scope }.',
|
|
375
390
|
].join(' '),
|
|
376
391
|
inputSchema: {
|
|
377
392
|
type: 'object',
|
|
@@ -386,20 +401,32 @@ export function buildPublicTools(store, config) {
|
|
|
386
401
|
items: { type: 'string' },
|
|
387
402
|
description: 'Restrict to these memory types (optional).',
|
|
388
403
|
},
|
|
404
|
+
scope: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
description: '"personal" (default), "workspace:<id>", or "all" (personal + all workspaces).',
|
|
407
|
+
default: 'personal',
|
|
408
|
+
},
|
|
389
409
|
},
|
|
390
410
|
},
|
|
391
411
|
handler: async (args) => {
|
|
392
412
|
const limit = args.limit ?? 20;
|
|
393
413
|
const types = args.types;
|
|
414
|
+
const scope = args.scope ?? 'personal';
|
|
394
415
|
const { getDb } = await import('../../db/index.js');
|
|
395
416
|
const db = getDb();
|
|
396
|
-
|
|
397
|
-
FROM memories`;
|
|
417
|
+
const conditions = [];
|
|
398
418
|
const params = [];
|
|
399
419
|
if (types && types.length > 0) {
|
|
400
|
-
|
|
420
|
+
conditions.push(`type IN (${types.map(() => '?').join(',')})`);
|
|
401
421
|
params.push(...types);
|
|
402
422
|
}
|
|
423
|
+
if (scope !== 'all') {
|
|
424
|
+
conditions.push('scope = ?');
|
|
425
|
+
params.push(scope);
|
|
426
|
+
}
|
|
427
|
+
let sql = `SELECT id, type, content, properties_json, created_at, scope FROM memories`;
|
|
428
|
+
if (conditions.length > 0)
|
|
429
|
+
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
403
430
|
sql += ` ORDER BY created_at DESC LIMIT ?`;
|
|
404
431
|
params.push(limit);
|
|
405
432
|
const rows = db.prepare(sql).all(...params);
|
|
@@ -408,6 +435,7 @@ export function buildPublicTools(store, config) {
|
|
|
408
435
|
return {
|
|
409
436
|
id: r.id,
|
|
410
437
|
type: r.type,
|
|
438
|
+
scope: r.scope ?? 'personal',
|
|
411
439
|
title: props.title,
|
|
412
440
|
tags: props.tags,
|
|
413
441
|
snippet: r.content.slice(0, 200),
|