@jungjaehoon/mama-core 1.1.3 → 1.1.5
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/dist/db-manager.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface DecisionRecord {
|
|
|
53
53
|
refined_from?: string | string[] | null;
|
|
54
54
|
created_at: number;
|
|
55
55
|
updated_at?: number;
|
|
56
|
-
edges?:
|
|
56
|
+
edges?: DecisionEdgeRow[];
|
|
57
57
|
}
|
|
58
58
|
export interface OutcomeData {
|
|
59
59
|
outcome?: string | null;
|
|
@@ -68,17 +68,40 @@ export interface VectorSearchParams {
|
|
|
68
68
|
threshold?: number;
|
|
69
69
|
timeWindow?: number;
|
|
70
70
|
}
|
|
71
|
+
export interface DecisionEdgeRow {
|
|
72
|
+
from_id: string;
|
|
73
|
+
to_id: string;
|
|
74
|
+
relationship: string;
|
|
75
|
+
reason?: string | null;
|
|
76
|
+
weight?: number;
|
|
77
|
+
created_at?: number;
|
|
78
|
+
created_by?: string;
|
|
79
|
+
approved_by_user?: number | null;
|
|
80
|
+
decision_id?: string | null;
|
|
81
|
+
evidence?: string | null;
|
|
82
|
+
}
|
|
83
|
+
export interface SemanticEdgeItem {
|
|
84
|
+
from_id: string;
|
|
85
|
+
to_id: string;
|
|
86
|
+
relationship: string;
|
|
87
|
+
reason?: string;
|
|
88
|
+
topic: string;
|
|
89
|
+
decision: string;
|
|
90
|
+
confidence?: number;
|
|
91
|
+
created_at?: string;
|
|
92
|
+
approved_by_user?: number | null;
|
|
93
|
+
}
|
|
71
94
|
export interface SemanticEdges {
|
|
72
|
-
refines:
|
|
73
|
-
refined_by:
|
|
74
|
-
contradicts:
|
|
75
|
-
contradicted_by:
|
|
76
|
-
builds_on:
|
|
77
|
-
built_on_by:
|
|
78
|
-
debates:
|
|
79
|
-
debated_by:
|
|
80
|
-
synthesizes:
|
|
81
|
-
synthesized_by:
|
|
95
|
+
refines: SemanticEdgeItem[];
|
|
96
|
+
refined_by: SemanticEdgeItem[];
|
|
97
|
+
contradicts: SemanticEdgeItem[];
|
|
98
|
+
contradicted_by: SemanticEdgeItem[];
|
|
99
|
+
builds_on: SemanticEdgeItem[];
|
|
100
|
+
built_on_by: SemanticEdgeItem[];
|
|
101
|
+
debates: SemanticEdgeItem[];
|
|
102
|
+
debated_by: SemanticEdgeItem[];
|
|
103
|
+
synthesizes: SemanticEdgeItem[];
|
|
104
|
+
synthesized_by: SemanticEdgeItem[];
|
|
82
105
|
}
|
|
83
106
|
/**
|
|
84
107
|
* Initialize SQLite database adapter and connect
|
|
@@ -20,8 +20,18 @@ import type { Server as HTTPServer } from 'http';
|
|
|
20
20
|
/**
|
|
21
21
|
* Message router interface
|
|
22
22
|
*/
|
|
23
|
+
interface StreamCallbacks {
|
|
24
|
+
onDelta?: (text: string) => void;
|
|
25
|
+
onToolUse?: (toolName: string, input: Record<string, unknown>) => void;
|
|
26
|
+
onToolComplete?: (toolName: string, toolUseId: string, isError: boolean) => void;
|
|
27
|
+
onFinal?: (response: unknown) => void;
|
|
28
|
+
onError?: (error: Error) => void;
|
|
29
|
+
}
|
|
23
30
|
interface MessageRouter {
|
|
24
|
-
process(message: NormalizedMessage
|
|
31
|
+
process(message: NormalizedMessage, options?: {
|
|
32
|
+
onQueued?: () => void;
|
|
33
|
+
onStream?: StreamCallbacks;
|
|
34
|
+
}): Promise<RouterResult>;
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
37
|
* Session store interface
|
|
@@ -81,6 +81,22 @@ function extractUserId(req) {
|
|
|
81
81
|
return `user_${Date.now()}`;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Safe send helper - guards against closed socket with try-catch for race conditions
|
|
86
|
+
*/
|
|
87
|
+
function safeSend(ws, data) {
|
|
88
|
+
if (ws.readyState === ws_1.WebSocket.OPEN) {
|
|
89
|
+
try {
|
|
90
|
+
ws.send(data);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Socket may have closed between readyState check and send
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
84
100
|
/**
|
|
85
101
|
* Create WebSocket handler with MessageRouter integration
|
|
86
102
|
*/
|
|
@@ -202,6 +218,8 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
202
218
|
}
|
|
203
219
|
}, 10000); // Every 10 seconds
|
|
204
220
|
let result;
|
|
221
|
+
let deltasSent = false;
|
|
222
|
+
let toolIndex = 0;
|
|
205
223
|
try {
|
|
206
224
|
// Build contentBlocks from attachments (files are pre-compressed by upload-handler)
|
|
207
225
|
let contentBlocks = undefined;
|
|
@@ -269,19 +287,52 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
269
287
|
timestamp: Date.now(),
|
|
270
288
|
},
|
|
271
289
|
};
|
|
272
|
-
result = await messageRouter.process(normalizedMessage
|
|
290
|
+
result = await messageRouter.process(normalizedMessage, {
|
|
291
|
+
onQueued: () => {
|
|
292
|
+
// Guard: check if socket is still open before sending
|
|
293
|
+
if (clientInfo.ws.readyState !== ws_1.WebSocket.OPEN) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Notify client that message is queued (session busy)
|
|
297
|
+
try {
|
|
298
|
+
clientInfo.ws.send(JSON.stringify({
|
|
299
|
+
type: 'queued',
|
|
300
|
+
message: '⏳ 이전 요청 처리 중... 대기열에 추가되었습니다.',
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
logger.warn('Queued notification failed:', err instanceof Error ? err.message : String(err));
|
|
305
|
+
}
|
|
306
|
+
logger.info(`Message queued for ${clientId} (session busy)`);
|
|
307
|
+
},
|
|
308
|
+
onStream: {
|
|
309
|
+
onDelta: (text) => {
|
|
310
|
+
deltasSent = true;
|
|
311
|
+
safeSend(clientInfo.ws, JSON.stringify({ type: 'stream', content: text }));
|
|
312
|
+
},
|
|
313
|
+
onToolUse: (tool, input) => {
|
|
314
|
+
const idx = toolIndex++;
|
|
315
|
+
safeSend(clientInfo.ws, JSON.stringify({ type: 'tool_use', tool, index: idx, input }));
|
|
316
|
+
},
|
|
317
|
+
onToolComplete: (tool, _toolUseId, isError) => {
|
|
318
|
+
safeSend(clientInfo.ws, JSON.stringify({ type: 'tool_complete', tool, index: toolIndex - 1, isError }));
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
273
322
|
}
|
|
274
323
|
finally {
|
|
275
324
|
clearInterval(keepAliveInterval);
|
|
276
325
|
}
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
326
|
+
// Only send full response if no deltas were streamed (Codex fallback)
|
|
327
|
+
if (!deltasSent) {
|
|
328
|
+
safeSend(clientInfo.ws, JSON.stringify({
|
|
329
|
+
type: 'stream',
|
|
330
|
+
content: result.response,
|
|
331
|
+
sessionId: result.sessionId,
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
// Always send stream end
|
|
335
|
+
safeSend(clientInfo.ws, JSON.stringify({
|
|
285
336
|
type: 'stream_end',
|
|
286
337
|
sessionId: result.sessionId,
|
|
287
338
|
duration: result.duration,
|
|
@@ -291,7 +342,7 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
291
342
|
catch (error) {
|
|
292
343
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
293
344
|
logger.error(`Message processing error for ${clientId}:`, errMsg);
|
|
294
|
-
clientInfo.ws
|
|
345
|
+
safeSend(clientInfo.ws, JSON.stringify({
|
|
295
346
|
type: 'error',
|
|
296
347
|
error: 'Failed to process message',
|
|
297
348
|
// Don't expose internal error details to client for security
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export { generateEmbedding, generateEnhancedEmbedding, generateBatchEmbeddings, cosineSimilarity, embeddingCache, EMBEDDING_DIM, MODEL_NAME, } from './embeddings.js';
|
|
11
11
|
export { EmbeddingCache } from './embedding-cache.js';
|
|
12
12
|
export { getEmbeddingFromServer, getServerStatus, isServerRunning, getServerPort, DEFAULT_PORT, HOST, TIMEOUT_MS, type ServerStatus, } from './embedding-client.js';
|
|
13
|
-
export { initDB, getDB, getAdapter, closeDB, insertEmbedding, vectorSearch, queryVectorSearch, insertDecisionWithEmbedding, queryDecisionGraph, querySemanticEdges, updateDecisionOutcome, getPreparedStmt, getDbPath, type DatabaseAdapter as DBManagerAdapter, type PreparedStatement, type DecisionRecord, type OutcomeData, type VectorSearchParams, type SemanticEdges, type DecisionInput, } from './db-manager.js';
|
|
13
|
+
export { initDB, getDB, getAdapter, closeDB, insertEmbedding, vectorSearch, queryVectorSearch, insertDecisionWithEmbedding, queryDecisionGraph, querySemanticEdges, updateDecisionOutcome, getPreparedStmt, getDbPath, type DatabaseAdapter as DBManagerAdapter, type PreparedStatement, type DecisionRecord, type OutcomeData, type VectorSearchParams, type SemanticEdges, type SemanticEdgeItem, type DecisionInput, } from './db-manager.js';
|
|
14
14
|
export { createAdapter, DatabaseAdapter, SQLiteAdapter, type AdapterConfig, type Statement, type VectorSearchResult, type RunResult, } from './db-adapter/index.js';
|
|
15
15
|
export { getSessionDecisions, incrementUsageSuccess, incrementUsageFailure, getDecisionById, traverseDecisionChain, DB_PATH, DB_DIR, LEGACY_DB_PATH, DEFAULT_DB_PATH, } from './memory-store.js';
|
|
16
16
|
import mama from './mama-api.js';
|
package/dist/mama-api.d.ts
CHANGED
|
@@ -172,6 +172,9 @@ export interface LoadCheckpointResult {
|
|
|
172
172
|
* Outcome badge map type
|
|
173
173
|
*/
|
|
174
174
|
export type OutcomeBadgeMap = Record<string, string | null>;
|
|
175
|
+
/**
|
|
176
|
+
* Raw semantic edge from database
|
|
177
|
+
*/
|
|
175
178
|
/**
|
|
176
179
|
* Recall options
|
|
177
180
|
*/
|
|
@@ -412,7 +415,51 @@ declare function save({ topic, decision, reasoning, confidence, type, outcome, f
|
|
|
412
415
|
* const markdown = await mama.recall('auth_strategy', { format: 'markdown' });
|
|
413
416
|
* // → "📋 Decision History: auth_strategy\n━━━━━━━━..."
|
|
414
417
|
*/
|
|
415
|
-
|
|
418
|
+
interface RecallEdgeRef {
|
|
419
|
+
to_topic?: string;
|
|
420
|
+
to_decision?: string;
|
|
421
|
+
to_id?: string;
|
|
422
|
+
from_topic?: string;
|
|
423
|
+
from_decision?: string;
|
|
424
|
+
from_id?: string;
|
|
425
|
+
reason?: string | null;
|
|
426
|
+
confidence?: number;
|
|
427
|
+
created_at?: string | number;
|
|
428
|
+
}
|
|
429
|
+
interface RecallGraphResult {
|
|
430
|
+
topic: string;
|
|
431
|
+
supersedes_chain: Array<{
|
|
432
|
+
id: string;
|
|
433
|
+
decision: string;
|
|
434
|
+
reasoning?: string | null;
|
|
435
|
+
confidence?: number;
|
|
436
|
+
outcome?: string | null;
|
|
437
|
+
failure_reason?: string | null;
|
|
438
|
+
created_at: number;
|
|
439
|
+
updated_at?: number;
|
|
440
|
+
superseded_by?: string | null;
|
|
441
|
+
supersedes?: string | null;
|
|
442
|
+
}>;
|
|
443
|
+
semantic_edges: {
|
|
444
|
+
refines: RecallEdgeRef[];
|
|
445
|
+
refined_by: RecallEdgeRef[];
|
|
446
|
+
contradicts: RecallEdgeRef[];
|
|
447
|
+
contradicted_by: RecallEdgeRef[];
|
|
448
|
+
};
|
|
449
|
+
meta: {
|
|
450
|
+
count: number;
|
|
451
|
+
latest_id?: string;
|
|
452
|
+
has_supersedes_chain: boolean;
|
|
453
|
+
has_semantic_edges: boolean;
|
|
454
|
+
semantic_edges_count: {
|
|
455
|
+
refines: number;
|
|
456
|
+
refined_by: number;
|
|
457
|
+
contradicts: number;
|
|
458
|
+
contradicted_by: number;
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
declare function recall(topic: string, options?: RecallOptions): Promise<string | RecallGraphResult>;
|
|
416
463
|
/**
|
|
417
464
|
* Update outcome of a decision
|
|
418
465
|
*
|
|
@@ -505,13 +552,21 @@ declare function saveCheckpoint(summary: string, openFiles?: string[], nextSteps
|
|
|
505
552
|
*
|
|
506
553
|
* @returns {Promise<Object|null>} Latest checkpoint or null
|
|
507
554
|
*/
|
|
555
|
+
interface ConversationMessage {
|
|
556
|
+
role: string;
|
|
557
|
+
content: string | Array<{
|
|
558
|
+
type: string;
|
|
559
|
+
text?: string;
|
|
560
|
+
[key: string]: unknown;
|
|
561
|
+
}>;
|
|
562
|
+
}
|
|
508
563
|
interface CheckpointRow {
|
|
509
564
|
id?: number;
|
|
510
565
|
timestamp?: number;
|
|
511
566
|
summary?: string;
|
|
512
567
|
open_files?: string | string[];
|
|
513
568
|
next_steps?: string;
|
|
514
|
-
recent_conversation?: string |
|
|
569
|
+
recent_conversation?: string | ConversationMessage[];
|
|
515
570
|
status?: string;
|
|
516
571
|
}
|
|
517
572
|
declare function loadCheckpoint(): Promise<CheckpointRow | null>;
|
package/dist/mama-api.js
CHANGED
|
@@ -347,26 +347,6 @@ async function _getReasoningGraphInfo(topic, currentId) {
|
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Recall decisions by topic
|
|
352
|
-
*
|
|
353
|
-
* DEFAULT: Returns JSON object with decisions and edges (LLM-first design)
|
|
354
|
-
* OPTIONAL: Returns Markdown string if format='markdown' (for human display)
|
|
355
|
-
*
|
|
356
|
-
* @param {string} topic - Decision topic to recall
|
|
357
|
-
* @param {Object} [options] - Options
|
|
358
|
-
* @param {string} [options.format='json'] - Output format: 'json' (default) or 'markdown'
|
|
359
|
-
* @returns {Promise<Object|string>} Decision history as JSON or Markdown
|
|
360
|
-
*
|
|
361
|
-
* @example
|
|
362
|
-
* // LLM usage (default)
|
|
363
|
-
* const data = await mama.recall('auth_strategy');
|
|
364
|
-
* // → { topic, decisions: [...], edges: [...], meta: {...} }
|
|
365
|
-
*
|
|
366
|
-
* // Human display
|
|
367
|
-
* const markdown = await mama.recall('auth_strategy', { format: 'markdown' });
|
|
368
|
-
* // → "📋 Decision History: auth_strategy\n━━━━━━━━..."
|
|
369
|
-
*/
|
|
370
350
|
async function recall(topic, options = {}) {
|
|
371
351
|
if (!topic || typeof topic !== 'string') {
|
|
372
352
|
throw new Error('mama.recall() requires topic (string)');
|
|
@@ -382,18 +362,22 @@ async function recall(topic, options = {}) {
|
|
|
382
362
|
topic,
|
|
383
363
|
supersedes_chain: [],
|
|
384
364
|
semantic_edges: { refines: [], refined_by: [], contradicts: [], contradicted_by: [] },
|
|
385
|
-
meta: {
|
|
365
|
+
meta: {
|
|
366
|
+
count: 0,
|
|
367
|
+
has_supersedes_chain: false,
|
|
368
|
+
has_semantic_edges: false,
|
|
369
|
+
semantic_edges_count: { refines: 0, refined_by: 0, contradicts: 0, contradicted_by: 0 },
|
|
370
|
+
},
|
|
386
371
|
};
|
|
387
372
|
}
|
|
388
373
|
// Query semantic edges for all decisions
|
|
389
374
|
const decisionIds = decisions.map((d) => d.id);
|
|
390
|
-
const
|
|
391
|
-
const rawEdges = (rawEdgesResult || {});
|
|
375
|
+
const rawEdges = await (0, memory_store_js_1.querySemanticEdges)(decisionIds);
|
|
392
376
|
const semanticEdges = {
|
|
393
|
-
refines:
|
|
394
|
-
refined_by:
|
|
395
|
-
contradicts:
|
|
396
|
-
contradicted_by:
|
|
377
|
+
refines: rawEdges.refines || [],
|
|
378
|
+
refined_by: rawEdges.refined_by || [],
|
|
379
|
+
contradicts: rawEdges.contradicts || [],
|
|
380
|
+
contradicted_by: rawEdges.contradicted_by || [],
|
|
397
381
|
};
|
|
398
382
|
// Markdown format (for human display)
|
|
399
383
|
if (format === 'markdown') {
|
|
@@ -566,7 +550,6 @@ async function expandWithGraph(candidates) {
|
|
|
566
550
|
synthesized_by: rawEdges.synthesized_by || [],
|
|
567
551
|
};
|
|
568
552
|
// Helper to add edge to graph
|
|
569
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic property access with idField
|
|
570
553
|
const addEdge = (edge, idField, source, rank, simFactor) => {
|
|
571
554
|
const id = edge[idField];
|
|
572
555
|
if (!graphEnhanced.has(id)) {
|
|
@@ -698,8 +681,10 @@ async function suggest(userQuestion, options = {}) {
|
|
|
698
681
|
let results = [];
|
|
699
682
|
let searchMethod = 'vector';
|
|
700
683
|
try {
|
|
701
|
-
// Check if
|
|
702
|
-
(0, memory_store_js_1.
|
|
684
|
+
// Check if vector search is available (sqlite-vec loaded)
|
|
685
|
+
if (!(0, memory_store_js_1.getAdapter)().vectorSearchEnabled) {
|
|
686
|
+
throw new Error('Vector search not available');
|
|
687
|
+
}
|
|
703
688
|
// Generate query embedding
|
|
704
689
|
const queryEmbedding = await (0, embeddings_js_1.generateEmbedding)(userQuestion);
|
|
705
690
|
// Adaptive threshold (shorter queries need higher confidence)
|