@jungjaehoon/mama-server 1.11.1 → 1.12.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/package.json +1 -1
- package/src/server.js +162 -23
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -198,19 +198,69 @@ class MAMAServer {
|
|
|
198
198
|
? `${this.getLegacyMigrationNotice()}\n\n`
|
|
199
199
|
: '';
|
|
200
200
|
|
|
201
|
-
const
|
|
201
|
+
const tools = [
|
|
202
|
+
// 1. SAVE — decisions, checkpoints, conversation ingestion
|
|
202
203
|
{
|
|
203
204
|
name: 'save',
|
|
204
|
-
description: `${legacyNotice}
|
|
205
|
+
description: `${legacyNotice}Save to MAMA memory. Use type parameter to choose what to save.
|
|
206
|
+
|
|
207
|
+
**type='decision'** — Save architectural decisions, lessons learned, insights.
|
|
208
|
+
Required: topic, decision, reasoning. Optional: confidence, scopes, event_date.
|
|
209
|
+
Triggers: user says "기억해", "remember", "decided". Reuse same topic to create evolution chain.
|
|
210
|
+
|
|
211
|
+
**type='checkpoint'** — Save session state for resumption.
|
|
212
|
+
Required: summary (4-section: Goal, Evidence, Unfinished, Next Briefing).
|
|
213
|
+
Optional: next_steps, open_files. Triggers: session ending, "체크포인트", "save progress".
|
|
214
|
+
|
|
215
|
+
**type='ingest'** — Import conversation messages into memory with optional extraction.
|
|
216
|
+
Required: messages (array of {role, content}). Optional: scopes, session_date, extract.
|
|
217
|
+
|
|
218
|
+
**Scopes**: Isolate memories per project/channel. Example: [{"kind":"project","id":"/my/app"}]
|
|
219
|
+
**event_date**: ISO 8601 date when event occurred (e.g. "2024-01-15"), not when saved.`,
|
|
205
220
|
inputSchema: {
|
|
206
221
|
type: 'object',
|
|
207
222
|
properties: {
|
|
208
|
-
...memoryTools.save_decision.inputSchema.properties,
|
|
209
223
|
type: {
|
|
210
224
|
type: 'string',
|
|
211
|
-
enum: ['decision', 'checkpoint'],
|
|
212
|
-
description: "What to save: 'decision' or '
|
|
225
|
+
enum: ['decision', 'checkpoint', 'ingest'],
|
|
226
|
+
description: "What to save: 'decision', 'checkpoint', or 'ingest'",
|
|
227
|
+
},
|
|
228
|
+
// Decision fields
|
|
229
|
+
topic: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: '[Decision] Topic identifier. Reuse same topic = supersedes previous.',
|
|
232
|
+
},
|
|
233
|
+
decision: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: '[Decision] The decision made.',
|
|
236
|
+
},
|
|
237
|
+
reasoning: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: "[Decision] Why. End with 'builds_on: <id>' or 'debates: <id>' to link.",
|
|
240
|
+
},
|
|
241
|
+
confidence: {
|
|
242
|
+
type: 'number',
|
|
243
|
+
description: '[Decision] 0.0-1.0. Default: 0.5',
|
|
244
|
+
minimum: 0,
|
|
245
|
+
maximum: 1,
|
|
246
|
+
},
|
|
247
|
+
scopes: {
|
|
248
|
+
type: 'array',
|
|
249
|
+
items: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
253
|
+
id: { type: 'string' },
|
|
254
|
+
},
|
|
255
|
+
required: ['kind', 'id'],
|
|
256
|
+
},
|
|
257
|
+
description: 'Memory scopes for isolation.',
|
|
213
258
|
},
|
|
259
|
+
event_date: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'ISO 8601 date when event occurred (e.g. "2024-01-15").',
|
|
262
|
+
},
|
|
263
|
+
// Checkpoint fields
|
|
214
264
|
summary: {
|
|
215
265
|
type: 'string',
|
|
216
266
|
description: '[Checkpoint] Session state summary.',
|
|
@@ -224,45 +274,110 @@ class MAMAServer {
|
|
|
224
274
|
items: { type: 'string' },
|
|
225
275
|
description: '[Checkpoint] Currently relevant files.',
|
|
226
276
|
},
|
|
277
|
+
// Ingest fields
|
|
278
|
+
messages: {
|
|
279
|
+
type: 'array',
|
|
280
|
+
items: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
role: { type: 'string', enum: ['user', 'assistant', 'system'] },
|
|
284
|
+
content: { type: 'string' },
|
|
285
|
+
},
|
|
286
|
+
required: ['role', 'content'],
|
|
287
|
+
},
|
|
288
|
+
description: '[Ingest] Conversation messages to import.',
|
|
289
|
+
},
|
|
290
|
+
session_date: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
description: '[Ingest] ISO 8601 date when conversation occurred.',
|
|
293
|
+
},
|
|
294
|
+
extract: {
|
|
295
|
+
type: 'boolean',
|
|
296
|
+
description: '[Ingest] Extract structured memories via LLM. Default: false',
|
|
297
|
+
},
|
|
227
298
|
},
|
|
228
299
|
required: ['type'],
|
|
229
300
|
},
|
|
230
301
|
},
|
|
302
|
+
// 2. SEARCH — unified search across decisions, checkpoints, load latest checkpoint
|
|
231
303
|
{
|
|
232
304
|
name: 'search',
|
|
233
|
-
description:
|
|
305
|
+
description: `Search MAMA memory. Returns results ranked by semantic similarity.
|
|
306
|
+
|
|
307
|
+
**With query** — Semantic search across decisions and checkpoints. Cross-lingual (Korean + English).
|
|
308
|
+
Triggers: "뭐였더라", "what did we decide", making architectural choices, debugging.
|
|
309
|
+
|
|
310
|
+
**Without query** — List recent items sorted by time.
|
|
311
|
+
|
|
312
|
+
**Resume session**: type='checkpoint' without query → loads latest checkpoint with full context (narrative, links, next steps).
|
|
313
|
+
Triggers: "이어서", "continue", "where were we", session start.
|
|
314
|
+
|
|
315
|
+
**type parameter**: 'decision' (choices/lessons only), 'checkpoint' (session states / resume), 'all' (both, default).
|
|
316
|
+
**scopes**: Filter by project/channel. Omit for global search.
|
|
317
|
+
**limit**: Max results (default: 10).
|
|
318
|
+
|
|
319
|
+
⚠️ REQUIRED: Call search BEFORE save to find related decisions and avoid orphans.`,
|
|
234
320
|
inputSchema: {
|
|
235
321
|
type: 'object',
|
|
236
322
|
properties: {
|
|
237
323
|
query: {
|
|
238
324
|
type: 'string',
|
|
239
|
-
description: 'Search query.
|
|
325
|
+
description: 'Search query. Omit to list recent items.',
|
|
240
326
|
},
|
|
241
327
|
type: {
|
|
242
328
|
type: 'string',
|
|
243
329
|
enum: ['all', 'decision', 'checkpoint'],
|
|
244
330
|
description: "Filter by type. Default: 'all'",
|
|
245
331
|
},
|
|
246
|
-
limit: { type: 'number', description: '
|
|
247
|
-
scopes:
|
|
332
|
+
limit: { type: 'number', description: 'Max results. Default: 10' },
|
|
333
|
+
scopes: {
|
|
334
|
+
type: 'array',
|
|
335
|
+
items: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
339
|
+
id: { type: 'string' },
|
|
340
|
+
},
|
|
341
|
+
required: ['kind', 'id'],
|
|
342
|
+
},
|
|
343
|
+
description: 'Filter by scope.',
|
|
344
|
+
},
|
|
248
345
|
},
|
|
249
346
|
},
|
|
250
347
|
},
|
|
348
|
+
// 3. UPDATE — decision outcome tracking
|
|
251
349
|
{
|
|
252
350
|
name: 'update',
|
|
253
|
-
description:
|
|
254
|
-
|
|
351
|
+
description: `Update decision outcome after real-world validation.
|
|
352
|
+
|
|
353
|
+
Triggers: "이거 안됐어", "this worked", days later when issues discovered.
|
|
354
|
+
outcome: 'success', 'failed', 'partial' (case-insensitive).
|
|
355
|
+
After failure → save a NEW decision with same topic to create evolution history.`,
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: 'object',
|
|
358
|
+
properties: {
|
|
359
|
+
id: { type: 'string', description: 'Decision ID to update.' },
|
|
360
|
+
outcome: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: "'success', 'failed', or 'partial' (case-insensitive).",
|
|
363
|
+
},
|
|
364
|
+
reason: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Why it succeeded/failed/was partial. Include evidence.',
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
required: ['id', 'outcome'],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
// 4. SEARCH_DECISIONS_AND_CONTRACTS — PreToolUse hook RPC
|
|
373
|
+
{
|
|
374
|
+
name: 'search_decisions_and_contracts',
|
|
375
|
+
description: 'Search decisions and contracts for PreToolUse hook injection.',
|
|
376
|
+
inputSchema: memoryTools.search_decisions_and_contracts.inputSchema,
|
|
255
377
|
},
|
|
256
378
|
];
|
|
257
379
|
|
|
258
|
-
|
|
259
|
-
const v2Tools = Object.values(memoryTools)
|
|
260
|
-
.filter((t) => t.name && t.inputSchema)
|
|
261
|
-
.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));
|
|
262
|
-
|
|
263
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
264
|
-
tools: [...legacyTools, ...v2Tools],
|
|
265
|
-
}));
|
|
380
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
266
381
|
|
|
267
382
|
// Handle tool execution — legacy wrappers + v2 tools from src/tools/
|
|
268
383
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -377,7 +492,11 @@ class MAMAServer {
|
|
|
377
492
|
};
|
|
378
493
|
}
|
|
379
494
|
|
|
380
|
-
|
|
495
|
+
if (type === 'ingest') {
|
|
496
|
+
return await memoryTools.ingest_conversation.handler(args);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return { success: false, message: "❌ type must be 'decision', 'checkpoint', or 'ingest'" };
|
|
381
500
|
}
|
|
382
501
|
|
|
383
502
|
/**
|
|
@@ -386,6 +505,11 @@ class MAMAServer {
|
|
|
386
505
|
async handleSearch(args) {
|
|
387
506
|
const { query, type = 'all', limit = 10, scopes } = args;
|
|
388
507
|
|
|
508
|
+
// type='checkpoint' without query → load latest checkpoint (resume session)
|
|
509
|
+
if (type === 'checkpoint' && !query) {
|
|
510
|
+
return await memoryTools.load_checkpoint.handler(args);
|
|
511
|
+
}
|
|
512
|
+
|
|
389
513
|
const results = [];
|
|
390
514
|
|
|
391
515
|
// Search decisions
|
|
@@ -400,7 +524,6 @@ class MAMAServer {
|
|
|
400
524
|
} else {
|
|
401
525
|
decisions = await mama.list({ limit, ...(scopes && { scopes }) });
|
|
402
526
|
}
|
|
403
|
-
// Ensure decisions is an array
|
|
404
527
|
if (Array.isArray(decisions)) {
|
|
405
528
|
results.push(
|
|
406
529
|
...decisions.map((d) => ({
|
|
@@ -411,8 +534,24 @@ class MAMAServer {
|
|
|
411
534
|
}
|
|
412
535
|
}
|
|
413
536
|
|
|
414
|
-
// Search checkpoints
|
|
415
|
-
if (type === 'all' || type === 'checkpoint') {
|
|
537
|
+
// Search checkpoints (with query = search, without = handled above as load)
|
|
538
|
+
if ((type === 'all' || type === 'checkpoint') && query) {
|
|
539
|
+
const checkpoints = await mama.listCheckpoints(limit);
|
|
540
|
+
results.push(
|
|
541
|
+
...checkpoints
|
|
542
|
+
.filter((c) => c.summary && c.summary.toLowerCase().includes(query.toLowerCase()))
|
|
543
|
+
.map((c) => ({
|
|
544
|
+
id: `checkpoint_${c.id}`,
|
|
545
|
+
summary: c.summary,
|
|
546
|
+
next_steps: c.next_steps,
|
|
547
|
+
created_at: c.timestamp,
|
|
548
|
+
_type: 'checkpoint',
|
|
549
|
+
}))
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// type='all' without query — include recent checkpoints
|
|
554
|
+
if (type === 'all' && !query) {
|
|
416
555
|
const checkpoints = await mama.listCheckpoints(limit);
|
|
417
556
|
results.push(
|
|
418
557
|
...checkpoints.map((c) => ({
|