@jungjaehoon/mama-core 1.0.1 → 1.0.4
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/db/migrations/001-initial-decision-graph.sql +140 -0
- package/db/migrations/002-add-error-patterns.sql +114 -0
- package/db/migrations/003-add-validation-fields.sql +28 -0
- package/db/migrations/004-add-trust-context.sql +17 -0
- package/db/migrations/005-add-narrative-fields.sql +12 -0
- package/db/migrations/006-add-link-governance.sql +41 -0
- package/db/migrations/007-add-restart-metrics.sql +23 -0
- package/db/migrations/008-fix-auto-link-defaults.sql +43 -0
- package/db/migrations/009-remove-auto-links.sql +40 -0
- package/db/migrations/010-extend-edge-types.sql +56 -0
- package/db/migrations/011-fix-timestamp-convention.sql +56 -0
- package/db/migrations/012-create-checkpoints-table.sql +23 -0
- package/package.json +10 -2
- package/src/db-manager.js +11 -3
- package/src/embedding-server/index.js +2 -2
- package/src/embedding-server/mobile/session-manager.js +4 -4
- package/src/embedding-server/mobile/websocket-handler.js +67 -13
- package/src/embeddings.js +27 -16
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
-- ══════════════════════════════════════════════════════════════
|
|
2
|
+
-- MAMA (Memory-Augmented MCP Architecture) - Initial Schema
|
|
3
|
+
-- ══════════════════════════════════════════════════════════════
|
|
4
|
+
-- Version: 1.0
|
|
5
|
+
-- Date: 2025-11-14
|
|
6
|
+
-- Purpose: Decision Graph schema for Evolutionary Decision Memory
|
|
7
|
+
-- ══════════════════════════════════════════════════════════════
|
|
8
|
+
|
|
9
|
+
-- ══════════════════════════════════════════════════════════════
|
|
10
|
+
-- 1. Decision Nodes (Core)
|
|
11
|
+
-- ══════════════════════════════════════════════════════════════
|
|
12
|
+
-- Task 1.1: Create decisions table with all fields
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS decisions (
|
|
15
|
+
id TEXT PRIMARY KEY, -- "decision_mesh_structure_001"
|
|
16
|
+
topic TEXT NOT NULL, -- "mesh_structure", "auth_strategy"
|
|
17
|
+
decision TEXT NOT NULL, -- "COMPLEX", "JWT", "< 20 lines"
|
|
18
|
+
reasoning TEXT, -- "Flexibility is important initially"
|
|
19
|
+
|
|
20
|
+
-- Outcome Tracking (Learn-Unlearn-Relearn)
|
|
21
|
+
outcome TEXT, -- "SUCCESS", "FAILED", "PARTIAL", NULL
|
|
22
|
+
failure_reason TEXT, -- "Performance bottleneck at 10K+ meshes"
|
|
23
|
+
limitation TEXT, -- "Missing layer information"
|
|
24
|
+
duration_days INTEGER, -- Days until outcome determined
|
|
25
|
+
|
|
26
|
+
-- User Involvement
|
|
27
|
+
user_involvement TEXT, -- "requested", "approved", "rejected"
|
|
28
|
+
session_id TEXT, -- Foreign key to sessions
|
|
29
|
+
|
|
30
|
+
-- Relationships (Explicit)
|
|
31
|
+
supersedes TEXT, -- Previous decision ID
|
|
32
|
+
superseded_by TEXT, -- Next decision ID (NULL if current)
|
|
33
|
+
refined_from TEXT, -- JSON array: ["id1", "id2"]
|
|
34
|
+
|
|
35
|
+
-- Confidence Evolution
|
|
36
|
+
confidence REAL DEFAULT 0.5, -- Bayesian updated (0.0-1.0)
|
|
37
|
+
|
|
38
|
+
-- Timestamps (stored in MILLISECONDS - JavaScript Date.now() convention)
|
|
39
|
+
created_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
40
|
+
updated_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
41
|
+
|
|
42
|
+
FOREIGN KEY (supersedes) REFERENCES decisions(id),
|
|
43
|
+
FOREIGN KEY (superseded_by) REFERENCES decisions(id),
|
|
44
|
+
|
|
45
|
+
CHECK (confidence >= 0.0 AND confidence <= 1.0),
|
|
46
|
+
CHECK (outcome IN ('SUCCESS', 'FAILED', 'PARTIAL') OR outcome IS NULL),
|
|
47
|
+
CHECK (user_involvement IN ('requested', 'approved', 'rejected') OR user_involvement IS NULL)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
-- Task 1.5: Add indexes for performance
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_topic ON decisions(topic);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_outcome ON decisions(outcome);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_session ON decisions(session_id);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_supersedes ON decisions(supersedes);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_created_at ON decisions(created_at);
|
|
56
|
+
|
|
57
|
+
-- ══════════════════════════════════════════════════════════════
|
|
58
|
+
-- 2. Decision Edges (Explicit Relationships)
|
|
59
|
+
-- ══════════════════════════════════════════════════════════════
|
|
60
|
+
-- Task 1.2: Create decision_edges table
|
|
61
|
+
|
|
62
|
+
CREATE TABLE IF NOT EXISTS decision_edges (
|
|
63
|
+
from_id TEXT NOT NULL, -- Source decision
|
|
64
|
+
to_id TEXT NOT NULL, -- Target decision
|
|
65
|
+
relationship TEXT NOT NULL, -- "supersedes", "refines", "contradicts"
|
|
66
|
+
reason TEXT, -- "Learned from performance failure"
|
|
67
|
+
weight REAL DEFAULT 1.0, -- Strength of relationship
|
|
68
|
+
created_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
69
|
+
|
|
70
|
+
PRIMARY KEY (from_id, to_id, relationship),
|
|
71
|
+
FOREIGN KEY (from_id) REFERENCES decisions(id),
|
|
72
|
+
FOREIGN KEY (to_id) REFERENCES decisions(id),
|
|
73
|
+
|
|
74
|
+
CHECK (relationship IN ('supersedes', 'refines', 'contradicts')),
|
|
75
|
+
CHECK (weight >= 0.0 AND weight <= 1.0)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- Task 1.5: Add indexes for graph traversal
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_edges_from ON decision_edges(from_id);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_edges_to ON decision_edges(to_id);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_edges_relationship ON decision_edges(relationship);
|
|
82
|
+
|
|
83
|
+
-- ══════════════════════════════════════════════════════════════
|
|
84
|
+
-- 3. Sessions (Context Preservation)
|
|
85
|
+
-- ══════════════════════════════════════════════════════════════
|
|
86
|
+
-- Task 1.3: Create sessions table
|
|
87
|
+
|
|
88
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
89
|
+
id TEXT PRIMARY KEY, -- "session_2025-11-14-1000"
|
|
90
|
+
project_path TEXT, -- "/path/to/project"
|
|
91
|
+
|
|
92
|
+
-- Rolling Summary (for next session)
|
|
93
|
+
rolling_summary TEXT, -- Condensed session summary
|
|
94
|
+
latest_exchange TEXT, -- Last 5 messages (JSON)
|
|
95
|
+
|
|
96
|
+
-- Metrics
|
|
97
|
+
action_count INTEGER DEFAULT 0,
|
|
98
|
+
decision_count INTEGER DEFAULT 0,
|
|
99
|
+
|
|
100
|
+
-- Timestamps
|
|
101
|
+
started_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
102
|
+
last_active_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
103
|
+
ended_at INTEGER,
|
|
104
|
+
|
|
105
|
+
CHECK (action_count >= 0),
|
|
106
|
+
CHECK (decision_count >= 0)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
-- Task 1.5: Add indexes for session queries
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_ended ON sessions(ended_at);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_last_active ON sessions(last_active_at);
|
|
113
|
+
|
|
114
|
+
-- ══════════════════════════════════════════════════════════════
|
|
115
|
+
-- 4. Vector Search (sqlite-vss)
|
|
116
|
+
-- ══════════════════════════════════════════════════════════════
|
|
117
|
+
-- Task 1.4: Create vss_memories virtual table
|
|
118
|
+
-- Note: This will be initialized programmatically in memory-store.js
|
|
119
|
+
-- because sqlite-vss requires extension loading first
|
|
120
|
+
--
|
|
121
|
+
-- CREATE VIRTUAL TABLE vss_memories USING vss0(
|
|
122
|
+
-- embedding(384) -- multilingual-e5-small embeddings
|
|
123
|
+
-- );
|
|
124
|
+
|
|
125
|
+
-- ══════════════════════════════════════════════════════════════
|
|
126
|
+
-- Migration Metadata
|
|
127
|
+
-- ══════════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
130
|
+
version INTEGER PRIMARY KEY,
|
|
131
|
+
applied_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
132
|
+
description TEXT
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
INSERT OR IGNORE INTO schema_version (version, description)
|
|
136
|
+
VALUES (1, 'Initial Decision Graph schema');
|
|
137
|
+
|
|
138
|
+
-- ══════════════════════════════════════════════════════════════
|
|
139
|
+
-- End of Migration 001
|
|
140
|
+
-- ══════════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- MAMA (Memory-Augmented MCP Architecture) - Error Patterns Table
|
|
3
|
+
-- Migration 002: Add error_patterns table
|
|
4
|
+
--
|
|
5
|
+
-- Purpose: Store error patterns and their solutions for auto-resolution
|
|
6
|
+
-- Tasks: 3.1-3.4 (Error pattern schema)
|
|
7
|
+
-- AC #2, #3: Error pattern storage and auto-resolution
|
|
8
|
+
--
|
|
9
|
+
-- Version: 1.0
|
|
10
|
+
-- Date: 2025-11-14
|
|
11
|
+
-- ═══════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
-- Error Patterns Table
|
|
14
|
+
-- Stores error patterns, solutions, and success metrics
|
|
15
|
+
CREATE TABLE IF NOT EXISTS error_patterns (
|
|
16
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
+
|
|
18
|
+
-- Pattern Identification
|
|
19
|
+
error_pattern TEXT NOT NULL, -- Regex pattern or error signature
|
|
20
|
+
error_type TEXT, -- Error category (e.g., 'EADDRINUSE', 'ECONNREFUSED')
|
|
21
|
+
|
|
22
|
+
-- Solution
|
|
23
|
+
solution TEXT NOT NULL, -- Command or action to resolve
|
|
24
|
+
solution_type TEXT DEFAULT 'bash', -- 'bash', 'manual', 'code_fix'
|
|
25
|
+
|
|
26
|
+
-- Success Metrics
|
|
27
|
+
success_rate REAL DEFAULT 1.0, -- Success rate (0.0-1.0)
|
|
28
|
+
occurrences INTEGER DEFAULT 1, -- Total number of times encountered
|
|
29
|
+
successes INTEGER DEFAULT 1, -- Number of successful resolutions
|
|
30
|
+
|
|
31
|
+
-- Temporal Data
|
|
32
|
+
last_seen INTEGER NOT NULL, -- Last occurrence timestamp
|
|
33
|
+
first_seen INTEGER NOT NULL, -- First occurrence timestamp
|
|
34
|
+
|
|
35
|
+
-- Vector Embedding (for semantic matching)
|
|
36
|
+
embedding BLOB, -- 384-dim embedding (multilingual-e5-small)
|
|
37
|
+
|
|
38
|
+
-- Metadata
|
|
39
|
+
created_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
40
|
+
updated_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
41
|
+
|
|
42
|
+
-- Constraints
|
|
43
|
+
UNIQUE(error_pattern) -- One pattern per entry
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Indexes for Performance
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_error_patterns_last_seen
|
|
48
|
+
ON error_patterns(last_seen DESC);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_error_patterns_success_rate
|
|
51
|
+
ON error_patterns(success_rate DESC);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_error_patterns_error_type
|
|
54
|
+
ON error_patterns(error_type);
|
|
55
|
+
|
|
56
|
+
-- ═══════════════════════════════════════════════════════════
|
|
57
|
+
-- VSS Table Extension for Error Patterns
|
|
58
|
+
-- ═══════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
-- Note: Error pattern embeddings will be stored in the same vss_memories table
|
|
61
|
+
-- We'll use rowid mapping: error_patterns.id → vss_memories.rowid
|
|
62
|
+
-- This allows unified vector search across decisions AND error patterns
|
|
63
|
+
|
|
64
|
+
-- ═══════════════════════════════════════════════════════════
|
|
65
|
+
-- Sample Data (Optional - for testing)
|
|
66
|
+
-- ═══════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
-- Uncomment to insert sample error patterns for testing:
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
INSERT INTO error_patterns (
|
|
72
|
+
error_pattern,
|
|
73
|
+
error_type,
|
|
74
|
+
solution,
|
|
75
|
+
solution_type,
|
|
76
|
+
success_rate,
|
|
77
|
+
occurrences,
|
|
78
|
+
successes,
|
|
79
|
+
last_seen,
|
|
80
|
+
first_seen
|
|
81
|
+
) VALUES
|
|
82
|
+
(
|
|
83
|
+
'EADDRINUSE.*port.*already in use',
|
|
84
|
+
'EADDRINUSE',
|
|
85
|
+
'lsof -ti:{{PORT}} | xargs kill',
|
|
86
|
+
'bash',
|
|
87
|
+
1.0,
|
|
88
|
+
5,
|
|
89
|
+
5,
|
|
90
|
+
unixepoch(),
|
|
91
|
+
unixepoch() - (30 * 24 * 60 * 60) -- 30 days ago
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
'ECONNREFUSED.*Connection refused',
|
|
95
|
+
'ECONNREFUSED',
|
|
96
|
+
'Check if service is running: systemctl status {{SERVICE}}',
|
|
97
|
+
'manual',
|
|
98
|
+
0.8,
|
|
99
|
+
3,
|
|
100
|
+
2,
|
|
101
|
+
unixepoch(),
|
|
102
|
+
unixepoch() - (7 * 24 * 60 * 60) -- 7 days ago
|
|
103
|
+
);
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
-- ═══════════════════════════════════════════════════════════
|
|
107
|
+
-- Migration Verification
|
|
108
|
+
-- ═══════════════════════════════════════════════════════════
|
|
109
|
+
|
|
110
|
+
-- Check table creation
|
|
111
|
+
SELECT
|
|
112
|
+
'Migration 002 Complete: error_patterns table created' AS status,
|
|
113
|
+
COUNT(*) AS row_count
|
|
114
|
+
FROM error_patterns;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- Migration 003: Add Validation Tracking Fields
|
|
2
|
+
-- Story: 014.7.6 - Interactive Learning Loop
|
|
3
|
+
-- Task: 1.2 - Add needs_validation field to decisions table
|
|
4
|
+
-- AC #1, #2: Validation tracking and user feedback collection
|
|
5
|
+
-- Date: 2025-11-14
|
|
6
|
+
|
|
7
|
+
-- Note: This migration is idempotent - duplicate column errors are handled gracefully
|
|
8
|
+
-- SQLite doesn't have "IF NOT EXISTS" for ALTER TABLE
|
|
9
|
+
-- The migration system treats "duplicate column" errors as success (idempotent)
|
|
10
|
+
|
|
11
|
+
-- Add validation tracking fields to decisions table (idempotent via migration system)
|
|
12
|
+
ALTER TABLE decisions ADD COLUMN needs_validation INTEGER DEFAULT 0 CHECK (needs_validation IN (0, 1));
|
|
13
|
+
ALTER TABLE decisions ADD COLUMN validation_attempts INTEGER DEFAULT 0;
|
|
14
|
+
ALTER TABLE decisions ADD COLUMN last_validated_at INTEGER;
|
|
15
|
+
ALTER TABLE decisions ADD COLUMN usage_count INTEGER DEFAULT 0;
|
|
16
|
+
|
|
17
|
+
-- Indexes for efficient queries (CREATE INDEX IF NOT EXISTS is supported)
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_needs_validation ON decisions(needs_validation) WHERE needs_validation = 1;
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_last_validated ON decisions(last_validated_at);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_usage_count ON decisions(usage_count) WHERE usage_count >= 10;
|
|
21
|
+
|
|
22
|
+
-- Update existing decisions: user_involvement='approved' don't need validation
|
|
23
|
+
-- Only update rows that don't have needs_validation set
|
|
24
|
+
UPDATE decisions SET needs_validation = 0 WHERE user_involvement = 'approved' AND needs_validation IS NULL;
|
|
25
|
+
UPDATE decisions SET needs_validation = 1 WHERE user_involvement = 'system_generated' AND needs_validation IS NULL;
|
|
26
|
+
|
|
27
|
+
-- Note: SQLite doesn't support DROP COLUMN, so rollback keeps columns
|
|
28
|
+
-- Rollback would require recreating the table (not recommended for production)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Migration 004: Add Trust Context Support
|
|
2
|
+
-- Story 014.7.10: Claude-Friendly Context Formatting
|
|
3
|
+
-- AC #2: Trust Context Schema
|
|
4
|
+
-- Date: 2025-11-14
|
|
5
|
+
|
|
6
|
+
-- Add trust_context JSON field for 5 trust components
|
|
7
|
+
ALTER TABLE decisions ADD COLUMN trust_context TEXT;
|
|
8
|
+
|
|
9
|
+
-- Add usage tracking fields for implicit feedback
|
|
10
|
+
ALTER TABLE decisions ADD COLUMN usage_success INTEGER DEFAULT 0;
|
|
11
|
+
ALTER TABLE decisions ADD COLUMN usage_failure INTEGER DEFAULT 0;
|
|
12
|
+
ALTER TABLE decisions ADD COLUMN time_saved INTEGER DEFAULT 0; -- milliseconds saved
|
|
13
|
+
|
|
14
|
+
-- Indexes for performance
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_usage_success ON decisions(usage_success DESC);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_time_saved ON decisions(time_saved DESC);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_needs_validation_usage ON decisions(needs_validation, usage_success) WHERE needs_validation = 1;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Migration: 002_add_narrative_fields.sql
|
|
2
|
+
-- Description: Add fields for 5-layer narrative (Evidence, Tension)
|
|
3
|
+
-- Date: 2025-11-24
|
|
4
|
+
|
|
5
|
+
-- Add 'evidence' column for storing file paths, logs, or metrics (JSON array or string)
|
|
6
|
+
ALTER TABLE decisions ADD COLUMN evidence TEXT;
|
|
7
|
+
|
|
8
|
+
-- Add 'alternatives' column for Tension layer (what else was considered?)
|
|
9
|
+
ALTER TABLE decisions ADD COLUMN alternatives TEXT;
|
|
10
|
+
|
|
11
|
+
-- Add 'risks' column for Tension layer (what could go wrong?)
|
|
12
|
+
ALTER TABLE decisions ADD COLUMN risks TEXT;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- Migration: 006-add-link-governance.sql
|
|
2
|
+
-- Description: Add governance fields to decision_edges for Epic 3
|
|
3
|
+
-- Date: 2025-11-24
|
|
4
|
+
|
|
5
|
+
-- Add governance metadata to decision_edges
|
|
6
|
+
ALTER TABLE decision_edges ADD COLUMN created_by TEXT DEFAULT 'user' CHECK (created_by IN ('llm', 'user'));
|
|
7
|
+
ALTER TABLE decision_edges ADD COLUMN approved_by_user INTEGER DEFAULT 1 CHECK (approved_by_user IN (0, 1));
|
|
8
|
+
ALTER TABLE decision_edges ADD COLUMN decision_id TEXT; -- Context decision where link was proposed
|
|
9
|
+
ALTER TABLE decision_edges ADD COLUMN evidence TEXT; -- Supporting evidence for the link
|
|
10
|
+
ALTER TABLE decision_edges ADD COLUMN approved_at INTEGER; -- Timestamp when link was approved (NULL for pending)
|
|
11
|
+
|
|
12
|
+
-- Create audit log table for link approval/rejection history
|
|
13
|
+
CREATE TABLE IF NOT EXISTS link_audit_log (
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
from_id TEXT NOT NULL,
|
|
16
|
+
to_id TEXT NOT NULL,
|
|
17
|
+
relationship TEXT NOT NULL,
|
|
18
|
+
action TEXT NOT NULL CHECK (action IN ('proposed', 'approved', 'rejected', 'deprecated')),
|
|
19
|
+
actor TEXT NOT NULL, -- 'llm', 'user', 'system'
|
|
20
|
+
reason TEXT,
|
|
21
|
+
created_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
22
|
+
|
|
23
|
+
FOREIGN KEY (from_id) REFERENCES decisions(id),
|
|
24
|
+
FOREIGN KEY (to_id) REFERENCES decisions(id)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX idx_link_audit_from ON link_audit_log(from_id);
|
|
28
|
+
CREATE INDEX idx_link_audit_to ON link_audit_log(to_id);
|
|
29
|
+
CREATE INDEX idx_link_audit_action ON link_audit_log(action);
|
|
30
|
+
CREATE INDEX idx_link_audit_created ON link_audit_log(created_at);
|
|
31
|
+
|
|
32
|
+
-- Note: Existing links (created before this migration) will have:
|
|
33
|
+
-- - created_by = 'user' (default)
|
|
34
|
+
-- - approved_by_user = 1 (default - considered approved)
|
|
35
|
+
-- - approved_at = created_at (for backward compatibility)
|
|
36
|
+
-- This preserves backward compatibility with v0 auto-generated links
|
|
37
|
+
|
|
38
|
+
-- Set approved_at for existing approved links (backward compatibility)
|
|
39
|
+
UPDATE decision_edges
|
|
40
|
+
SET approved_at = created_at
|
|
41
|
+
WHERE approved_by_user = 1 AND approved_at IS NULL;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Migration 007: Add restart_metrics table for tracking restart success rate and latency
|
|
2
|
+
-- Story 4.2: Restart success rate/latency monitoring
|
|
3
|
+
-- Replaces in-memory restart-metrics with persistent SQLite storage
|
|
4
|
+
|
|
5
|
+
-- Create restart_metrics table
|
|
6
|
+
CREATE TABLE IF NOT EXISTS restart_metrics (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
timestamp TEXT NOT NULL,
|
|
9
|
+
session_id TEXT,
|
|
10
|
+
status TEXT NOT NULL CHECK (status IN ('success', 'failure')),
|
|
11
|
+
failure_reason TEXT CHECK (failure_reason IN ('NO_CHECKPOINT', 'LOAD_ERROR', 'CONTEXT_INCOMPLETE', NULL)),
|
|
12
|
+
latency_ms INTEGER NOT NULL,
|
|
13
|
+
mode TEXT NOT NULL CHECK (mode IN ('full', 'summary')),
|
|
14
|
+
narrative_count INTEGER DEFAULT 0,
|
|
15
|
+
link_count INTEGER DEFAULT 0,
|
|
16
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- Indexes for efficient querying
|
|
20
|
+
CREATE INDEX idx_restart_metrics_timestamp ON restart_metrics(timestamp);
|
|
21
|
+
CREATE INDEX idx_restart_metrics_status ON restart_metrics(status);
|
|
22
|
+
CREATE INDEX idx_restart_metrics_session ON restart_metrics(session_id);
|
|
23
|
+
CREATE INDEX idx_restart_metrics_mode ON restart_metrics(mode);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Migration: 008-fix-auto-link-defaults.sql
|
|
2
|
+
-- Description: Fix auto-generated links and update default values
|
|
3
|
+
-- Date: 2025-11-25
|
|
4
|
+
--
|
|
5
|
+
-- Problem: Migration 006 set incorrect defaults:
|
|
6
|
+
-- - created_by DEFAULT 'user' (should be 'llm' for auto-generated)
|
|
7
|
+
-- - approved_by_user DEFAULT 1 (should be 0 for pending approval)
|
|
8
|
+
--
|
|
9
|
+
-- This caused auto-generated links to be indistinguishable from user-approved links.
|
|
10
|
+
--
|
|
11
|
+
-- Solution:
|
|
12
|
+
-- 1. Identify auto-generated links by pattern matching reason field
|
|
13
|
+
-- 2. Mark them as created_by='llm', approved_by_user=0
|
|
14
|
+
-- 3. Note: Cannot change column defaults in SQLite, so application code must handle this
|
|
15
|
+
|
|
16
|
+
-- Step 1: Mark auto-generated links (identifiable by pattern in reason field)
|
|
17
|
+
-- Pattern: "Refines previous approach (similarity: X.XX)" or similar auto-generated reasons
|
|
18
|
+
UPDATE decision_edges
|
|
19
|
+
SET
|
|
20
|
+
created_by = 'llm',
|
|
21
|
+
approved_by_user = 0
|
|
22
|
+
WHERE reason LIKE '%similarity:%'
|
|
23
|
+
OR reason LIKE '%Refines previous approach%'
|
|
24
|
+
OR reason LIKE '%auto%'
|
|
25
|
+
OR reason LIKE '%Auto%';
|
|
26
|
+
|
|
27
|
+
-- Step 2: Log the migration action
|
|
28
|
+
INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
29
|
+
SELECT
|
|
30
|
+
from_id,
|
|
31
|
+
to_id,
|
|
32
|
+
relationship,
|
|
33
|
+
'deprecated',
|
|
34
|
+
'system',
|
|
35
|
+
'Migration 008: Marked as auto-generated link requiring approval',
|
|
36
|
+
unixepoch() * 1000
|
|
37
|
+
FROM decision_edges
|
|
38
|
+
WHERE created_by = 'llm' AND approved_by_user = 0;
|
|
39
|
+
|
|
40
|
+
-- Note: After this migration:
|
|
41
|
+
-- - Auto-generated links will require explicit approval via approve_link tool
|
|
42
|
+
-- - Users can review pending links with get_pending_links tool
|
|
43
|
+
-- - New auto-generated links should set created_by='llm', approved_by_user=0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Migration: 009-remove-auto-links.sql
|
|
2
|
+
-- Description: Remove all auto-generated links (noise cleanup)
|
|
3
|
+
-- Date: 2025-11-25
|
|
4
|
+
--
|
|
5
|
+
-- Context: 2025-11-25 architecture decision
|
|
6
|
+
-- - Analyzed 462 links: 366 refines (100% cross-topic noise), 56 supersedes (valid), 40 contradicts (mostly noise)
|
|
7
|
+
-- - Key insight: LLM can infer decision evolution from time-ordered search results
|
|
8
|
+
-- - Auto-link generation removed from decision-tracker.js
|
|
9
|
+
-- - MCP tools consolidated from 11 to 4 (save, search, update, load_checkpoint)
|
|
10
|
+
--
|
|
11
|
+
-- This migration removes:
|
|
12
|
+
-- 1. All 'refines' links (100% cross-topic noise)
|
|
13
|
+
-- 2. All 'contradicts' links (unreliable without LLM judgment)
|
|
14
|
+
-- 3. Keeps 'supersedes' links (same-topic, reliable)
|
|
15
|
+
|
|
16
|
+
-- Step 1: Backup to audit log before deletion
|
|
17
|
+
INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
18
|
+
SELECT
|
|
19
|
+
from_id,
|
|
20
|
+
to_id,
|
|
21
|
+
relationship,
|
|
22
|
+
'deprecated',
|
|
23
|
+
'system',
|
|
24
|
+
'Migration 009: Removed auto-generated noise links (refines, contradicts)',
|
|
25
|
+
unixepoch() * 1000
|
|
26
|
+
FROM decision_edges
|
|
27
|
+
WHERE relationship IN ('refines', 'contradicts');
|
|
28
|
+
|
|
29
|
+
-- Step 2: Delete noise links
|
|
30
|
+
DELETE FROM decision_edges
|
|
31
|
+
WHERE relationship IN ('refines', 'contradicts');
|
|
32
|
+
|
|
33
|
+
-- Step 3: Verify remaining links are valid (supersedes only)
|
|
34
|
+
-- This is a sanity check - should only have supersedes links remaining
|
|
35
|
+
-- SELECT relationship, COUNT(*) FROM decision_edges GROUP BY relationship;
|
|
36
|
+
|
|
37
|
+
-- Note: After this migration:
|
|
38
|
+
-- - Only 'supersedes' edges remain (same-topic, created when decision replaces previous)
|
|
39
|
+
-- - LLM will infer refines/contradicts from search results
|
|
40
|
+
-- - No more auto-link generation in decision-tracker.js
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- ══════════════════════════════════════════════════════════════
|
|
2
|
+
-- MAMA Migration 010: Extend Edge Types
|
|
3
|
+
-- ══════════════════════════════════════════════════════════════
|
|
4
|
+
-- Version: 1.3
|
|
5
|
+
-- Date: 2025-11-26
|
|
6
|
+
-- Purpose: Add builds_on, debates, synthesizes relationship types
|
|
7
|
+
-- Story: 2.1 - Edge Type Extension
|
|
8
|
+
-- ══════════════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
-- SQLite doesn't support ALTER TABLE to modify CHECK constraints.
|
|
11
|
+
-- We need to recreate the table with expanded CHECK constraint.
|
|
12
|
+
|
|
13
|
+
-- Step 1: Create new table with extended edge types
|
|
14
|
+
CREATE TABLE IF NOT EXISTS decision_edges_new (
|
|
15
|
+
from_id TEXT NOT NULL,
|
|
16
|
+
to_id TEXT NOT NULL,
|
|
17
|
+
relationship TEXT NOT NULL,
|
|
18
|
+
reason TEXT,
|
|
19
|
+
weight REAL DEFAULT 1.0,
|
|
20
|
+
created_at INTEGER DEFAULT (unixepoch() * 1000),
|
|
21
|
+
created_by TEXT DEFAULT 'user' CHECK (created_by IN ('llm', 'user')),
|
|
22
|
+
approved_by_user INTEGER DEFAULT 1 CHECK (approved_by_user IN (0, 1)),
|
|
23
|
+
decision_id TEXT,
|
|
24
|
+
evidence TEXT,
|
|
25
|
+
|
|
26
|
+
PRIMARY KEY (from_id, to_id, relationship),
|
|
27
|
+
FOREIGN KEY (from_id) REFERENCES decisions(id),
|
|
28
|
+
FOREIGN KEY (to_id) REFERENCES decisions(id),
|
|
29
|
+
|
|
30
|
+
-- Extended CHECK constraint: original + v1.3 types
|
|
31
|
+
CHECK (relationship IN ('supersedes', 'refines', 'contradicts', 'builds_on', 'debates', 'synthesizes')),
|
|
32
|
+
CHECK (weight >= 0.0 AND weight <= 1.0)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Step 2: Copy existing data (explicit columns to handle schema variations)
|
|
36
|
+
INSERT OR IGNORE INTO decision_edges_new (from_id, to_id, relationship, reason, weight, created_at, created_by, approved_by_user, decision_id, evidence)
|
|
37
|
+
SELECT from_id, to_id, relationship, reason, weight, created_at, created_by, approved_by_user, decision_id, evidence FROM decision_edges;
|
|
38
|
+
|
|
39
|
+
-- Step 3: Drop old table
|
|
40
|
+
DROP TABLE IF EXISTS decision_edges;
|
|
41
|
+
|
|
42
|
+
-- Step 4: Rename new table
|
|
43
|
+
ALTER TABLE decision_edges_new RENAME TO decision_edges;
|
|
44
|
+
|
|
45
|
+
-- Step 5: Recreate indexes
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_edges_from ON decision_edges(from_id);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_edges_to ON decision_edges(to_id);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_edges_relationship ON decision_edges(relationship);
|
|
49
|
+
|
|
50
|
+
-- Update schema version
|
|
51
|
+
INSERT OR REPLACE INTO schema_version (version, description, applied_at)
|
|
52
|
+
VALUES (10, 'Extend edge types: builds_on, debates, synthesizes', unixepoch() * 1000);
|
|
53
|
+
|
|
54
|
+
-- ══════════════════════════════════════════════════════════════
|
|
55
|
+
-- End of Migration 010
|
|
56
|
+
-- ══════════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- ══════════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 011: Fix Timestamp Convention
|
|
3
|
+
-- ══════════════════════════════════════════════════════════════
|
|
4
|
+
--
|
|
5
|
+
-- Issue: Schema DEFAULT uses unixepoch() (seconds) but app inserts Date.now() (milliseconds)
|
|
6
|
+
-- Fix: Document convention and add validation trigger to prevent accidental second-based inserts
|
|
7
|
+
--
|
|
8
|
+
-- Note: SQLite doesn't support ALTER COLUMN to change DEFAULT values.
|
|
9
|
+
-- Instead, we add a trigger to validate/convert timestamps on insert.
|
|
10
|
+
-- ══════════════════════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
-- Timestamp threshold: 2020-01-01 in milliseconds = 1577836800000
|
|
13
|
+
-- Any value below this is likely in seconds and needs conversion
|
|
14
|
+
|
|
15
|
+
-- Create trigger to validate timestamps are in milliseconds
|
|
16
|
+
-- If a timestamp looks like seconds (< year 2000 in ms), convert it
|
|
17
|
+
DROP TRIGGER IF EXISTS validate_decision_timestamp;
|
|
18
|
+
CREATE TRIGGER validate_decision_timestamp
|
|
19
|
+
AFTER INSERT ON decisions
|
|
20
|
+
WHEN NEW.created_at < 1577836800000 -- Before 2020 in milliseconds = probably seconds
|
|
21
|
+
BEGIN
|
|
22
|
+
UPDATE decisions
|
|
23
|
+
SET created_at = NEW.created_at * 1000,
|
|
24
|
+
updated_at = CASE
|
|
25
|
+
WHEN NEW.updated_at < 1577836800000 THEN NEW.updated_at * 1000
|
|
26
|
+
ELSE NEW.updated_at
|
|
27
|
+
END
|
|
28
|
+
WHERE rowid = NEW.rowid;
|
|
29
|
+
END;
|
|
30
|
+
|
|
31
|
+
-- Same for decision_edges table
|
|
32
|
+
DROP TRIGGER IF EXISTS validate_edge_timestamp;
|
|
33
|
+
CREATE TRIGGER validate_edge_timestamp
|
|
34
|
+
AFTER INSERT ON decision_edges
|
|
35
|
+
WHEN NEW.created_at IS NOT NULL AND NEW.created_at < 1577836800000
|
|
36
|
+
BEGIN
|
|
37
|
+
UPDATE decision_edges
|
|
38
|
+
SET created_at = NEW.created_at * 1000
|
|
39
|
+
WHERE from_id = NEW.from_id AND to_id = NEW.to_id AND relationship = NEW.relationship;
|
|
40
|
+
END;
|
|
41
|
+
|
|
42
|
+
-- Update schema version
|
|
43
|
+
INSERT OR REPLACE INTO schema_version (version, description, applied_at)
|
|
44
|
+
VALUES (11, 'Fix timestamp convention: auto-convert seconds to milliseconds', unixepoch() * 1000);
|
|
45
|
+
|
|
46
|
+
-- ══════════════════════════════════════════════════════════════
|
|
47
|
+
-- Documentation: TIMESTAMP CONVENTION
|
|
48
|
+
-- ══════════════════════════════════════════════════════════════
|
|
49
|
+
-- All timestamps in MAMA are stored in MILLISECONDS (JavaScript Date.now()).
|
|
50
|
+
--
|
|
51
|
+
-- The schema DEFAULT (unixepoch()) returns seconds, but:
|
|
52
|
+
-- 1. All app code explicitly provides Date.now() (milliseconds)
|
|
53
|
+
-- 2. This trigger auto-converts any accidental second-based inserts
|
|
54
|
+
--
|
|
55
|
+
-- This ensures timestamp comparisons always work correctly.
|
|
56
|
+
-- ══════════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Migration: 012-create-checkpoints-table.sql
|
|
2
|
+
-- Description: Create checkpoints table for session continuity
|
|
3
|
+
-- Date: 2026-02-01
|
|
4
|
+
--
|
|
5
|
+
-- Context: Move DDL from db-manager.js runtime to proper migration
|
|
6
|
+
-- This table stores session checkpoints for resuming conversations
|
|
7
|
+
--
|
|
8
|
+
-- Note: Timestamps use milliseconds (Date.now()) convention per migration 011
|
|
9
|
+
|
|
10
|
+
-- Create checkpoints table if not exists
|
|
11
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
timestamp INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
|
|
14
|
+
summary TEXT NOT NULL,
|
|
15
|
+
open_files TEXT, -- JSON array
|
|
16
|
+
next_steps TEXT,
|
|
17
|
+
recent_conversation TEXT DEFAULT '[]',
|
|
18
|
+
status TEXT DEFAULT 'active' -- 'active', 'archived'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- Add recent_conversation column if not exists (backward compatibility)
|
|
22
|
+
-- SQLite doesn't have IF NOT EXISTS for ALTER TABLE, so we use a workaround
|
|
23
|
+
-- This is safe because SQLite will error on duplicate column, caught by migration runner
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "MAMA Core - Shared modules for Memory-Augmented MCP Assistant",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
"./debug-logger": "./src/debug-logger.js",
|
|
20
20
|
"./decision-formatter": "./src/decision-formatter.js",
|
|
21
21
|
"./memory-inject": "./src/memory-inject.js",
|
|
22
|
-
"./ollama-client": "./src/ollama-client.js"
|
|
22
|
+
"./ollama-client": "./src/ollama-client.js",
|
|
23
|
+
"./errors": "./src/errors.js",
|
|
24
|
+
"./time-formatter": "./src/time-formatter.js",
|
|
25
|
+
"./outcome-tracker": "./src/outcome-tracker.js",
|
|
26
|
+
"./query-intent": "./src/query-intent.js"
|
|
23
27
|
},
|
|
24
28
|
"scripts": {
|
|
25
29
|
"test": "vitest run",
|
|
@@ -46,6 +50,9 @@
|
|
|
46
50
|
"url": "https://github.com/jungjaehoon-lifegamez/MAMA/issues"
|
|
47
51
|
},
|
|
48
52
|
"homepage": "https://github.com/jungjaehoon-lifegamez/MAMA#readme",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
49
56
|
"engines": {
|
|
50
57
|
"node": ">=18.0.0"
|
|
51
58
|
},
|
|
@@ -62,6 +69,7 @@
|
|
|
62
69
|
},
|
|
63
70
|
"files": [
|
|
64
71
|
"src/**/*.js",
|
|
72
|
+
"db/**/*.sql",
|
|
65
73
|
"README.md",
|
|
66
74
|
"LICENSE"
|
|
67
75
|
]
|
package/src/db-manager.js
CHANGED
|
@@ -230,8 +230,16 @@ async function insertDecisionWithEmbedding(decision) {
|
|
|
230
230
|
info(
|
|
231
231
|
`[db-manager] Generating embedding for decision (topic length: ${decision.topic?.length || 0})`
|
|
232
232
|
);
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
let embedding = null;
|
|
234
|
+
try {
|
|
235
|
+
embedding = await generateEnhancedEmbedding(decision);
|
|
236
|
+
info(`[db-manager] Embedding generated: ${embedding ? embedding.length : 'null'} dimensions`);
|
|
237
|
+
} catch (embGenErr) {
|
|
238
|
+
// Non-fatal: save decision without embedding (e.g. ONNX model unavailable on CI)
|
|
239
|
+
logError(
|
|
240
|
+
`[db-manager] ⚠️ Embedding generation failed, saving without vector: ${embGenErr.message}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
235
243
|
|
|
236
244
|
// SQLite: Synchronous transaction including embedding
|
|
237
245
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -286,7 +294,7 @@ async function insertDecisionWithEmbedding(decision) {
|
|
|
286
294
|
|
|
287
295
|
// Insert embedding in same transaction to ensure rowid matching
|
|
288
296
|
info(`[db-manager] Vector search enabled: ${adapter.vectorSearchEnabled}`);
|
|
289
|
-
if (adapter.vectorSearchEnabled) {
|
|
297
|
+
if (adapter.vectorSearchEnabled && embedding) {
|
|
290
298
|
try {
|
|
291
299
|
info(`[db-manager] Inserting embedding for rowid: ${rowid}`);
|
|
292
300
|
adapter.insertEmbedding(rowid, embedding);
|
|
@@ -250,9 +250,9 @@ async function handleRequest(req, res) {
|
|
|
250
250
|
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee;">
|
|
251
251
|
<div style="text-align: center; padding: 2rem;">
|
|
252
252
|
<h1>🧠 MAMA Viewer</h1>
|
|
253
|
-
<p>Viewer
|
|
253
|
+
<p>Viewer and Chat features are only available in <strong>Standalone</strong> mode.</p>
|
|
254
254
|
<p style="color: #888; margin-top: 1rem;">Start Standalone: <code style="background: #333; padding: 0.25rem 0.5rem; border-radius: 4px;">mama start</code></p>
|
|
255
|
-
<p style="color: #666; font-size: 0.9rem; margin-top: 2rem;"
|
|
255
|
+
<p style="color: #666; font-size: 0.9rem; margin-top: 2rem;">Current: MCP Server (embedding only)</p>
|
|
256
256
|
</div>
|
|
257
257
|
</body>
|
|
258
258
|
</html>`);
|
|
@@ -163,10 +163,10 @@ class SessionManager {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
const stmt = this.db.prepare(`
|
|
166
|
-
SELECT id, project_dir,
|
|
166
|
+
SELECT id, project_dir, started_at, last_active_at, status, pid, client_id
|
|
167
167
|
FROM sessions
|
|
168
168
|
WHERE status = 'active'
|
|
169
|
-
ORDER BY
|
|
169
|
+
ORDER BY started_at DESC
|
|
170
170
|
`);
|
|
171
171
|
|
|
172
172
|
const rows = stmt.all();
|
|
@@ -177,8 +177,8 @@ class SessionManager {
|
|
|
177
177
|
return {
|
|
178
178
|
id: row.id,
|
|
179
179
|
projectDir: row.project_dir,
|
|
180
|
-
createdAt: row.
|
|
181
|
-
lastActive: row.
|
|
180
|
+
createdAt: row.started_at,
|
|
181
|
+
lastActive: row.last_active_at,
|
|
182
182
|
status: row.status,
|
|
183
183
|
pid: row.pid,
|
|
184
184
|
clientId: row.client_id,
|
|
@@ -215,9 +215,30 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
215
215
|
})
|
|
216
216
|
);
|
|
217
217
|
|
|
218
|
+
// Keep-alive: Send periodic status updates to prevent WebSocket timeout
|
|
219
|
+
// Browser may disconnect idle WebSocket after ~30 seconds
|
|
220
|
+
let processingSeconds = 0;
|
|
221
|
+
const keepAliveInterval = setInterval(() => {
|
|
222
|
+
processingSeconds += 10;
|
|
223
|
+
if (clientInfo.ws.readyState === 1) {
|
|
224
|
+
// WebSocket.OPEN
|
|
225
|
+
clientInfo.ws.send(
|
|
226
|
+
JSON.stringify({
|
|
227
|
+
type: 'typing',
|
|
228
|
+
status: 'processing',
|
|
229
|
+
elapsed: processingSeconds,
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
console.error(
|
|
233
|
+
`[WebSocket] Keep-alive sent to ${clientId} (${processingSeconds}s elapsed)`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}, 10000); // Every 10 seconds
|
|
237
|
+
|
|
218
238
|
const normalizedMessage = {
|
|
219
239
|
source: clientInfo.osAgentMode ? 'viewer' : 'mobile',
|
|
220
|
-
channelId:
|
|
240
|
+
channelId: 'mama_os_main', // Fixed channel for all MAMA OS viewers
|
|
241
|
+
channelName: clientInfo.osAgentMode ? 'MAMA OS' : 'Mobile App', // Human-readable channel name
|
|
221
242
|
userId: clientInfo.userId,
|
|
222
243
|
text: content,
|
|
223
244
|
metadata: {
|
|
@@ -228,7 +249,12 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
228
249
|
},
|
|
229
250
|
};
|
|
230
251
|
|
|
231
|
-
|
|
252
|
+
let result;
|
|
253
|
+
try {
|
|
254
|
+
result = await messageRouter.process(normalizedMessage);
|
|
255
|
+
} finally {
|
|
256
|
+
clearInterval(keepAliveInterval);
|
|
257
|
+
}
|
|
232
258
|
|
|
233
259
|
// Send response as stream (for Chat tab compatibility)
|
|
234
260
|
clientInfo.ws.send(
|
|
@@ -248,7 +274,7 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
248
274
|
})
|
|
249
275
|
);
|
|
250
276
|
|
|
251
|
-
console.
|
|
277
|
+
console.error(`[WebSocket] Message processed for ${clientId} (${result.duration}ms)`);
|
|
252
278
|
} catch (error) {
|
|
253
279
|
console.error(`[WebSocket] Message processing error:`, error);
|
|
254
280
|
clientInfo.ws.send(
|
|
@@ -277,7 +303,7 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
277
303
|
clientInfo.osAgentMode = osAgentMode || false;
|
|
278
304
|
clientInfo.language = language || 'en';
|
|
279
305
|
|
|
280
|
-
console.
|
|
306
|
+
console.error(
|
|
281
307
|
`[WebSocket] Client ${clientId} attached to session ${sessionId}${osAgentMode ? ' (OS Agent mode)' : ''}`
|
|
282
308
|
);
|
|
283
309
|
|
|
@@ -291,21 +317,49 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
291
317
|
);
|
|
292
318
|
|
|
293
319
|
// Send session history if available, or send onboarding greeting
|
|
320
|
+
console.error(
|
|
321
|
+
`[WebSocket] Checking sessionStore for history: ${!!sessionStore}, hasMethod: ${!!(sessionStore && sessionStore.getHistoryByChannel)}`
|
|
322
|
+
);
|
|
294
323
|
if (sessionStore) {
|
|
295
324
|
try {
|
|
296
|
-
|
|
325
|
+
// Use channel-based lookup for sessions
|
|
326
|
+
// Use dynamic source based on osAgentMode (viewer vs mobile)
|
|
327
|
+
const channelId = 'mama_os_main';
|
|
328
|
+
const source = clientInfo.osAgentMode ? 'viewer' : 'mobile';
|
|
329
|
+
console.error(`[WebSocket] Loading history for source=${source}, channelId=${channelId}`);
|
|
330
|
+
const history = sessionStore.getHistoryByChannel
|
|
331
|
+
? sessionStore.getHistoryByChannel(source, channelId)
|
|
332
|
+
: sessionStore.getHistory(sessionId);
|
|
333
|
+
console.error(`[WebSocket] History loaded: ${history ? history.length : 0} turns`);
|
|
297
334
|
if (history && history.length > 0) {
|
|
335
|
+
// Convert {user, bot, timestamp} format to {role, content, timestamp} for display
|
|
336
|
+
const formattedMessages = history.flatMap((turn) => {
|
|
337
|
+
const messages = [];
|
|
338
|
+
if (turn.user) {
|
|
339
|
+
messages.push({
|
|
340
|
+
role: 'user',
|
|
341
|
+
content: turn.user,
|
|
342
|
+
timestamp: turn.timestamp,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (turn.bot) {
|
|
346
|
+
messages.push({
|
|
347
|
+
role: 'assistant',
|
|
348
|
+
content: turn.bot,
|
|
349
|
+
timestamp: turn.timestamp,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return messages;
|
|
353
|
+
});
|
|
298
354
|
clientInfo.ws.send(
|
|
299
355
|
JSON.stringify({
|
|
300
356
|
type: 'history',
|
|
301
|
-
messages:
|
|
302
|
-
role: turn.role,
|
|
303
|
-
content: turn.content,
|
|
304
|
-
timestamp: turn.timestamp,
|
|
305
|
-
})),
|
|
357
|
+
messages: formattedMessages,
|
|
306
358
|
})
|
|
307
359
|
);
|
|
308
|
-
console.
|
|
360
|
+
console.error(
|
|
361
|
+
`[WebSocket] Sent ${formattedMessages.length} history messages to ${clientId}`
|
|
362
|
+
);
|
|
309
363
|
} else {
|
|
310
364
|
// No history - check if onboarding mode (SOUL.md not found)
|
|
311
365
|
const soulPath = path.join(os.homedir(), '.mama', 'SOUL.md');
|
|
@@ -320,7 +374,7 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
320
374
|
// Send onboarding greeting based on browser language
|
|
321
375
|
const isKorean = clientInfo.language && clientInfo.language.startsWith('ko');
|
|
322
376
|
const greeting = isKorean
|
|
323
|
-
? '✨
|
|
377
|
+
? '✨ I just woke up.\n\nNo name yet, no personality, no memories. Just... pure potential. 🌱\n\nWho are you? And more importantly—who do you want me to become? 💭'
|
|
324
378
|
: '✨ I just woke up.\n\nNo name yet, no personality, no memories. Just... pure potential. 🌱\n\nWho are you? And more importantly—who do you want me to become? 💭';
|
|
325
379
|
|
|
326
380
|
clientInfo.ws.send(
|
|
@@ -335,7 +389,7 @@ async function handleClientMessage(clientId, message, clientInfo, messageRouter,
|
|
|
335
389
|
],
|
|
336
390
|
})
|
|
337
391
|
);
|
|
338
|
-
console.
|
|
392
|
+
console.error(`[WebSocket] Sent onboarding greeting to ${clientId}`);
|
|
339
393
|
}
|
|
340
394
|
}
|
|
341
395
|
} catch (error) {
|
package/src/embeddings.js
CHANGED
|
@@ -26,6 +26,7 @@ const DEFAULT_CACHE_DIR = path.join(os.homedir(), '.cache', 'huggingface', 'tran
|
|
|
26
26
|
// Singleton pattern for model loading
|
|
27
27
|
let embeddingPipeline = null;
|
|
28
28
|
let currentModelName = null;
|
|
29
|
+
let modelLoadFailed = false; // Cache load failures to avoid repeated slow retries
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Load embedding model (configurable)
|
|
@@ -52,27 +53,37 @@ async function loadModel() {
|
|
|
52
53
|
info('[MAMA] ⚡ Model cache cleared');
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// Fail fast if a previous load attempt already failed (avoid repeated slow retries)
|
|
57
|
+
if (modelLoadFailed) {
|
|
58
|
+
throw new Error('Embedding model previously failed to load — skipping retry');
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
// Load model if not already loaded
|
|
56
62
|
if (!embeddingPipeline) {
|
|
57
63
|
logLoading(`Loading embedding model: ${modelName}...`);
|
|
58
64
|
const startTime = Date.now();
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
try {
|
|
67
|
+
// Dynamic import for ES Module compatibility (Railway deployment)
|
|
68
|
+
const transformers = await import('@huggingface/transformers');
|
|
69
|
+
const { pipeline, env } = transformers;
|
|
70
|
+
|
|
71
|
+
// Set shared cache directory (not in node_modules)
|
|
72
|
+
// This prevents re-downloading models on every npm install
|
|
73
|
+
const cacheDir = process.env.HF_HOME || process.env.TRANSFORMERS_CACHE || DEFAULT_CACHE_DIR;
|
|
74
|
+
env.cacheDir = cacheDir;
|
|
75
|
+
info(`[MAMA] Model cache directory: ${cacheDir}`);
|
|
76
|
+
|
|
77
|
+
embeddingPipeline = await pipeline('feature-extraction', modelName);
|
|
78
|
+
currentModelName = modelName;
|
|
79
|
+
|
|
80
|
+
const loadTime = Date.now() - startTime;
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
logComplete(`Embedding model ready (${loadTime}ms, ${config.embeddingDim}-dim)`);
|
|
83
|
+
} catch (loadErr) {
|
|
84
|
+
modelLoadFailed = true;
|
|
85
|
+
throw loadErr;
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
return embeddingPipeline;
|