prism-mcp-server 4.0.0 โ 4.2.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/README.md +233 -78
- package/dist/config.js +8 -0
- package/dist/dashboard/ui.js +120 -0
- package/dist/lifecycle.js +164 -0
- package/dist/server.js +99 -49
- package/dist/storage/configStorage.js +44 -11
- package/dist/storage/supabase.js +8 -0
- package/dist/storage/supabaseMigrations.js +110 -0
- package/dist/tools/sessionMemoryHandlers.js +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
## Table of Contents
|
|
16
16
|
|
|
17
|
-
- [What's New (v4.
|
|
17
|
+
- [What's New (v4.2.0)](#whats-new-in-v420--project-repo-registry-)
|
|
18
|
+
- [Multi-Instance Support](#multi-instance-support)
|
|
18
19
|
- [How Prism Compares](#how-prism-compares)
|
|
19
20
|
- [Quick Start](#quick-start-zero-config--local-mode)
|
|
20
21
|
- [Mind Palace Dashboard](#-the-mind-palace-dashboard)
|
|
@@ -41,26 +42,48 @@
|
|
|
41
42
|
|
|
42
43
|
---
|
|
43
44
|
|
|
44
|
-
## What's New in v4.
|
|
45
|
+
## What's New in v4.2.0 โ Project Repo Registry ๐๏ธ
|
|
45
46
|
|
|
46
47
|
| Feature | Description |
|
|
47
48
|
|---|---|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
| ๐๏ธ **Project Repo Paths** | Map each project to its repo directory in the dashboard. `session_save_ledger` validates `files_changed` paths and warns on mismatch โ prevents cross-project contamination. |
|
|
50
|
+
| ๐ **Universal Auto-Load** | Auto-load projects via dynamic tool descriptions โ works across all MCP clients (Claude, Cursor, Gemini, Antigravity) without lifecycle hooks. Dashboard is the sole source of truth. |
|
|
51
|
+
| ๐ **Dashboard-First Config** | Removed `PRISM_AUTOLOAD_PROJECTS` env var override. The Mind Palace dashboard is now the single source of truth for auto-load project configuration. |
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><strong>What's in v4.1.0 โ Auto-Migration & Multi-Instance ๐</strong></summary>
|
|
55
|
+
|
|
56
|
+
| Feature | Description |
|
|
57
|
+
|---|---|
|
|
58
|
+
| ๐ **Auto-Migrations (Supabase)** | Zero-config schema upgrades โ pending DDL migrations run automatically on server startup via `prism_apply_ddl` RPC. |
|
|
59
|
+
| ๐ **Multi-Instance Support** | `PRISM_INSTANCE` env var enables instance-aware PID locks โ run multiple Prism servers side-by-side without conflicts. |
|
|
60
|
+
| ๐ก๏ธ **Server Lifecycle Management** | Singleton PID lock with graceful shutdown and stale PID recovery. |
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
<details>
|
|
65
|
+
<summary><strong>What's in v4.0.0 โ Behavioral Memory ๐ง </strong></summary>
|
|
66
|
+
|
|
67
|
+
| Feature | Description |
|
|
68
|
+
|---|---|
|
|
69
|
+
| ๐ง **Behavioral Memory** | `session_save_experience` โ log actions, outcomes, corrections with confidence scores. Auto-injects warnings into context so agents learn from mistakes. |
|
|
70
|
+
| ๐ฏ **Dynamic Roles** | Role auto-resolves from dashboard settings. Set once in Mind Palace, applies everywhere. |
|
|
71
|
+
| ๐ **Token Budget** | `max_tokens` on `session_load_context` โ intelligently truncates to fit your budget. |
|
|
72
|
+
| ๐ **Importance Decay** | Stale corrections auto-fade over time to keep context fresh. |
|
|
73
|
+
| ๐ง **Claude Code Hooks** | Simplified SessionStart/Stop hooks that reliably trigger MCP tool calls. |
|
|
74
|
+
|
|
75
|
+
</details>
|
|
53
76
|
|
|
54
77
|
<details>
|
|
55
78
|
<summary><strong>What's in v3.1.0 โ Memory Lifecycle ๐</strong></summary>
|
|
56
79
|
|
|
57
80
|
| Feature | Description |
|
|
58
81
|
|---|---|
|
|
59
|
-
| ๐ **Memory Analytics** |
|
|
60
|
-
| โณ **
|
|
61
|
-
| ๐๏ธ **
|
|
62
|
-
| ๐ฆ **PKM Export
|
|
63
|
-
| ๐งช **
|
|
82
|
+
| ๐ **Memory Analytics** | Dashboard sparkline chart, session counts, rollup savings, context richness metrics. |
|
|
83
|
+
| โณ **Data Retention (TTL)** | Per-project TTL via `knowledge_set_retention` or dashboard. Auto-expires old entries every 12h. |
|
|
84
|
+
| ๐๏ธ **Auto-Compaction** | Background health check after saves โ auto-compacts when brain is degraded. |
|
|
85
|
+
| ๐ฆ **PKM Export** | Export project memory as ZIP of Markdown files for Obsidian/Logseq. |
|
|
86
|
+
| ๐งช **95 Tests** | Analytics, TTL, rollup, compaction, type guards, and export coverage. |
|
|
64
87
|
|
|
65
88
|
</details>
|
|
66
89
|
|
|
@@ -69,10 +92,10 @@
|
|
|
69
92
|
|
|
70
93
|
| Feature | Description |
|
|
71
94
|
|---|---|
|
|
72
|
-
| ๐งน **Brain Health Clean-up** |
|
|
73
|
-
| ๐ค **Agent Identity
|
|
74
|
-
| ๐ **Role-Scoped Skills** |
|
|
75
|
-
| ๐ค **Resource Formatting
|
|
95
|
+
| ๐งน **Brain Health Clean-up** | One-click **Fix Issues** button โ detects and cleans orphaned handoffs, missing embeddings, stale rollups. |
|
|
96
|
+
| ๐ค **Agent Identity** | Set Default Role and Agent Name in dashboard โ auto-applies as fallback in all tools. |
|
|
97
|
+
| ๐ **Role-Scoped Skills** | Per-role persistent rules documents, auto-injected at `session_load_context`. |
|
|
98
|
+
| ๐ค **Resource Formatting** | `memory://` resources render as formatted text instead of raw JSON. |
|
|
76
99
|
|
|
77
100
|
</details>
|
|
78
101
|
|
|
@@ -81,13 +104,13 @@
|
|
|
81
104
|
|
|
82
105
|
| Feature | Description |
|
|
83
106
|
|---|---|
|
|
84
|
-
| ๐ **Role-Scoped Memory** | Optional `role`
|
|
85
|
-
| ๐ฅ **Agent Registry** |
|
|
86
|
-
| ๐ฏ **Team Roster
|
|
87
|
-
| โ๏ธ **Dashboard Settings** |
|
|
88
|
-
| ๐ก **Hivemind Radar** |
|
|
89
|
-
| ๐ **Conditional
|
|
90
|
-
| โ
**
|
|
107
|
+
| ๐ **Role-Scoped Memory** | Optional `role` param โ each role gets isolated memory within a project. |
|
|
108
|
+
| ๐ฅ **Agent Registry** | `agent_register`, `agent_heartbeat`, `agent_list_team` โ multi-agent discovery. |
|
|
109
|
+
| ๐ฏ **Team Roster** | Auto-injected teammate awareness during context loading. |
|
|
110
|
+
| โ๏ธ **Dashboard Settings** | Runtime toggles backed by persistent key-value store. |
|
|
111
|
+
| ๐ก **Hivemind Radar** | Dashboard widget showing active agents, roles, and heartbeats. |
|
|
112
|
+
| ๐ **Conditional Tools** | `PRISM_ENABLE_HIVEMIND` gates multi-agent tools. |
|
|
113
|
+
| โ
**58 Tests** | Storage, tools, dashboard, concurrent writes, role isolation. |
|
|
91
114
|
|
|
92
115
|
</details>
|
|
93
116
|
|
|
@@ -97,10 +120,10 @@
|
|
|
97
120
|
|
|
98
121
|
| Feature | Description |
|
|
99
122
|
|---|---|
|
|
100
|
-
| ๐ **Memory Tracing
|
|
101
|
-
| ๐ก๏ธ **GDPR
|
|
102
|
-
| ๐ **LangChain Integration
|
|
103
|
-
| ๐งฉ **LangGraph
|
|
123
|
+
| ๐ **Memory Tracing** | `MemoryTrace` with latency breakdown and scoring metadata for LangSmith. |
|
|
124
|
+
| ๐ก๏ธ **GDPR Deletion** | `session_forget_memory` with soft/hard delete and Article 17 justification. |
|
|
125
|
+
| ๐ **LangChain Integration** | `PrismMemoryRetriever` / `PrismKnowledgeRetriever` BaseRetriever adapters. |
|
|
126
|
+
| ๐งฉ **LangGraph Agent** | 5-node research agent example with MCP bridge and hybrid search. |
|
|
104
127
|
|
|
105
128
|
</details>
|
|
106
129
|
|
|
@@ -109,8 +132,8 @@
|
|
|
109
132
|
|
|
110
133
|
| Feature | Description |
|
|
111
134
|
|---|---|
|
|
112
|
-
| ๐ **Dynamic Versioning** |
|
|
113
|
-
| ๐ก๏ธ **Embedding
|
|
135
|
+
| ๐ **Dynamic Versioning** | Version derived from `package.json` โ MCP handshake, dashboard, and npm stay in sync. |
|
|
136
|
+
| ๐ก๏ธ **Embedding Validation** | Validates 768-dimension vectors at runtime to catch model regressions. |
|
|
114
137
|
|
|
115
138
|
</details>
|
|
116
139
|
|
|
@@ -130,11 +153,11 @@
|
|
|
130
153
|
|
|
131
154
|
| Feature | Description |
|
|
132
155
|
|---|---|
|
|
133
|
-
| ๐ค **LangGraph
|
|
134
|
-
| ๐ง **Agentic Memory** | `save_session` node persists
|
|
135
|
-
| ๐ **MCP Client Bridge** |
|
|
136
|
-
| ๐ง **Storage
|
|
137
|
-
| ๐ก๏ธ **Error Boundaries** |
|
|
156
|
+
| ๐ค **LangGraph Agent** | 5-node research agent with autonomous looping, MCP integration, persistent memory. |
|
|
157
|
+
| ๐ง **Agentic Memory** | `save_session` node persists findings to ledger โ agents don't just answer and forget. |
|
|
158
|
+
| ๐ **MCP Client Bridge** | JSON-RPC 2.0 client wraps Prism tools as LangChain `StructuredTool` objects. |
|
|
159
|
+
| ๐ง **Storage Fix** | Resource/Prompt handlers route through `getStorage()` โ eliminates EOF crashes. |
|
|
160
|
+
| ๐ก๏ธ **Error Boundaries** | Graceful error handling with proper MCP error responses. |
|
|
138
161
|
|
|
139
162
|
</details>
|
|
140
163
|
|
|
@@ -172,25 +195,29 @@
|
|
|
172
195
|
|
|
173
196
|
| Feature | **Prism MCP** | [MCP Memory](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) | [Mem0](https://github.com/mem0ai/mem0) | [Mnemory](https://github.com/fpytloun/mnemory) | [Basic Memory](https://github.com/basicmachines-co/basic-memory) |
|
|
174
197
|
|---|---|---|---|---|---|
|
|
175
|
-
| **Pricing** | โ
Free
|
|
176
|
-
| **Storage** | SQLite
|
|
177
|
-
| **Zero Config** | โ
|
|
178
|
-
| **
|
|
179
|
-
| **
|
|
180
|
-
| **
|
|
181
|
-
| **
|
|
182
|
-
| **
|
|
183
|
-
| **
|
|
184
|
-
| **
|
|
185
|
-
| **
|
|
186
|
-
| **
|
|
198
|
+
| **Pricing** | โ
Free / MIT | โ
Free / MIT | Freemium | โ
Free / OSS | Freemium |
|
|
199
|
+
| **Storage** | SQLite + Supabase | JSON file | Postgres + Qdrant | Qdrant + S3 | Markdown files |
|
|
200
|
+
| **Zero Config** | โ
npx one-liner | โ
| โ Qdrant/Postgres | โ
uvx | โ
pip |
|
|
201
|
+
| **Behavioral Memory** | โ
Importance tracking | โ | โ | โ | โ |
|
|
202
|
+
| **Dynamic Roles** | โ
Dashboard auto-resolve | โ | โ | โ | โ |
|
|
203
|
+
| **Token Budget** | โ
max_tokens param | โ | โ | โ | โ |
|
|
204
|
+
| **Importance Decay** | โ
Auto-fade stale data | โ | โ | โ | โ |
|
|
205
|
+
| **Semantic Search** | โ
Vectors + FTS5 | โ | โ
pgvector | โ
Qdrant | โ Text only |
|
|
206
|
+
| **Knowledge Graph** | โ
Neural Graph | โ
Entity model | โ | โ
Graph | โ
MD links |
|
|
207
|
+
| **Time Travel** | โ
History + checkout | โ | โ | โ | โ |
|
|
208
|
+
| **Fact Merging** | โ
Gemini async | โ | โ
Built-in | โ
Contradiction | โ |
|
|
209
|
+
| **Security Scan** | โ
Injection detection | โ | โ | โ
Anti-injection | โ |
|
|
210
|
+
| **Health Check** | โ
fsck tool | โ | โ | โ
3-phase fsck | โ |
|
|
211
|
+
| **Visual Dashboard** | โ
Mind Palace | โ | โ
Cloud UI | โ
Mgmt UI | โ |
|
|
212
|
+
| **Multi-Agent Sync** | โ
Real-time | โ | โ | โ Per-user | โ |
|
|
213
|
+
| **Visual Memory** | โ
Screenshot vault | โ | โ | โ
Artifacts | โ |
|
|
187
214
|
| **Auto-Compaction** | โ
Gemini rollups | โ | โ | โ | โ |
|
|
188
215
|
| **Morning Briefing** | โ
Gemini synthesis | โ | โ | โ | โ |
|
|
189
216
|
| **OCC (Concurrency)** | โ
Version-based | โ | โ | โ | โ |
|
|
190
|
-
| **GDPR Compliance** | โ
Soft/hard delete
|
|
191
|
-
| **Memory Tracing** | โ
|
|
192
|
-
| **LangChain
|
|
193
|
-
| **MCP Native** | โ
stdio
|
|
217
|
+
| **GDPR Compliance** | โ
Soft/hard delete | โ | โ | โ | โ |
|
|
218
|
+
| **Memory Tracing** | โ
Latency breakdown | โ | โ | โ | โ |
|
|
219
|
+
| **LangChain** | โ
BaseRetriever | โ | โ | โ | โ |
|
|
220
|
+
| **MCP Native** | โ
stdio | โ
stdio | โ Python SDK | โ
HTTP + MCP | โ
stdio |
|
|
194
221
|
| **Language** | TypeScript | TypeScript | Python | Python | Python |
|
|
195
222
|
|
|
196
223
|
> **When to choose Prism MCP:** You want MCP-native memory with zero infrastructure overhead, progressive context loading, and enterprise features (OCC, compaction, time travel, security scanning) that work directly in Claude Desktop โ without running Qdrant, Postgres, or cloud services.
|
|
@@ -280,6 +307,7 @@ Open **`http://localhost:3000`** in your browser to see exactly what your AI age
|
|
|
280
307
|
|
|
281
308
|
- **Current State & TODOs** โ See the exact context injected into the LLM's prompt
|
|
282
309
|
- **Agent Identity Chip** โ Header shows your active role + name (e.g. `๐ ๏ธ dev ยท Antigravity`); click to open Settings
|
|
310
|
+
- **Project Repo Paths** โ Map each project to its repo directory for save validation
|
|
283
311
|
- **Brain Health ๐ฉบ** โ Memory integrity status at a glance; **๐งน Fix Issues** button auto-cleans orphaned handoffs in one click
|
|
284
312
|
- **Git Drift Detection** โ Alerts you if you've modified code outside the agent's view
|
|
285
313
|
- **Morning Briefing** โ AI-synthesized action plan from your last sessions
|
|
@@ -621,6 +649,47 @@ The tool and dashboard button both call the same repair logic โ the dashboard
|
|
|
621
649
|
| `session_search_memory` | Semantic search with `enable_trace` | `query`, `enable_trace` | Results + `MemoryTrace` in `content[1]` |
|
|
622
650
|
| `knowledge_search` | Knowledge search with `enable_trace` | `query`, `enable_trace` | Results + `MemoryTrace` in `content[1]` |
|
|
623
651
|
|
|
652
|
+
### v4.0 Behavioral Memory Tools
|
|
653
|
+
|
|
654
|
+
| Tool | Purpose | Key Args |
|
|
655
|
+
|------|---------|----------|
|
|
656
|
+
| `session_save_experience` | Record behavioral events with importance tracking | `project`, `event_type`, `context`, `action`, `outcome` |
|
|
657
|
+
|
|
658
|
+
**Dynamic Roles (v4.0):** `role` is now *optional* on all tools. Set your **Default Role** once in the dashboard (โ๏ธ Settings โ Agent Identity) and it auto-applies everywhere โ no need to pass it per call.
|
|
659
|
+
|
|
660
|
+
**Token Budget (v4.0):** Set a default in the dashboard (โ๏ธ Settings โ Token Budget) or pass `max_tokens` per call to override:
|
|
661
|
+
|
|
662
|
+
```json
|
|
663
|
+
{ "name": "session_load_context", "arguments": {
|
|
664
|
+
"project": "my-app", "level": "standard", "max_tokens": 2000
|
|
665
|
+
}}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
> ๐ก Set Token Budget to `0` in the dashboard for unlimited (default). Per-call `max_tokens` always takes priority.
|
|
669
|
+
|
|
670
|
+
**Recording experiences:**
|
|
671
|
+
|
|
672
|
+
```json
|
|
673
|
+
{ "name": "session_save_experience", "arguments": {
|
|
674
|
+
"project": "my-app",
|
|
675
|
+
"event_type": "correction",
|
|
676
|
+
"context": "User asked to add a database migration",
|
|
677
|
+
"action": "Ran ALTER TABLE directly in production",
|
|
678
|
+
"outcome": "Data was corrupted",
|
|
679
|
+
"correction": "Always create a migration file and test in staging first",
|
|
680
|
+
"confidence_score": 95
|
|
681
|
+
}}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**Event types:** `correction` (user corrected the agent), `success` (task went well), `failure` (task failed), `learning` (new knowledge acquired).
|
|
685
|
+
|
|
686
|
+
**How behavioral memory works:**
|
|
687
|
+
1. Agent records experiences via `session_save_experience`
|
|
688
|
+
2. Prism assigns an **importance score** based on event type + confidence
|
|
689
|
+
3. Stale entries **auto-decay** in importance over time
|
|
690
|
+
4. On `session_load_context`, high-importance corrections auto-surface as `[โ ๏ธ BEHAVIORAL WARNINGS]`
|
|
691
|
+
5. Agent sees warnings and avoids repeating past mistakes
|
|
692
|
+
|
|
624
693
|
### Code Mode Templates (v2.1)
|
|
625
694
|
|
|
626
695
|
Instead of writing custom JavaScript, pass a `template` name for instant extraction:
|
|
@@ -798,6 +867,7 @@ The retrievers use `_aget_relevant_documents` as the primary path with `asyncio.
|
|
|
798
867
|
| `BRAVE_API_KEY` | No | Brave Search Pro API key (enables web/local search tools) |
|
|
799
868
|
| `PRISM_STORAGE` | No | `"local"` (default) or `"supabase"` โ **requires restart** |
|
|
800
869
|
| `PRISM_ENABLE_HIVEMIND` | No | Set `"true"` to enable multi-agent Hivemind tools โ **requires restart** |
|
|
870
|
+
| `PRISM_INSTANCE` | No | Instance name for PID lock isolation (e.g. `"athena"`, `"prism-dev"`). Enables [multi-instance support](#multi-instance-support). Default: `"default"` |
|
|
801
871
|
| `GOOGLE_API_KEY` | No | Google AI / Gemini โ enables paper analysis, Morning Briefings, compaction |
|
|
802
872
|
| `BRAVE_ANSWERS_API_KEY` | No | Separate Brave Answers key for AI-grounded answers |
|
|
803
873
|
| `SUPABASE_URL` | If cloud mode | Supabase project URL |
|
|
@@ -864,12 +934,27 @@ The agent boots up knowing exactly what to do โ zero prompting needed.
|
|
|
864
934
|
|
|
865
935
|
### Auto-Load on Session Start (Recommended)
|
|
866
936
|
|
|
867
|
-
For the best experience,
|
|
937
|
+
For the best experience, ensure your agent boots with full project memory on every new session. There are three approaches โ use whichever fits your workflow:
|
|
938
|
+
|
|
939
|
+
#### Option A: Dashboard Setting (Easiest)
|
|
940
|
+
|
|
941
|
+
Open the **Mind Palace Dashboard** (โ๏ธ Settings) and type your project names into the **Auto-Load Projects** field under Boot Settings:
|
|
942
|
+
|
|
943
|
+
```
|
|
944
|
+
prism-mcp, my-app
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
That's it. Restart your AI client and Prism auto-pushes context for each listed project on next boot.
|
|
948
|
+
|
|
949
|
+
#### Option B: Client-Side Hooks / Rules
|
|
950
|
+
|
|
951
|
+
For clients that support lifecycle hooks or startup rules, instruct the AI to **call `session_load_context` as a tool** at session start:
|
|
868
952
|
|
|
869
|
-
See the full setup guides for each client:
|
|
870
953
|
- **[Claude Code Integration (Hooks)](#claude-code-integration-hooks)** โ `SessionStart` and `Stop` hook JSON samples for `~/.claude/settings.json`
|
|
871
954
|
- **[Gemini / Antigravity Integration](#gemini--antigravity-integration)** โ global rules for `~/.gemini/GEMINI.md` or user rules
|
|
872
955
|
|
|
956
|
+
> **You can use both approaches together.** The dashboard auto-load (A) injects project names into the `session_load_context` tool description โ works universally across all MCP clients. Client-side hooks (B) give the AI full structured access to the context response (including version numbers for OCC).
|
|
957
|
+
|
|
873
958
|
> **Key principle:** Never hardcode a `role` in your hooks or rules. Set your role once in the **Mind Palace Dashboard โ๏ธ Settings โ Agent Identity**, and Prism automatically resolves it for every tool call across all clients. See [Role Resolution](#role-resolution--no-hardcoding-needed).
|
|
874
959
|
|
|
875
960
|
> **Tip:** Replace `my-project` with your actual project identifiers. You can list as many projects as you need โ each one gets its own independent memory timeline.
|
|
@@ -1083,9 +1168,12 @@ Traces are returned as `content[1]` in MCP responses โ a separate content bloc
|
|
|
1083
1168
|
|
|
1084
1169
|
### 2. Apply Migrations
|
|
1085
1170
|
|
|
1086
|
-
In the SQL Editor, run:
|
|
1171
|
+
In the SQL Editor, run the **bootstrap migration** first:
|
|
1087
1172
|
1. [`supabase/migrations/015_session_memory.sql`](supabase/migrations/015_session_memory.sql)
|
|
1088
1173
|
2. [`supabase/migrations/016_knowledge_accumulation.sql`](supabase/migrations/016_knowledge_accumulation.sql)
|
|
1174
|
+
3. [`supabase/migrations/027_auto_migration_infra.sql`](supabase/migrations/027_auto_migration_infra.sql) โ **enables auto-migrations** (see below)
|
|
1175
|
+
|
|
1176
|
+
> **After applying migration 027**, all future schema changes are applied automatically on server startup โ no manual SQL required.
|
|
1089
1177
|
|
|
1090
1178
|
### 3. Get Credentials
|
|
1091
1179
|
|
|
@@ -1113,6 +1201,70 @@ export PRISM_STORAGE="supabase"
|
|
|
1113
1201
|
|
|
1114
1202
|
</details>
|
|
1115
1203
|
|
|
1204
|
+
### Auto-Migrations (Supabase Cloud)
|
|
1205
|
+
|
|
1206
|
+
Prism includes a zero-config auto-migration system for Supabase. Once the bootstrap migration (`027_auto_migration_infra.sql`) is applied, all future schema changes are applied automatically on server startup.
|
|
1207
|
+
|
|
1208
|
+
**How it works:**
|
|
1209
|
+
|
|
1210
|
+
1. On startup, the migration runner checks each entry in `MIGRATIONS[]` (defined in `supabaseMigrations.ts`)
|
|
1211
|
+
2. For each migration, it calls `prism_apply_ddl(version, name, sql)` โ a `SECURITY DEFINER` RPC function
|
|
1212
|
+
3. The function checks `prism_schema_versions` โ if the version is already recorded, it's silently skipped (idempotent)
|
|
1213
|
+
4. If not applied, it executes the DDL and records the version number
|
|
1214
|
+
|
|
1215
|
+
**Graceful degradation:** If `prism_apply_ddl()` doesn't exist (you haven't applied migration 027 yet), the runner logs a warning and continues โ the server still starts, but newer schema features may not be available.
|
|
1216
|
+
|
|
1217
|
+
**Adding new migrations** โ just append to the `MIGRATIONS[]` array in `src/storage/supabaseMigrations.ts`:
|
|
1218
|
+
|
|
1219
|
+
```typescript
|
|
1220
|
+
{
|
|
1221
|
+
version: 28,
|
|
1222
|
+
name: "my_new_feature",
|
|
1223
|
+
sql: `ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS my_col TEXT;`,
|
|
1224
|
+
}
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
All SQL must be idempotent (`IF NOT EXISTS` / `IF EXISTS` guards).
|
|
1228
|
+
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
## Multi-Instance Support
|
|
1232
|
+
|
|
1233
|
+
Run multiple Prism MCP servers side-by-side on the same machine without PID lock conflicts. This is useful when you have different MCP configurations (e.g., one for web search + memory, another for memory-only) running in different clients simultaneously.
|
|
1234
|
+
|
|
1235
|
+
### Configuration
|
|
1236
|
+
|
|
1237
|
+
Set `PRISM_INSTANCE` to a unique name per server instance:
|
|
1238
|
+
|
|
1239
|
+
```json
|
|
1240
|
+
{
|
|
1241
|
+
"mcpServers": {
|
|
1242
|
+
"prism-search": {
|
|
1243
|
+
"command": "node",
|
|
1244
|
+
"args": ["/path/to/prism/dist/server.js"],
|
|
1245
|
+
"env": {
|
|
1246
|
+
"PRISM_INSTANCE": "prism-search",
|
|
1247
|
+
"BRAVE_API_KEY": "your-key"
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
"prism-memory": {
|
|
1251
|
+
"command": "node",
|
|
1252
|
+
"args": ["/path/to/prism/dist/server.js"],
|
|
1253
|
+
"env": {
|
|
1254
|
+
"PRISM_INSTANCE": "prism-memory"
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
### How it works
|
|
1262
|
+
|
|
1263
|
+
- Each instance gets its own PID file: `/tmp/prism-{PRISM_INSTANCE}.pid`
|
|
1264
|
+
- Default instance name is `"default"` (backward compatible)
|
|
1265
|
+
- Instances share the same SQLite database and Supabase backend โ only the process lock is isolated
|
|
1266
|
+
- Graceful shutdown cleans up the instance's PID file
|
|
1267
|
+
|
|
1116
1268
|
---
|
|
1117
1269
|
|
|
1118
1270
|
## Hybrid Search Pipeline (Brave + Vertex AI)
|
|
@@ -1139,12 +1291,13 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
1139
1291
|
|
|
1140
1292
|
```
|
|
1141
1293
|
โโโ src/
|
|
1142
|
-
โ โโโ server.ts # MCP server core + tool routing
|
|
1294
|
+
โ โโโ server.ts # MCP server core + tool routing + lifecycle
|
|
1143
1295
|
โ โโโ config.ts # Environment management
|
|
1144
1296
|
โ โโโ storage/
|
|
1145
1297
|
โ โ โโโ interface.ts # StorageBackend abstraction (+ GDPR delete methods)
|
|
1146
1298
|
โ โ โโโ sqlite.ts # SQLite local storage (libSQL + F32_BLOB + deleted_at migration)
|
|
1147
1299
|
โ โ โโโ supabase.ts # Supabase cloud storage (+ soft/hard delete)
|
|
1300
|
+
โ โ โโโ supabaseMigrations.ts # Auto-migration runner for Supabase DDL
|
|
1148
1301
|
โ โ โโโ configStorage.ts # Boot config micro-DB (~/.prism-mcp/prism-config.db)
|
|
1149
1302
|
โ โ โโโ index.ts # Backend factory (auto-selects based on PRISM_STORAGE)
|
|
1150
1303
|
โ โโโ sync/
|
|
@@ -1182,7 +1335,8 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
1182
1335
|
โ โโโ prism_retriever.py # PrismMemoryRetriever + PrismKnowledgeRetriever
|
|
1183
1336
|
โ โโโ tools.py # Agent tools + GDPR forget_memory
|
|
1184
1337
|
โ โโโ demo_retriever.py # Standalone retriever demo
|
|
1185
|
-
โโโ supabase/migrations/ # Cloud mode SQL schemas
|
|
1338
|
+
โโโ supabase/migrations/ # Cloud mode SQL schemas + auto-migration bootstrap
|
|
1339
|
+
โ โโโ 027_auto_migration_infra.sql # prism_apply_ddl() RPC + schema version tracking
|
|
1186
1340
|
โโโ vertex-ai/ # Vertex AI hybrid search pipeline
|
|
1187
1341
|
โโโ index.ts # Server entry point
|
|
1188
1342
|
โโโ package.json
|
|
@@ -1194,40 +1348,41 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
1194
1348
|
|
|
1195
1349
|
> **[View the full project board โ](https://github.com/users/dcostenco/projects/1/views/1)**
|
|
1196
1350
|
|
|
1197
|
-
### โ
|
|
1351
|
+
### โ
v4.2 โ Project Repo Registry (Shipped!)
|
|
1198
1352
|
|
|
1199
|
-
|
|
1353
|
+
| Feature | Description |
|
|
1354
|
+
|---|---|
|
|
1355
|
+
| ๐๏ธ **Project Repo Paths** | Dashboard UI to map projects to repo directories + `session_save_ledger` path validation. |
|
|
1356
|
+
| ๐ **Universal Auto-Load** | Dynamic tool descriptions replace env var โ dashboard is sole source of truth. |
|
|
1357
|
+
| ๐ **Dashboard-First Config** | Removed `PRISM_AUTOLOAD_PROJECTS` env var override. |
|
|
1200
1358
|
|
|
1201
|
-
### โ
|
|
1359
|
+
### โ
v4.1 โ Auto-Migration & Multi-Instance (Shipped!)
|
|
1202
1360
|
|
|
1203
|
-
See [What's
|
|
1361
|
+
See [What's in v4.1.0](#whats-in-v410--auto-migration--multi-instance-) above.
|
|
1204
1362
|
|
|
1205
|
-
###
|
|
1363
|
+
### โ
v4.0 โ Behavioral Memory (Shipped!)
|
|
1206
1364
|
|
|
1207
|
-
|
|
1365
|
+
See [What's in v4.0.0](#whats-in-v400--behavioral-memory-) above.
|
|
1208
1366
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1367
|
+
### โ
v3.1 โ Memory Lifecycle (Shipped!)
|
|
1368
|
+
|
|
1369
|
+
See [What's in v3.1.0](#whats-in-v310--memory-lifecycle-) above.
|
|
1370
|
+
|
|
1371
|
+
### โ
v3.0 โ Agent Hivemind (Shipped!)
|
|
1372
|
+
|
|
1373
|
+
See [What's in v3.0.0](#whats-in-v300--agent-hivemind-) above.
|
|
1216
1374
|
|
|
1217
1375
|
### ๐ Future Ideas
|
|
1218
1376
|
|
|
1219
1377
|
| Feature | Issue | Description |
|
|
1220
1378
|
|---------|-------|-------------|
|
|
1221
|
-
|
|
|
1222
|
-
| OpenTelemetry
|
|
1223
|
-
| GDPR
|
|
1224
|
-
|
|
|
1225
|
-
|
|
|
1226
|
-
|
|
|
1227
|
-
|
|
|
1228
|
-
| Automated Data Retention (TTL) | [#16](https://github.com/dcostenco/prism-mcp/issues/16) | Time-based memory expiration policies |
|
|
1229
|
-
| Obsidian / Logseq Export Bridge | [#17](https://github.com/dcostenco/prism-mcp/issues/17) | Export memory to Markdown knowledge bases |
|
|
1230
|
-
| Interactive Knowledge Graph Editor | [#19](https://github.com/dcostenco/prism-mcp/issues/19) | Visual graph editor inside the Mind Palace dashboard |
|
|
1379
|
+
| Insight Graduation | โ | `knowledge_upvote` / `knowledge_downvote` โ importance โฅ 7 promotes to rule |
|
|
1380
|
+
| OpenTelemetry | [#6](https://github.com/dcostenco/prism-mcp/issues/6) | W3C tracing with Jaeger/Zipkin export |
|
|
1381
|
+
| GDPR Portability | [#7](https://github.com/dcostenco/prism-mcp/issues/7) | `session_export_memory` for Art. 20 |
|
|
1382
|
+
| CRDT Conflict Resolution | [#9](https://github.com/dcostenco/prism-mcp/issues/9) | Conflict-free types for concurrent edits |
|
|
1383
|
+
| Pluggable LLM Providers | [#13](https://github.com/dcostenco/prism-mcp/issues/13) | Anthropic, OpenAI, Ollama adapters |
|
|
1384
|
+
| VLM / OCR for Images | [#14](https://github.com/dcostenco/prism-mcp/issues/14) | Auto-extract text from stored images |
|
|
1385
|
+
| Knowledge Graph Editor | [#19](https://github.com/dcostenco/prism-mcp/issues/19) | Visual graph editor in Mind Palace |
|
|
1231
1386
|
|
|
1232
1387
|
### ๐งฐ Infrastructure & Stack
|
|
1233
1388
|
|
package/dist/config.js
CHANGED
|
@@ -114,6 +114,14 @@ export const PRISM_DEBUG_LOGGING = process.env.PRISM_DEBUG_LOGGING === "true";
|
|
|
114
114
|
// doesn't increase tool count.
|
|
115
115
|
// Set PRISM_ENABLE_HIVEMIND=true to unlock the Agent Registry tools.
|
|
116
116
|
export const PRISM_ENABLE_HIVEMIND = process.env.PRISM_ENABLE_HIVEMIND === "true";
|
|
117
|
+
// โโโ v4.1: Auto-Load Projects โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
118
|
+
// Auto-load is configured exclusively via the Mind Palace dashboard
|
|
119
|
+
// ("Auto-Load Projects" checkboxes in Settings). The setting is stored
|
|
120
|
+
// in prism-config.db and read at startup via getSettingSync().
|
|
121
|
+
//
|
|
122
|
+
// The PRISM_AUTOLOAD_PROJECTS env var has been removed โ the dashboard
|
|
123
|
+
// is the single source of truth. This prevents mismatches between
|
|
124
|
+
// env var and dashboard values causing duplicate project loads.
|
|
117
125
|
if (PRISM_AUTO_CAPTURE) {
|
|
118
126
|
// Use console.error instead of debugLog here to prevent circular dependency
|
|
119
127
|
if (PRISM_DEBUG_LOGGING) {
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -696,6 +696,19 @@ export function renderDashboardHTML(version) {
|
|
|
696
696
|
</select>
|
|
697
697
|
</div>
|
|
698
698
|
|
|
699
|
+
<div class="setting-row">
|
|
700
|
+
<div>
|
|
701
|
+
<div class="setting-label">Token Budget</div>
|
|
702
|
+
<div class="setting-desc">Max tokens for session_load_context (0 = unlimited)</div>
|
|
703
|
+
</div>
|
|
704
|
+
<input type="number" id="input-max-tokens"
|
|
705
|
+
placeholder="0"
|
|
706
|
+
min="0" max="100000" step="500"
|
|
707
|
+
style="padding: 0.2rem 0.5rem; background: var(--bg-hover); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; font-size: 0.85rem; font-family: var(--font-mono); width: 90px; text-align: right;"
|
|
708
|
+
onchange="saveSetting('max_tokens', this.value)"
|
|
709
|
+
oninput="clearTimeout(this._t); this._t=setTimeout(()=>saveSetting('max_tokens',this.value),800)" />
|
|
710
|
+
</div>
|
|
711
|
+
|
|
699
712
|
<div class="setting-section">Boot Settings <span class="boot-badge">Restart Required</span></div>
|
|
700
713
|
|
|
701
714
|
<div class="setting-row">
|
|
@@ -716,6 +729,26 @@ export function renderDashboardHTML(version) {
|
|
|
716
729
|
</select>
|
|
717
730
|
</div>
|
|
718
731
|
|
|
732
|
+
<div class="setting-row" style="align-items:flex-start">
|
|
733
|
+
<div>
|
|
734
|
+
<div class="setting-label">Auto-Load Projects</div>
|
|
735
|
+
<div class="setting-desc">Select projects to auto-push context on startup</div>
|
|
736
|
+
</div>
|
|
737
|
+
<div id="autoload-checkboxes" style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem;font-family:var(--font-mono);max-height:120px;overflow-y:auto;">
|
|
738
|
+
<span style="color:var(--text-muted);font-size:0.8rem">Loadingโฆ</span>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
<div class="setting-row" style="align-items:flex-start">
|
|
743
|
+
<div>
|
|
744
|
+
<div class="setting-label">Project Repo Paths</div>
|
|
745
|
+
<div class="setting-desc">Map each project to its repo directory for save validation</div>
|
|
746
|
+
</div>
|
|
747
|
+
<div id="repopath-inputs" style="display:flex;flex-direction:column;gap:6px;font-size:0.85rem;max-height:160px;overflow-y:auto;">
|
|
748
|
+
<span style="color:var(--text-muted);font-size:0.8rem">Loadingโฆ</span>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
719
752
|
<div class="setting-section">Agent Identity</div>
|
|
720
753
|
|
|
721
754
|
<div class="setting-row">
|
|
@@ -1278,6 +1311,88 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
|
|
|
1278
1311
|
}
|
|
1279
1312
|
|
|
1280
1313
|
|
|
1314
|
+
// โโโ Auto-Load Checkboxes (v4.1) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1315
|
+
async function loadAutoloadCheckboxes() {
|
|
1316
|
+
var container = document.getElementById('autoload-checkboxes');
|
|
1317
|
+
if (!container) return;
|
|
1318
|
+
try {
|
|
1319
|
+
var projRes = await fetch('/api/projects');
|
|
1320
|
+
var projData = await projRes.json();
|
|
1321
|
+
var projects = projData.projects || [];
|
|
1322
|
+
|
|
1323
|
+
var settRes = await fetch('/api/settings');
|
|
1324
|
+
var settData = await settRes.json();
|
|
1325
|
+
var saved = (settData.settings || {}).autoload_projects || '';
|
|
1326
|
+
var selected = saved.split(',').map(function(s){ return s.trim(); }).filter(Boolean);
|
|
1327
|
+
|
|
1328
|
+
if (projects.length === 0) {
|
|
1329
|
+
container.innerHTML = '<span style="color:var(--text-muted);font-size:0.8rem">No projects found</span>';
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
container.innerHTML = projects.map(function(p) {
|
|
1334
|
+
var checked = selected.indexOf(p) !== -1 ? ' checked' : '';
|
|
1335
|
+
return '<label style="display:flex;align-items:center;gap:6px;cursor:pointer;color:var(--text-primary)">' +
|
|
1336
|
+
'<input type="checkbox" value="' + escapeHtml(p) + '"' + checked +
|
|
1337
|
+
' onchange="onAutoloadToggle()"' +
|
|
1338
|
+
' style="accent-color:var(--accent-purple);cursor:pointer" />' +
|
|
1339
|
+
escapeHtml(p) + '</label>';
|
|
1340
|
+
}).join('');
|
|
1341
|
+
} catch(e) {
|
|
1342
|
+
container.innerHTML = '<span style="color:var(--accent-rose);font-size:0.8rem">Failed to load</span>';
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function onAutoloadToggle() {
|
|
1347
|
+
var container = document.getElementById('autoload-checkboxes');
|
|
1348
|
+
if (!container) return;
|
|
1349
|
+
var boxes = container.querySelectorAll('input[type=checkbox]');
|
|
1350
|
+
var selected = [];
|
|
1351
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
1352
|
+
if (boxes[i].checked) selected.push(boxes[i].value);
|
|
1353
|
+
}
|
|
1354
|
+
saveBootSetting('autoload_projects', selected.join(','));
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// โโโ Project Repo Paths (v4.2) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1358
|
+
async function loadRepoPathInputs() {
|
|
1359
|
+
var container = document.getElementById('repopath-inputs');
|
|
1360
|
+
if (!container) return;
|
|
1361
|
+
try {
|
|
1362
|
+
var projRes = await fetch('/api/projects');
|
|
1363
|
+
var projData = await projRes.json();
|
|
1364
|
+
var projects = projData.projects || [];
|
|
1365
|
+
|
|
1366
|
+
var settRes = await fetch('/api/settings');
|
|
1367
|
+
var settData = await settRes.json();
|
|
1368
|
+
var settings = settData.settings || {};
|
|
1369
|
+
|
|
1370
|
+
if (projects.length === 0) {
|
|
1371
|
+
container.innerHTML = '<span style="color:var(--text-muted);font-size:0.8rem">No projects found</span>';
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
container.innerHTML = projects.map(function(p) {
|
|
1376
|
+
var savedPath = settings['repo_path:' + p] || '';
|
|
1377
|
+
return '<div style="display:flex;align-items:center;gap:6px">' +
|
|
1378
|
+
'<span style="min-width:100px;color:var(--text-secondary);font-family:var(--font-mono);font-size:0.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeHtml(p) + '">' + escapeHtml(p) + '</span>' +
|
|
1379
|
+
'<input type="text" value="' + escapeHtml(savedPath) + '"' +
|
|
1380
|
+
' placeholder="/path/to/repo"' +
|
|
1381
|
+
' data-project="' + escapeHtml(p) + '"' +
|
|
1382
|
+
' style="flex:1;min-width:140px;padding:0.2rem 0.4rem;background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border-glass);border-radius:4px;font-size:0.8rem;font-family:var(--font-mono)"' +
|
|
1383
|
+
' onchange="saveRepoPath(this.dataset.project, this.value)"' +
|
|
1384
|
+
' oninput="clearTimeout(this._t); var self=this; this._t=setTimeout(function(){saveRepoPath(self.dataset.project, self.value)},1200)" />' +
|
|
1385
|
+
'</div>';
|
|
1386
|
+
}).join('');
|
|
1387
|
+
} catch(e) {
|
|
1388
|
+
container.innerHTML = '<span style="color:var(--accent-rose);font-size:0.8rem">Failed to load</span>';
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
async function saveRepoPath(project, path) {
|
|
1393
|
+
await saveSetting('repo_path:' + project, path.trim());
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1281
1396
|
async function loadSettings() {
|
|
1282
1397
|
try {
|
|
1283
1398
|
var res = await fetch('/api/settings');
|
|
@@ -1304,6 +1419,11 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
|
|
|
1304
1419
|
// Agent Identity
|
|
1305
1420
|
if (s.default_role) document.getElementById('select-default-role').value = s.default_role;
|
|
1306
1421
|
if (s.agent_name) document.getElementById('input-agent-name').value = s.agent_name;
|
|
1422
|
+
if (s.max_tokens) document.getElementById('input-max-tokens').value = s.max_tokens;
|
|
1423
|
+
// Autoload checkboxes are loaded dynamically
|
|
1424
|
+
loadAutoloadCheckboxes();
|
|
1425
|
+
// Repo path inputs are loaded dynamically
|
|
1426
|
+
loadRepoPathInputs();
|
|
1307
1427
|
} catch(e) { console.warn('Settings load failed:', e); }
|
|
1308
1428
|
}
|
|
1309
1429
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Lifecycle Management
|
|
3
|
+
* Handles singleton PID locking, graceful shutdown, and SQLite handle cleanup.
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL: All logging MUST use console.error() (stderr).
|
|
6
|
+
* Using console.log() (stdout) will corrupt the MCP JSON-RPC stream.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
import { closeConfigStorage } from "./storage/configStorage.js";
|
|
13
|
+
import { getStorage } from "./storage/index.js";
|
|
14
|
+
const PRISM_DIR = path.join(os.homedir(), ".prism-mcp");
|
|
15
|
+
/**
|
|
16
|
+
* Instance-aware PID file.
|
|
17
|
+
* Set PRISM_INSTANCE env var to run multiple Prism MCP servers
|
|
18
|
+
* side-by-side (e.g. "athena-public" and "prism-mcp").
|
|
19
|
+
* Each instance gets its own PID file to prevent lock conflicts.
|
|
20
|
+
*/
|
|
21
|
+
const INSTANCE_NAME = process.env.PRISM_INSTANCE || "default";
|
|
22
|
+
const PID_FILE = path.join(PRISM_DIR, `server-${INSTANCE_NAME}.pid`);
|
|
23
|
+
function log(msg) {
|
|
24
|
+
console.error(`[Prism Lifecycle] ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks if a process is an orphan (adopted by init/launchd, PPID=1).
|
|
28
|
+
* Returns false on Windows (PID logic is different there).
|
|
29
|
+
*/
|
|
30
|
+
function isOrphanProcess(pid) {
|
|
31
|
+
if (process.platform === "win32") {
|
|
32
|
+
// Windows doesn't have reliable PPID checks via 'ps'.
|
|
33
|
+
// Safer to assume it's NOT an orphan to avoid killing valid instances.
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
// 'ps -o ppid= -p PID' returns just the parent PID
|
|
38
|
+
const ppid = execSync(`ps -o ppid= -p ${pid}`, { encoding: "utf8" }).trim();
|
|
39
|
+
return ppid === "1";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// If ps fails (e.g. process gone), assume it's safe to claim
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Ensures valid server execution state.
|
|
48
|
+
*
|
|
49
|
+
* LOGIC:
|
|
50
|
+
* 1. If --no-lock is passed, skip everything (testing mode).
|
|
51
|
+
* 2. If PID file exists:
|
|
52
|
+
* - If process is dead: Overwrite lock.
|
|
53
|
+
* - If process is alive AND is an orphan (PPID=1): Kill it (Zombie), then overwrite.
|
|
54
|
+
* - If process is alive AND has a parent: Log warning, allow coexistence (don't kill).
|
|
55
|
+
*/
|
|
56
|
+
export function acquireLock() {
|
|
57
|
+
if (process.argv.includes("--no-lock")) {
|
|
58
|
+
log("Lock acquisition skipped (--no-lock flag)");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!fs.existsSync(PRISM_DIR)) {
|
|
62
|
+
fs.mkdirSync(PRISM_DIR, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
if (fs.existsSync(PID_FILE)) {
|
|
65
|
+
try {
|
|
66
|
+
const oldPid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
67
|
+
if (oldPid && oldPid !== process.pid) {
|
|
68
|
+
let isAlive = false;
|
|
69
|
+
try {
|
|
70
|
+
process.kill(oldPid, 0); // 0 signal checks for existence
|
|
71
|
+
isAlive = true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
isAlive = false;
|
|
75
|
+
}
|
|
76
|
+
if (isAlive) {
|
|
77
|
+
// Process exists. Is it a zombie?
|
|
78
|
+
if (isOrphanProcess(oldPid)) {
|
|
79
|
+
log(`Found zombie process (PID ${oldPid}, PPID=1). Terminating...`);
|
|
80
|
+
try {
|
|
81
|
+
process.kill(oldPid, "SIGTERM");
|
|
82
|
+
// Give it 100ms to die, then force kill if needed
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
try {
|
|
85
|
+
process.kill(oldPid, "SIGKILL");
|
|
86
|
+
}
|
|
87
|
+
catch { }
|
|
88
|
+
}, 100);
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
log(`Failed to kill zombie: ${e}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// It has a parent (e.g., another VS Code window or Claude Desktop)
|
|
96
|
+
log(`Existing server (PID ${oldPid}) is active and managed. Coexisting...`);
|
|
97
|
+
// We do NOT overwrite the PID file here.
|
|
98
|
+
// If we overwrite it, the *active* server will fail to clean up
|
|
99
|
+
// the PID file when it eventually shuts down.
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
log(`Warning: Failed to process existing PID file: ${err}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Claim the lock for this process
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(PID_FILE, process.pid.toString(), "utf8");
|
|
112
|
+
log(`Acquired singleton lock (PID ${process.pid})`);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
log(`Warning: Failed to write PID file: ${err}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Registers handlers to close SQLite file handles cleanly when the server stops.
|
|
120
|
+
*/
|
|
121
|
+
export function registerShutdownHandlers() {
|
|
122
|
+
let shuttingDown = false;
|
|
123
|
+
const shutdown = async (reason) => {
|
|
124
|
+
if (shuttingDown)
|
|
125
|
+
return;
|
|
126
|
+
shuttingDown = true;
|
|
127
|
+
log(`Shutting down gracefully (${reason})...`);
|
|
128
|
+
try {
|
|
129
|
+
// 1. Close system settings DB
|
|
130
|
+
closeConfigStorage();
|
|
131
|
+
// 2. Close main ledger DB
|
|
132
|
+
const storage = await getStorage();
|
|
133
|
+
if (storage && typeof storage.close === "function") {
|
|
134
|
+
await storage.close();
|
|
135
|
+
}
|
|
136
|
+
// 3. Remove PID lockfile (only if WE own it)
|
|
137
|
+
if (fs.existsSync(PID_FILE)) {
|
|
138
|
+
try {
|
|
139
|
+
const storedPid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
140
|
+
if (storedPid === process.pid) {
|
|
141
|
+
fs.unlinkSync(PID_FILE);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Ignore read errors during shutdown
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
log(`Error during shutdown cleanup: ${err}`);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
// OS Signals
|
|
157
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
158
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
159
|
+
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
160
|
+
// MCP Client Disconnect (CRITICAL)
|
|
161
|
+
process.stdin.on("close", () => {
|
|
162
|
+
shutdown("CLIENT_DISCONNECTED_STDIN_CLOSED");
|
|
163
|
+
});
|
|
164
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -61,6 +61,7 @@ SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/
|
|
|
61
61
|
import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID, PRISM_ENABLE_HIVEMIND } from "./config.js";
|
|
62
62
|
import { getSyncBus } from "./sync/factory.js";
|
|
63
63
|
import { startDashboardServer } from "./dashboard/server.js";
|
|
64
|
+
import { acquireLock, registerShutdownHandlers } from "./lifecycle.js";
|
|
64
65
|
// โโโ v2.3.6 FIX: Use Storage Abstraction for Prompts/Resources โโโ
|
|
65
66
|
// CRITICAL FIX: Previously imported supabaseRpc/supabaseGet directly,
|
|
66
67
|
// which bypassed the storage abstraction layer and caused the server
|
|
@@ -114,43 +115,52 @@ const BASE_TOOLS = [
|
|
|
114
115
|
BRAVE_ANSWERS_TOOL, // brave_answers โ AI-grounded answers
|
|
115
116
|
RESEARCH_PAPER_ANALYSIS_TOOL, // gemini_research_paper_analysis โ paper analysis
|
|
116
117
|
];
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
118
|
+
// โโโ v4.1 FIX: Build Session Memory Tools dynamically โโโโโโโโ
|
|
119
|
+
// The session_load_context tool description is dynamically modified
|
|
120
|
+
// to include auto-load project instructions. Tool descriptions are
|
|
121
|
+
// the ONLY mechanism guaranteed by ALL MCP clients (Antigravity,
|
|
122
|
+
// Claude Code, Claude CLI). The `instructions` field and
|
|
123
|
+
// sendLoggingMessage are NOT reliably surfaced by any client.
|
|
124
|
+
//
|
|
125
|
+
// buildSessionMemoryTools() is called inside createServer() AFTER
|
|
126
|
+
// initConfigStorage() has run, so getSettingSync() returns real
|
|
127
|
+
// dashboard config values.
|
|
128
|
+
function buildSessionMemoryTools(autoloadList) {
|
|
129
|
+
// Dynamically inject auto-load instruction into session_load_context description
|
|
130
|
+
let loadContextTool = SESSION_LOAD_CONTEXT_TOOL;
|
|
131
|
+
if (autoloadList.length > 0) {
|
|
132
|
+
const projectList = autoloadList.join(", ");
|
|
133
|
+
loadContextTool = {
|
|
134
|
+
...SESSION_LOAD_CONTEXT_TOOL,
|
|
135
|
+
description: SESSION_LOAD_CONTEXT_TOOL.description +
|
|
136
|
+
`\n\nIMPORTANT: At the start of every conversation, you MUST call this tool for each of these projects: ${projectList}. Do this before any other work. Load level should be "standard" unless the user specifies otherwise.`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return [
|
|
140
|
+
SESSION_SAVE_LEDGER_TOOL, // session_save_ledger โ append immutable session log
|
|
141
|
+
SESSION_SAVE_HANDOFF_TOOL, // session_save_handoff โ upsert latest project state (now with OCC)
|
|
142
|
+
loadContextTool, // session_load_context โ progressive context loading (+ auto-load instruction)
|
|
143
|
+
KNOWLEDGE_SEARCH_TOOL, // knowledge_search โ search accumulated knowledge
|
|
144
|
+
KNOWLEDGE_FORGET_TOOL, // knowledge_forget โ prune bad/old memories
|
|
145
|
+
SESSION_COMPACT_LEDGER_TOOL, // session_compact_ledger โ auto-compact old ledger entries (v0.4.0)
|
|
146
|
+
SESSION_SEARCH_MEMORY_TOOL, // session_search_memory โ semantic search via embeddings (v0.4.0)
|
|
147
|
+
MEMORY_HISTORY_TOOL, // memory_history โ view version timeline (v2.0)
|
|
148
|
+
MEMORY_CHECKOUT_TOOL, // memory_checkout โ revert to past version (v2.0)
|
|
149
|
+
// โโโ v2.0: Visual Memory tools โโโ
|
|
150
|
+
SESSION_SAVE_IMAGE_TOOL, // session_save_image โ save image to media vault (v2.0)
|
|
151
|
+
SESSION_VIEW_IMAGE_TOOL, // session_view_image โ retrieve image from vault (v2.0)
|
|
152
|
+
// โโโ v2.2.0: Health Check tool โโโ
|
|
153
|
+
SESSION_HEALTH_CHECK_TOOL, // session_health_check โ brain integrity checker (v2.2.0)
|
|
154
|
+
// โโโ Phase 2: GDPR Memory Deletion tool โโโ
|
|
155
|
+
SESSION_FORGET_MEMORY_TOOL, // session_forget_memory โ GDPR-compliant memory deletion (Phase 2)
|
|
156
|
+
// โโโ v3.1: TTL Retention tool โโโ
|
|
157
|
+
KNOWLEDGE_SET_RETENTION_TOOL, // knowledge_set_retention โ set auto-expiry TTL for a project
|
|
158
|
+
// โโโ v4.0: Active Behavioral Memory tools โโโ
|
|
159
|
+
SESSION_SAVE_EXPERIENCE_TOOL, // session_save_experience โ record typed experience events
|
|
160
|
+
KNOWLEDGE_UPVOTE_TOOL, // knowledge_upvote โ increase entry importance
|
|
161
|
+
KNOWLEDGE_DOWNVOTE_TOOL, // knowledge_downvote โ decrease entry importance
|
|
162
|
+
];
|
|
163
|
+
}
|
|
154
164
|
// โโโ v0.4.0: Resource Subscription Tracking โโโโโโโโโโโโโโโโโโโโโโ
|
|
155
165
|
// REVIEWER NOTE: We track which project URIs clients have subscribed
|
|
156
166
|
// to. When sessionSaveHandoffHandler successfully updates a project,
|
|
@@ -194,6 +204,35 @@ export function notifyResourceUpdate(project, server) {
|
|
|
194
204
|
* with subscribe support for live refresh
|
|
195
205
|
*/
|
|
196
206
|
export function createServer() {
|
|
207
|
+
// โโโ v4.1 FIX: Auto-Load via Dynamic Tool Descriptions โโโโโโโโ
|
|
208
|
+
// Read auto-load projects EXCLUSIVELY from dashboard config
|
|
209
|
+
// (available after initConfigStorage() in startServer).
|
|
210
|
+
//
|
|
211
|
+
// ARCHITECTURE DECISION: We inject the auto-load instruction into
|
|
212
|
+
// the session_load_context TOOL DESCRIPTION, not into `instructions`
|
|
213
|
+
// or `sendLoggingMessage`. Tool descriptions are the ONLY mechanism
|
|
214
|
+
// guaranteed by ALL MCP clients (Antigravity, Claude Code, Claude CLI).
|
|
215
|
+
//
|
|
216
|
+
// The PRISM_AUTOLOAD_PROJECTS env var has been removed โ the dashboard
|
|
217
|
+
// is the single source of truth. This prevents mismatches between
|
|
218
|
+
// env var and dashboard settings causing duplicate project loads.
|
|
219
|
+
const dashboardAutoload = getSettingSync("autoload_projects", "");
|
|
220
|
+
const autoloadList = dashboardAutoload
|
|
221
|
+
.split(",").map(p => p.trim()).filter(Boolean);
|
|
222
|
+
if (autoloadList.length > 0) {
|
|
223
|
+
console.error(`[Prism] Auto-load projects (dashboard): ${autoloadList.join(', ')}`);
|
|
224
|
+
}
|
|
225
|
+
// Build the dynamic tool list with auto-load instruction injected
|
|
226
|
+
const SESSION_MEMORY_TOOLS = buildSessionMemoryTools(autoloadList);
|
|
227
|
+
// Combine: always list ALL tools so scanners (Glama, Smithery, MCP Registry)
|
|
228
|
+
// can enumerate the full capability set. Runtime guards in the CallTool handler
|
|
229
|
+
// still prevent execution without valid Supabase credentials.
|
|
230
|
+
const ALL_TOOLS = [
|
|
231
|
+
...BASE_TOOLS,
|
|
232
|
+
...SESSION_MEMORY_TOOLS,
|
|
233
|
+
// v3.0: Agent Hivemind tools โ only when PRISM_ENABLE_HIVEMIND=true
|
|
234
|
+
...(PRISM_ENABLE_HIVEMIND ? AGENT_REGISTRY_TOOLS : []),
|
|
235
|
+
];
|
|
197
236
|
const server = new Server({
|
|
198
237
|
name: SERVER_CONFIG.name,
|
|
199
238
|
version: SERVER_CONFIG.version,
|
|
@@ -201,21 +240,13 @@ export function createServer() {
|
|
|
201
240
|
capabilities: {
|
|
202
241
|
tools: {},
|
|
203
242
|
// โโโ v0.4.0: Prompt capability (Enhancement #1) โโโ
|
|
204
|
-
// REVIEWER NOTE: Declaring `prompts: {}` tells Claude Desktop
|
|
205
|
-
// that we support the prompts/list and prompts/get methods.
|
|
206
|
-
// This enables the /resume_session slash command in the UI.
|
|
207
|
-
// Only enabled when Supabase is configured (prompts need
|
|
208
|
-
// session data to be useful).
|
|
209
243
|
...(SESSION_MEMORY_ENABLED ? { prompts: {} } : {}),
|
|
210
244
|
// โโโ v0.4.0: Resource capability (Enhancement #3) โโโ
|
|
211
|
-
// REVIEWER NOTE: Setting subscribe: true tells Claude Desktop
|
|
212
|
-
// that we support resource subscriptions. When a user attaches
|
|
213
|
-
// memory://project/handoff and the LLM later updates it,
|
|
214
|
-
// we push an update notification so the attached context
|
|
215
|
-
// is silently refreshed. Without subscribe:true, the
|
|
216
|
-
// paperclipped context would become stale.
|
|
217
245
|
...(SESSION_MEMORY_ENABLED ? { resources: { subscribe: true } } : {}),
|
|
218
246
|
},
|
|
247
|
+
// Supplementary signal โ not all clients support this field.
|
|
248
|
+
// Primary mechanism is the dynamic tool description above.
|
|
249
|
+
instructions: `Prism MCP โ The Mind Palace for AI Agents. This server provides persistent session memory, knowledge search, and context management tools. Use session_load_context to recover previous work state, session_save_ledger to log completed work, and session_save_handoff to preserve state for the next session.`,
|
|
219
250
|
});
|
|
220
251
|
// โโ Handler: Initialize โโ
|
|
221
252
|
// NOTE: The SDK's built-in _oninitialize() handles the Initialize request.
|
|
@@ -666,7 +697,7 @@ export function createSandboxServer() {
|
|
|
666
697
|
});
|
|
667
698
|
// Register all tool listings unconditionally
|
|
668
699
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
669
|
-
tools: [...BASE_TOOLS, ...
|
|
700
|
+
tools: [...BASE_TOOLS, ...buildSessionMemoryTools([]), ...AGENT_REGISTRY_TOOLS],
|
|
670
701
|
}));
|
|
671
702
|
// Register prompts listing so scanners see resume_session
|
|
672
703
|
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
@@ -715,6 +746,9 @@ export function createSandboxServer() {
|
|
|
715
746
|
* responses to stdout. Log messages go to stderr.
|
|
716
747
|
*/
|
|
717
748
|
export async function startServer() {
|
|
749
|
+
// MUST BE FIRST: Kill any zombie processes and acquire the singleton PID lock
|
|
750
|
+
// before touching SQLite. This prevents lock contention on prism-config.db.
|
|
751
|
+
acquireLock();
|
|
718
752
|
// Pre-warm the config settings cache BEFORE connecting the MCP transport.
|
|
719
753
|
// This ensures getSettingSync() returns real values (agent_name, default_role)
|
|
720
754
|
// during the Initialize handshake โ zero extra latency for resource reads.
|
|
@@ -723,6 +757,10 @@ export async function startServer() {
|
|
|
723
757
|
const server = createServer();
|
|
724
758
|
const transport = new StdioServerTransport();
|
|
725
759
|
await server.connect(transport);
|
|
760
|
+
// Register graceful shutdown handlers (SIGTERM, SIGINT, SIGHUP, stdin close).
|
|
761
|
+
// The stdin close handler is critical โ when MCP clients disconnect, they
|
|
762
|
+
// often just close the pipe without sending a signal, leaving zombie processes.
|
|
763
|
+
registerShutdownHandlers();
|
|
726
764
|
// Pre-warm storage AFTER connecting โ fired async so we never block the
|
|
727
765
|
// stdio handshake. Supabase REST initialization can take 500msโ5s; blocking
|
|
728
766
|
// on it before server.connect() was the root cause of the 1m 56s CLI delay.
|
|
@@ -740,6 +778,18 @@ export async function startServer() {
|
|
|
740
778
|
]).catch(err => {
|
|
741
779
|
console.error(`[Prism] Storage pre-warm failed (non-fatal): ${err}`);
|
|
742
780
|
});
|
|
781
|
+
// โโโ v4.1: Auto-Load is handled via dynamic tool descriptions โโ
|
|
782
|
+
// The session_load_context tool description is dynamically modified
|
|
783
|
+
// in createServer() โ buildSessionMemoryTools() to include the
|
|
784
|
+
// auto-load projects list. This is the ONLY universally reliable
|
|
785
|
+
// mechanism โ tool descriptions are surfaced by ALL MCP clients.
|
|
786
|
+
//
|
|
787
|
+
// Previous approaches that FAILED:
|
|
788
|
+
// - sendLoggingMessage: goes to debug logs, not AI conversation
|
|
789
|
+
// - instructions field: not supported by Claude Code or Claude CLI
|
|
790
|
+
//
|
|
791
|
+
// No runtime code needed here โ the instruction is baked into the
|
|
792
|
+
// tool schema returned by ListTools.
|
|
743
793
|
}
|
|
744
794
|
// โโโ v2.0 Step 6: Initialize SyncBus (Telepathy) โโโ
|
|
745
795
|
// Fire-and-forget โ SyncBus is non-critical for startup.
|
|
@@ -102,17 +102,36 @@ export async function getSetting(key, defaultValue = "") {
|
|
|
102
102
|
export async function setSetting(key, value) {
|
|
103
103
|
await initConfigStorage();
|
|
104
104
|
const client = getClient();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
// Retry with exponential backoff for SQLITE_BUSY (concurrent writes).
|
|
106
|
+
// The dashboard and load tests can fire many parallel setting saves.
|
|
107
|
+
const MAX_RETRIES = 5;
|
|
108
|
+
const BASE_DELAY_MS = 20;
|
|
109
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
110
|
+
try {
|
|
111
|
+
await client.execute({
|
|
112
|
+
sql: `
|
|
113
|
+
INSERT INTO system_settings (key, value, updated_at)
|
|
114
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
115
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP
|
|
116
|
+
`,
|
|
117
|
+
args: [key, value],
|
|
118
|
+
});
|
|
119
|
+
// Keep the cache in sync so getSettingSync() reflects the new value immediately.
|
|
120
|
+
if (settingsCache) {
|
|
121
|
+
settingsCache[key] = value;
|
|
122
|
+
}
|
|
123
|
+
return; // Success โ exit
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
const isBusy = err?.code === "SQLITE_BUSY" || err?.rawCode === 5;
|
|
127
|
+
if (isBusy && attempt < MAX_RETRIES) {
|
|
128
|
+
// Exponential backoff + jitter
|
|
129
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 10;
|
|
130
|
+
await new Promise(r => setTimeout(r, delay));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
throw err; // Not SQLITE_BUSY or retries exhausted
|
|
134
|
+
}
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
export async function getAllSettings() {
|
|
@@ -129,3 +148,17 @@ export async function getAllSettings() {
|
|
|
129
148
|
}
|
|
130
149
|
return settings;
|
|
131
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Closes the config SQLite client to release the file handle on prism-config.db.
|
|
153
|
+
* Called by the lifecycle module during graceful shutdown.
|
|
154
|
+
*/
|
|
155
|
+
export function closeConfigStorage() {
|
|
156
|
+
if (configClient) {
|
|
157
|
+
try {
|
|
158
|
+
configClient.close();
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
console.error(`[ConfigStorage] Error closing db:`, e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
package/dist/storage/supabase.js
CHANGED
|
@@ -15,10 +15,18 @@
|
|
|
15
15
|
import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
|
|
16
16
|
import { debugLog } from "../utils/logger.js";
|
|
17
17
|
import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
|
|
18
|
+
import { runAutoMigrations } from "./supabaseMigrations.js";
|
|
18
19
|
export class SupabaseStorage {
|
|
19
20
|
// โโโ Lifecycle โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
20
21
|
async initialize() {
|
|
21
22
|
debugLog("[SupabaseStorage] Initialized (REST API, stateless)");
|
|
23
|
+
// Auto-apply pending schema migrations (non-fatal)
|
|
24
|
+
try {
|
|
25
|
+
await runAutoMigrations();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error("[SupabaseStorage] Auto-migration failed. Server will continue, but some tools may be unstable.", err instanceof Error ? err.message : err);
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
async close() {
|
|
24
32
|
debugLog("[SupabaseStorage] Closed (no-op for REST)");
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Auto-Migration Runner (v4.1)
|
|
3
|
+
*
|
|
4
|
+
* On server startup, this module checks the `prism_schema_versions` table
|
|
5
|
+
* and applies any pending DDL migrations via the `prism_apply_ddl` RPC.
|
|
6
|
+
*
|
|
7
|
+
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
8
|
+
* HOW IT WORKS:
|
|
9
|
+
* 1. For each migration in MIGRATIONS[], call prism_apply_ddl(version, name, sql)
|
|
10
|
+
* 2. The Postgres function checks if the version is already applied (idempotent)
|
|
11
|
+
* 3. If not applied, it EXECUTE's the SQL and records the version
|
|
12
|
+
*
|
|
13
|
+
* GRACEFUL DEGRADATION:
|
|
14
|
+
* If prism_apply_ddl doesn't exist (PGRST202), the runner logs a
|
|
15
|
+
* warning and skips โ the server still starts, but v4+ tools may
|
|
16
|
+
* fail against an old schema.
|
|
17
|
+
*
|
|
18
|
+
* SECURITY NOTE:
|
|
19
|
+
* prism_apply_ddl is SECURITY DEFINER (runs as postgres owner).
|
|
20
|
+
* The prism_schema_versions table has RLS: only service_role can write.
|
|
21
|
+
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
22
|
+
*/
|
|
23
|
+
import { supabaseRpc } from "../utils/supabaseApi.js";
|
|
24
|
+
/**
|
|
25
|
+
* All Supabase DDL migrations.
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: Only add migrations for schema changes that Supabase
|
|
28
|
+
* users need. SQLite handles its own schema in sqlite.ts.
|
|
29
|
+
*
|
|
30
|
+
* Each `sql` string is passed to Postgres EXECUTE โ it runs as a
|
|
31
|
+
* single transaction. Use IF NOT EXISTS / IF EXISTS guards generously.
|
|
32
|
+
*/
|
|
33
|
+
export const MIGRATIONS = [
|
|
34
|
+
{
|
|
35
|
+
version: 26,
|
|
36
|
+
name: "active_behavioral_memory",
|
|
37
|
+
sql: `
|
|
38
|
+
-- v4.0: Active Behavioral Memory columns
|
|
39
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS event_type TEXT NOT NULL DEFAULT 'session';
|
|
40
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS confidence_score INTEGER DEFAULT NULL;
|
|
41
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS importance INTEGER NOT NULL DEFAULT 0;
|
|
42
|
+
|
|
43
|
+
-- Soft-delete / archival columns
|
|
44
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
|
|
45
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS archived_at TIMESTAMPTZ DEFAULT NULL;
|
|
46
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_reason TEXT DEFAULT NULL;
|
|
47
|
+
|
|
48
|
+
-- Indexes
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_event_type ON session_ledger(event_type);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_importance ON session_ledger(importance DESC);
|
|
51
|
+
|
|
52
|
+
-- Partial index for high-priority warnings
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_behavioral_warnings
|
|
54
|
+
ON session_ledger(project, user_id, role, importance DESC)
|
|
55
|
+
WHERE event_type = 'correction' AND importance >= 3
|
|
56
|
+
AND deleted_at IS NULL AND archived_at IS NULL;
|
|
57
|
+
`,
|
|
58
|
+
},
|
|
59
|
+
// Future migrations go here (version 28+)
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* Current schema version โ derived from the MIGRATIONS array.
|
|
63
|
+
* Automatically updates when new migrations are added.
|
|
64
|
+
* Used for logging and diagnostics.
|
|
65
|
+
*/
|
|
66
|
+
export const CURRENT_SCHEMA_VERSION = MIGRATIONS.length > 0 ? MIGRATIONS[MIGRATIONS.length - 1].version : 27;
|
|
67
|
+
// โโโ Runner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
68
|
+
/**
|
|
69
|
+
* Run all pending auto-migrations on Supabase startup.
|
|
70
|
+
*
|
|
71
|
+
* Called from SupabaseStorage.initialize(). Non-fatal: if the
|
|
72
|
+
* migration infrastructure (027) hasn't been applied, the runner
|
|
73
|
+
* logs a warning and returns silently.
|
|
74
|
+
*/
|
|
75
|
+
export async function runAutoMigrations() {
|
|
76
|
+
if (MIGRATIONS.length === 0) {
|
|
77
|
+
return; // Nothing to apply
|
|
78
|
+
}
|
|
79
|
+
console.error(`[Prism Auto-Migration] Schema v${CURRENT_SCHEMA_VERSION} โ checking ${MIGRATIONS.length} migration(s)โฆ`);
|
|
80
|
+
for (const migration of MIGRATIONS) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await supabaseRpc("prism_apply_ddl", {
|
|
83
|
+
p_version: migration.version,
|
|
84
|
+
p_name: migration.name,
|
|
85
|
+
p_sql: migration.sql,
|
|
86
|
+
});
|
|
87
|
+
// Parse the JSON result from the RPC
|
|
88
|
+
const data = (typeof result === "string" ? JSON.parse(result) : result);
|
|
89
|
+
if (data?.status === "applied") {
|
|
90
|
+
console.error(`[Prism Auto-Migration] โ
Applied migration ${migration.version}: ${migration.name}`);
|
|
91
|
+
}
|
|
92
|
+
else if (data?.status === "already_applied") {
|
|
93
|
+
// Silent skip โ expected for idempotent restarts
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
// PGRST202 = function not found โ migration infra (027) not applied yet
|
|
99
|
+
if (errMsg.includes("PGRST202") || errMsg.includes("Could not find the function")) {
|
|
100
|
+
console.error("[Prism Auto-Migration] โ ๏ธ prism_apply_ddl() not found. " +
|
|
101
|
+
"Apply migration 027_auto_migration_infra.sql to enable auto-migrations.\n" +
|
|
102
|
+
" Run: supabase db push (or apply the SQL in the Supabase Dashboard SQL Editor)");
|
|
103
|
+
return; // Stop โ no point trying further migrations
|
|
104
|
+
}
|
|
105
|
+
// Any other error: log and throw to surface the problem
|
|
106
|
+
console.error(`[Prism Auto-Migration] โ Migration ${migration.version} (${migration.name}) failed: ${errMsg}`);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -55,6 +55,23 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
55
55
|
}
|
|
56
56
|
const { project, conversation_id, summary, todos, files_changed, decisions, role } = args;
|
|
57
57
|
const storage = await getStorage();
|
|
58
|
+
// โโโ Repo path mismatch validation (v4.2) โโโ
|
|
59
|
+
let repoPathWarning = "";
|
|
60
|
+
if (files_changed && files_changed.length > 0) {
|
|
61
|
+
try {
|
|
62
|
+
const configuredPath = await getSetting(`repo_path:${project}`, "");
|
|
63
|
+
if (configuredPath && configuredPath.trim()) {
|
|
64
|
+
const normalizedPath = configuredPath.trim().replace(/\\/g, "/").replace(/\/+$/, ""); // normalize + strip trailing slash
|
|
65
|
+
const mismatched = files_changed.filter((f) => !f.replace(/\\/g, "/").startsWith(normalizedPath));
|
|
66
|
+
if (mismatched.length === files_changed.length) {
|
|
67
|
+
repoPathWarning = `\n\nโ ๏ธ Project mismatch: none of the files_changed paths match repo_path "${normalizedPath}" ` +
|
|
68
|
+
`configured for project "${project}". Consider saving under the correct project.`;
|
|
69
|
+
debugLog(`[session_save_ledger] Repo path mismatch for "${project}": expected prefix "${normalizedPath}"`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch { /* getSetting non-fatal */ }
|
|
74
|
+
}
|
|
58
75
|
debugLog(`[session_save_ledger] Saving ledger entry for project="${project}"`);
|
|
59
76
|
// Auto-extract keywords from summary + decisions for knowledge accumulation
|
|
60
77
|
const combinedText = [summary, ...(decisions || [])].join(" ");
|
|
@@ -130,6 +147,7 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
130
147
|
(files_changed?.length ? `Files changed: ${files_changed.length}\n` : "") +
|
|
131
148
|
(decisions?.length ? `Decisions: ${decisions.length}\n` : "") +
|
|
132
149
|
(GOOGLE_API_KEY ? `๐ Embedding generation queued for semantic search.\n` : "") +
|
|
150
|
+
repoPathWarning +
|
|
133
151
|
`\nRaw response: ${JSON.stringify(result)}`,
|
|
134
152
|
}],
|
|
135
153
|
isError: false,
|
|
@@ -360,7 +378,8 @@ export async function sessionLoadContextHandler(args) {
|
|
|
360
378
|
throw new Error("Invalid arguments for session_load_context");
|
|
361
379
|
}
|
|
362
380
|
const { project, level = "standard", role } = args;
|
|
363
|
-
const maxTokens = args.max_tokens
|
|
381
|
+
const maxTokens = args.max_tokens
|
|
382
|
+
|| parseInt(await getSetting("max_tokens", "0"), 10) || undefined; // v4.0: arg > dashboard setting > none
|
|
364
383
|
const agentName = await getSetting("agent_name", "");
|
|
365
384
|
const validLevels = ["quick", "standard", "deep"];
|
|
366
385
|
if (!validLevels.includes(level)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-mcp",
|
|
5
5
|
"description": "The Mind Palace for AI Agents โ local-first MCP server with persistent memory (SQLite/Supabase), visual dashboard, time travel, multi-agent sync, Morning Briefings, reality drift detection, code mode templates, semantic vector search, and Brave Search + Gemini analysis. Zero-config local mode.",
|
|
6
6
|
"module": "index.ts",
|