prism-mcp-server 7.4.0 → 7.6.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 +135 -60
- package/dist/cli.js +0 -0
- package/dist/config.js +11 -0
- package/dist/dashboard/intentHealth.js +62 -0
- package/dist/dashboard/server.js +35 -1
- package/dist/dashboard/ui.js +117 -11
- package/dist/storage/sqlite.js +24 -0
- package/dist/storage/supabase.js +30 -1
- package/dist/tools/ledgerHandlers.js +13 -0
- package/dist/utils/llm/adapters/anthropic.js +5 -2
- package/dist/utils/llm/adapters/voyage.js +129 -0
- package/dist/utils/llm/factory.js +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
[](CONTRIBUTING.md)
|
|
10
10
|
|
|
11
|
+

|
|
12
|
+
|
|
11
13
|
**Your AI agent forgets everything between sessions. Prism fixes that.**
|
|
12
14
|
|
|
13
15
|
One command. Persistent memory. Local-first by default. Optional cloud power-ups.
|
|
@@ -21,20 +23,21 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
|
|
|
21
23
|
## 📖 Table of Contents
|
|
22
24
|
|
|
23
25
|
- [Why Prism?](#why-prism)
|
|
24
|
-
- [Quick Start](
|
|
25
|
-
- [The Magic Moment](
|
|
26
|
-
- [Setup Guides](
|
|
27
|
-
- [Universal Import](
|
|
28
|
-
- [What Makes Prism Different](
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [The Magic Moment](#the-magic-moment)
|
|
28
|
+
- [Setup Guides](#setup-guides)
|
|
29
|
+
- [Universal Import: Bring Your History](#universal-import-bring-your-history)
|
|
30
|
+
- [What Makes Prism Different](#what-makes-prism-different)
|
|
31
|
+
- [Data Privacy & Egress](#data-privacy--egress)
|
|
32
|
+
- [Use Cases](#use-cases)
|
|
33
|
+
- [What's New](#whats-new)
|
|
34
|
+
- [How Prism Compares](#how-prism-compares)
|
|
35
|
+
- [Tool Reference](#tool-reference)
|
|
33
36
|
- [Environment Variables](#environment-variables)
|
|
34
37
|
- [Architecture](#architecture)
|
|
35
|
-
- [Scientific Foundation](
|
|
36
|
-
- [
|
|
37
|
-
- [Troubleshooting FAQ](
|
|
38
|
+
- [Scientific Foundation](#scientific-foundation)
|
|
39
|
+
- [Milestones & Roadmap](#milestones--roadmap)
|
|
40
|
+
- [Troubleshooting FAQ](#troubleshooting-faq)
|
|
38
41
|
|
|
39
42
|
---
|
|
40
43
|
|
|
@@ -46,12 +49,23 @@ Every time you start a new conversation with an AI coding assistant, it starts f
|
|
|
46
49
|
|
|
47
50
|
> 📌 **Terminology:** Throughout this doc, **"Prism"** refers to the MCP server and storage engine. **"Mind Palace"** refers to the visual dashboard UI at `localhost:3000` — your window into the agent's brain. They work together; the dashboard is optional.
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
Prism has two pillars:
|
|
53
|
+
|
|
54
|
+
1. **🧠 Persistent Memory** — Memories are ranked like a human brain: recently and frequently accessed context surfaces first, while stale context fades naturally. The result is retrieval quality that no flat vector search can match. *(See [Scientific Foundation](#-scientific-foundation) for the ACT-R math.)*
|
|
55
|
+
|
|
56
|
+
2. **🏭 Autonomous Execution (Dark Factory)** — When you're ready, Prism can run coding tasks end-to-end with a fail-closed pipeline where an adversarial evaluator catches bugs the generator missed — before you ever see the PR. *(See [Dark Factory](#-dark-factory--adversarial-autonomous-pipelines).)*
|
|
50
57
|
|
|
51
58
|
---
|
|
52
59
|
|
|
53
60
|
## 🚀 Quick Start
|
|
54
61
|
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
- **Node.js v18+** (v20 LTS recommended; v23.x has [known `npx` quirk](#common-installation-pitfalls))
|
|
65
|
+
- Any MCP-compatible client (Claude Desktop, Cursor, Windsurf, Cline, etc.)
|
|
66
|
+
- No API keys required for core features (see [Capability Matrix](#capability-matrix))
|
|
67
|
+
|
|
68
|
+
### Install
|
|
55
69
|
|
|
56
70
|
Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
|
|
57
71
|
|
|
@@ -70,6 +84,10 @@ Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`,
|
|
|
70
84
|
|
|
71
85
|
**That's it.** Restart your client. All tools are available. The **Mind Palace Dashboard** (the visual UI for your agent's brain) starts automatically at `http://localhost:3000`. You don't need to keep a tab open — the dashboard runs in the background and the MCP tools work with or without it.
|
|
72
86
|
|
|
87
|
+
> 🔮 **Pro Tip:** Once installed, open **`http://localhost:3000`** in your browser to view the Mind Palace Dashboard — a beautiful, real-time UI of your agent's brain. Explore the Knowledge Graph, Intent Health gauges, and Session Ledger.
|
|
88
|
+
|
|
89
|
+
> 🔄 **Updating Prism:** `npx -y` caches the package locally. To force an update to the latest version, restart your MCP client — `npx -y` will fetch the newest release automatically. If you're stuck on a stale version, run `npx clear-npx-cache` (or `npm cache clean --force`) before restarting.
|
|
90
|
+
|
|
73
91
|
<details>
|
|
74
92
|
<summary>Port 3000 already in use? (Next.js / Vite / etc.)</summary>
|
|
75
93
|
|
|
@@ -109,6 +127,8 @@ Then open `http://localhost:3001` instead.
|
|
|
109
127
|
|
|
110
128
|
> 🔑 The core Mind Palace works **100% offline** with zero API keys. Cloud keys unlock intelligence features. See [Environment Variables](#environment-variables).
|
|
111
129
|
|
|
130
|
+
> 💰 **API Cost Note:** `GOOGLE_API_KEY` (Gemini) has a generous free tier that covers most individual use. `BRAVE_API_KEY` offers 2,000 free searches/month. `FIRECRAWL_API_KEY` has a free plan with 500 credits. For typical solo development, expect **$0/month** on the free tiers. Only high-volume teams or heavy autonomous pipeline usage will incur meaningful costs.
|
|
131
|
+
|
|
112
132
|
---
|
|
113
133
|
|
|
114
134
|
## ✨ The Magic Moment
|
|
@@ -300,6 +320,32 @@ Then add to your MCP config:
|
|
|
300
320
|
|
|
301
321
|
</details>
|
|
302
322
|
|
|
323
|
+
<details>
|
|
324
|
+
<summary><strong>Cloud Deployment (Render)</strong></summary>
|
|
325
|
+
|
|
326
|
+
Prism can be deployed natively to cloud platforms like [Render](https://render.com) so your agent's memory is always online and accessible across different machines or teams.
|
|
327
|
+
|
|
328
|
+
1. Fork this repository.
|
|
329
|
+
2. In the Render Dashboard, create a new **Web Service** pointing to your repository.
|
|
330
|
+
3. In the setup wizard, select **Docker** as the Runtime.
|
|
331
|
+
4. Set the Dockerfile path to `Dockerfile.smithery`.
|
|
332
|
+
5. Connect your local MCP client to your new cloud endpoint using the `sse` transport:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"mcpServers": {
|
|
337
|
+
"prism-mcp-cloud": {
|
|
338
|
+
"command": "npx",
|
|
339
|
+
"args": ["-y", "supergateway", "--url", "https://your-prism-app.onrender.com/sse"]
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
> **Note:** The `Dockerfile.smithery` uses an optimized multi-stage build that compiles Typescript safely in a development environment before booting the server in a stripped-down production image. No NPM publishing required!
|
|
346
|
+
|
|
347
|
+
</details>
|
|
348
|
+
|
|
303
349
|
### Common Installation Pitfalls
|
|
304
350
|
|
|
305
351
|
> **❌ Don't use `npm install -g`:**
|
|
@@ -321,9 +367,15 @@ Then add to your MCP config:
|
|
|
321
367
|
> **❓ Seeing warnings about missing API keys on startup?**
|
|
322
368
|
> That's expected and not an error. `BRAVE_API_KEY` / `GOOGLE_API_KEY` warnings are informational only — core session memory works with zero keys. See [Environment Variables](#environment-variables) for what each key unlocks.
|
|
323
369
|
|
|
370
|
+
> 💡 **Do agents auto-load Prism?** Agents using Cursor, Windsurf, or other MCP clients will see the `session_load_context` tool automatically, but may not call it unprompted. Add this to your project's `.cursorrules` (or equivalent system prompt) to guarantee auto-load:
|
|
371
|
+
> ```
|
|
372
|
+
> At the start of every conversation, call session_load_context with project "my-project" before doing any work.
|
|
373
|
+
> ```
|
|
374
|
+
> Claude Code users can use the `.clauderules` auto-load hook shown in the [Setup Guides](#setup-guides). Prism also has a **server-side fallback** (v5.2.1+) that auto-pushes context after 10 seconds if no load is detected.
|
|
375
|
+
|
|
324
376
|
---
|
|
325
377
|
|
|
326
|
-
## 📥 Universal Import
|
|
378
|
+
## 📥 Universal Import: Bring Your History
|
|
327
379
|
|
|
328
380
|
Switching to Prism? Don't leave months of AI session history behind. Prism can **ingest historical sessions from Claude Code, Gemini, and OpenAI** and give your Mind Palace an instant head start — no manual re-entry required.
|
|
329
381
|
|
|
@@ -349,7 +401,7 @@ npx -y prism-mcp-server universal-import --format gemini --path ./gemini_history
|
|
|
349
401
|
**Option 2 — Dashboard:** Open `localhost:3000`, navigate to the **Import** tab, select the format and file, and click Import. Supports dry-run preview.
|
|
350
402
|
|
|
351
403
|
### Why It's Safe to Re-Run
|
|
352
|
-
* **
|
|
404
|
+
* **Memory-Safe Streaming:** Processes massive log files line-by-line using `stream-json` to prevent Out-of-Memory (OOM) crashes.
|
|
353
405
|
* **Idempotent Dedup:** Content-hash prevents duplicate imports on re-run (`skipCount` reported).
|
|
354
406
|
* **Chronological Integrity:** Uses timestamp fallbacks and `requestId` sorting to preserve your memory timeline.
|
|
355
407
|
* **Smart Context Mapping:** Extracts `cwd`, `gitBranch`, and tool usage patterns into searchable metadata.
|
|
@@ -369,6 +421,7 @@ Every save creates a versioned snapshot. Made a mistake? `memory_checkout` rever
|
|
|
369
421
|
A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what your agent is thinking:
|
|
370
422
|
|
|
371
423
|
- **Current State & TODOs** — the exact context injected into the LLM's prompt
|
|
424
|
+
- **Intent Health Gauges** — per-project 0–100 health score with staleness decay, TODO load, and decision signals
|
|
372
425
|
- **Interactive Knowledge Graph** — force-directed neural graph with click-to-filter, node renaming, and surgical keyword deletion
|
|
373
426
|
- **Deep Storage Manager** — preview and execute vector purge operations with dry-run safety
|
|
374
427
|
- **Session Ledger** — full audit trail of every decision your agent has made
|
|
@@ -378,7 +431,7 @@ A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what y
|
|
|
378
431
|
- **Morning Briefing** — AI-synthesized action plan after 4+ hours away
|
|
379
432
|
- **Brain Health** — memory integrity scan with one-click auto-repair
|
|
380
433
|
|
|
381
|
-
|
|
434
|
+
|
|
382
435
|
|
|
383
436
|
### 🧬 10× Memory Compression
|
|
384
437
|
Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's ICLR research), Prism compresses 768-dim embeddings from **3,072 bytes → ~400 bytes** — enabling decades of session history on a standard laptop. No native modules. No vector database required.
|
|
@@ -387,7 +440,7 @@ Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's I
|
|
|
387
440
|
Multiple agents (dev, QA, PM) can work on the same project with **role-isolated memory**. Agents discover each other automatically, share context in real-time via Telepathy sync, and see a team roster during context loading. → [Multi-agent setup example](examples/multi-agent-hivemind/)
|
|
388
441
|
|
|
389
442
|
### 🚦 Task Router
|
|
390
|
-
Prism can score coding tasks and recommend whether to keep execution on the host model or delegate to local Claw. This enables faster handling of small
|
|
443
|
+
Prism can score coding tasks and recommend whether to keep execution on the host model or delegate to a **local Claw agent** (a lightweight sub-agent powered by Ollama/vLLM for fast, local-safe edits). This enables faster handling of small edits while preserving host execution for complex work. In client startup/skill flows, use defensive delegation: route only coding tasks, call `session_task_route` only when available, delegate to `claw` only when executor tooling exists and task is non-destructive, and fallback to host when router/executor is unavailable. → [Task router real-life example](examples/router_real_life_test.ts)
|
|
391
444
|
|
|
392
445
|
### 🖼️ Visual Memory
|
|
393
446
|
Save UI screenshots, architecture diagrams, and bug states to a searchable vault. Images are auto-captioned by a VLM (Claude Vision / GPT-4V / Gemini) and become semantically searchable across sessions.
|
|
@@ -398,11 +451,39 @@ OpenTelemetry spans for every MCP tool call, LLM hop, and background worker. Rou
|
|
|
398
451
|
### 🌐 Autonomous Web Scholar
|
|
399
452
|
Prism researches while you sleep. A background pipeline searches the web, scrapes articles, synthesizes findings via LLM, and injects results directly into your semantic memory — fully searchable on your next session. Brave Search → Firecrawl scrape → LLM synthesis → Prism ledger. Task-aware, Hivemind-integrated, and zero-config when API keys are missing (falls back to Yahoo + Readability).
|
|
400
453
|
|
|
401
|
-
### 🔒 GDPR Compliant
|
|
402
|
-
Soft/hard delete (Art. 17), full export in JSON, Markdown, or Obsidian vault `.zip` (Art. 20), API key redaction, per-project TTL retention, and audit trail. Enterprise-ready out of the box.
|
|
403
|
-
|
|
404
454
|
### 🏭 Dark Factory — Adversarial Autonomous Pipelines
|
|
405
|
-
When you trigger a Dark Factory pipeline, Prism doesn't just run your task — it fights itself to produce high-quality output. A `PLAN_CONTRACT` step locks a machine-parseable rubric before any code is written. After execution, an **Adversarial Evaluator** (in a fully isolated context) scores the output against the rubric. It cannot pass the Generator without providing exact file and line evidence for every failing criterion. Failed evaluations inject the critique directly into the Generator's retry prompt so it's never flying blind. The result: security issues, regressions, and lazy debug logs caught autonomously — before you ever see the PR.
|
|
455
|
+
When you trigger a Dark Factory pipeline, Prism doesn't just run your task — it fights itself to produce high-quality output. A `PLAN_CONTRACT` step locks a machine-parseable rubric before any code is written. After execution, an **Adversarial Evaluator** (in a fully isolated context) scores the output against the rubric. It cannot pass the Generator without providing exact file and line evidence for every failing criterion. Failed evaluations inject the critique directly into the Generator's retry prompt so it's never flying blind. The result: security issues, regressions, and lazy debug logs caught autonomously — before you ever see the PR. → [See it in action](examples/adversarial-eval-demo/README.md)
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## 🔒 Data Privacy & Egress
|
|
460
|
+
|
|
461
|
+
**Where is my data stored?**
|
|
462
|
+
|
|
463
|
+
All data lives under `~/.prism-mcp/` on your machine:
|
|
464
|
+
|
|
465
|
+
| File | Contents |
|
|
466
|
+
|------|----------|
|
|
467
|
+
| `~/.prism-mcp/data.db` | All sessions, handoffs, embeddings, knowledge graph (SQLite + WAL) |
|
|
468
|
+
| `~/.prism-mcp/prism-config.db` | Dashboard settings, system config, API keys |
|
|
469
|
+
| `~/.prism-mcp/media/<project>/` | Visual memory vault (screenshots, HTML captures) |
|
|
470
|
+
| `~/.prism-mcp/dashboard.port` | Ephemeral port lock file |
|
|
471
|
+
| `~/.prism-mcp/sync.lock` | Sync coordination lock |
|
|
472
|
+
|
|
473
|
+
**Hard reset:** To completely erase your agent's brain, stop Prism and delete the directory:
|
|
474
|
+
```bash
|
|
475
|
+
rm -rf ~/.prism-mcp
|
|
476
|
+
```
|
|
477
|
+
Prism will recreate the directory with empty databases on next startup.
|
|
478
|
+
|
|
479
|
+
**What leaves your machine?**
|
|
480
|
+
- **Local mode (default):** Nothing. Zero network calls. All data is on-disk SQLite.
|
|
481
|
+
- **With `GOOGLE_API_KEY`:** Text snippets are sent to Gemini for embedding generation, summaries, and Morning Briefings. No session data is stored on Google's servers beyond the API call.
|
|
482
|
+
- **With `VOYAGE_API_KEY` / `OPENAI_API_KEY`:** Text snippets are sent to providers if selected as your embedding endpoints.
|
|
483
|
+
- **With `BRAVE_API_KEY` / `FIRECRAWL_API_KEY`:** Web Scholar queries are sent to Brave/Firecrawl for search and scraping.
|
|
484
|
+
- **With Supabase:** Session data syncs to your own Supabase instance (you control the Postgres database).
|
|
485
|
+
|
|
486
|
+
**GDPR compliance:** Soft/hard delete (Art. 17), full export in JSON, Markdown, or Obsidian vault `.zip` (Art. 20), API key redaction in exports, per-project TTL retention policies, and immutable audit trail. Enterprise-ready out of the box.
|
|
406
487
|
|
|
407
488
|
---
|
|
408
489
|
|
|
@@ -411,7 +492,8 @@ When you trigger a Dark Factory pipeline, Prism doesn't just run your task — i
|
|
|
411
492
|
- **Long-running feature work** — Save state at end of day, restore full context next morning. No re-explaining.
|
|
412
493
|
- **Multi-agent collaboration** — Dev, QA, and PM agents share real-time context without stepping on each other's memory.
|
|
413
494
|
- **Consulting / multi-project** — Switch between client projects with progressive loading: `quick` (~50 tokens), `standard` (~200), or `deep` (~1000+).
|
|
414
|
-
|
|
495
|
+
- **Autonomous execution (v7.4)** — Dark Factory pipeline: `plan → plan_contract → execute → evaluate → verify → finalize`. Generator and evaluator run in isolated roles — the evaluator cannot approve without evidence-bound findings scored against a pre-committed rubric.
|
|
496
|
+
- **Project health monitoring (v7.5)** — Intent Health Dashboard scores each project 0–100 based on staleness, TODO load, and decision quality — turning silent drift into an actionable signal.
|
|
415
497
|
- **Team onboarding** — New team member's agent loads the full project history instantly.
|
|
416
498
|
- **Behavior enforcement** — Agent corrections auto-graduate into permanent `.cursorrules` / `.clauderules` rules.
|
|
417
499
|
- **Offline / air-gapped** — Full SQLite local mode + Ollama LLM adapter. Zero internet dependency.
|
|
@@ -434,8 +516,6 @@ Then continue a specific thread with a follow-up message to the selected agent,
|
|
|
434
516
|
|
|
435
517
|
---
|
|
436
518
|
|
|
437
|
-
---
|
|
438
|
-
|
|
439
519
|
## ⚔️ Adversarial Evaluation in Action
|
|
440
520
|
|
|
441
521
|
> **Split-Brain Anti-Sycophancy** — the signature feature of v7.4.0.
|
|
@@ -543,18 +623,13 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
|
|
|
543
623
|
|
|
544
624
|
## 🆕 What's New
|
|
545
625
|
|
|
626
|
+
> **Current release: v7.5.0**
|
|
546
627
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
-
|
|
550
|
-
- 🔧 **v7.3.3 — Dashboard Stability Hotfix:** Fixed a multi-layer quote-escaping trap in the `abortPipeline` onclick handler that silently killed the dashboard IIFE and froze the project selector at "Loading projects..." forever. Fixed via `data-id` attribute pattern + ES5 lint guard (`npm run lint:dashboard`).
|
|
551
|
-
- 🏭 **v7.3.1 — Dark Factory (Fail-Closed Execution):** The LLM can no longer touch the filesystem directly. Every autonomous `EXECUTE` step passes 3 gates — Parse → Type → Scope — before any side effect occurs. Scope violations terminate the entire pipeline.
|
|
552
|
-
- 📊 **v7.3.2 — Verification Diagnostics v2:** `verify status --json` now emits per-layer `diff_counts` + `changed_keys`. JSON schema is contract-enforced in CI (`schema_version: 1`).
|
|
553
|
-
- 🔭 **v7.2.0 — Verification Harness:** Spec-frozen contracts (`verification_harness.json` hash-locked before execution), multi-layer assertions across Data / Agent / Pipeline, and finalization gate policies (`warn` / `gate` / `abort`).
|
|
554
|
-
- 🚦 **v7.1.0 — Task Router:** Heuristic + ML-experience routing delegates cloud vs. local model in under 2ms, cold-start safe, per-project experience-corrected.
|
|
555
|
-
- 🧠 **v7.0.0 — ACT-R Activation Memory:** `B_i = ln(Σ t_j^{-d})` recency × frequency re-ranking. Stale memories fade naturally. Active context surfaces automatically.
|
|
628
|
+
- 🩺 **v7.5.0 — Intent Health Dashboard + Security Hardening:** Real-time 0–100 project health scoring (staleness × TODO load × decisions). 10 XSS injection vectors patched. Algorithm hardened with NaN guards and score ceiling.
|
|
629
|
+
- ⚔️ **v7.4.0 — Adversarial Evaluation:** Split-brain anti-sycophancy pipeline. Generator and evaluator in isolated roles with evidence-bound findings.
|
|
630
|
+
- 🏭 **v7.3.x — Dark Factory + Stability:** Fail-closed 3-gate execution pipeline. Dashboard stability and verification diagnostics.
|
|
556
631
|
|
|
557
|
-
👉 **[Full release history → CHANGELOG.md](CHANGELOG.md)** · [ROADMAP →](ROADMAP.md)
|
|
632
|
+
👉 **[Full release history → CHANGELOG.md](CHANGELOG.md)** · **[ROADMAP →](ROADMAP.md)**
|
|
558
633
|
|
|
559
634
|
---
|
|
560
635
|
|
|
@@ -765,6 +840,8 @@ Requires `PRISM_DARK_FACTORY_ENABLED=true`.
|
|
|
765
840
|
| `PRISM_ENABLE_HIVEMIND` | No | `"true"` to enable multi-agent tools — restart required |
|
|
766
841
|
| `PRISM_INSTANCE` | No | Instance name for multi-server PID isolation |
|
|
767
842
|
| `GOOGLE_API_KEY` | No | Gemini — enables semantic search, Briefings, compaction |
|
|
843
|
+
| `VOYAGE_API_KEY` | No | Voyage AI — optional premium embedding provider |
|
|
844
|
+
| `OPENAI_API_KEY` | No | OpenAI — optional proxy model and embedding provider |
|
|
768
845
|
| `BRAVE_ANSWERS_API_KEY` | No | Separate Brave Answers key |
|
|
769
846
|
| `SUPABASE_URL` | If cloud | Supabase project URL |
|
|
770
847
|
| `SUPABASE_KEY` | If cloud | Supabase anon/service key |
|
|
@@ -793,6 +870,10 @@ Requires `PRISM_DARK_FACTORY_ENABLED=true`.
|
|
|
793
870
|
|
|
794
871
|
</details>
|
|
795
872
|
|
|
873
|
+
### System Settings (Dashboard)
|
|
874
|
+
Some configurations are stored dynamically in SQLite (`system_settings` table) and can be edited through the Dashboard UI at `http://localhost:3000`:
|
|
875
|
+
- **`intent_health_stale_threshold_days`** (default: `30`): Number of days before a project is considered fully stale for Intent Health scoring.
|
|
876
|
+
|
|
796
877
|
---
|
|
797
878
|
|
|
798
879
|
## Architecture
|
|
@@ -888,6 +969,7 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
|
|
|
888
969
|
| **v7.3** | Dark Factory — 3-gate fail-closed EXECUTE pipeline (parse → type → scope) with structured JSON action contract | Industrial safety systems (defense-in-depth, fail-closed valves) | ✅ Shipped |
|
|
889
970
|
| **v7.2** | Verification-first harness — spec-freeze contract, rubric hash lock, multi-layer assertions, CLI `verify` commands | Programmatic verification systems + adversarial validation loops | ✅ Shipped |
|
|
890
971
|
| **v7.4** | Adversarial Evaluation — PLAN_CONTRACT + EVALUATE with isolated generator/evaluator roles, pre-committed rubrics, and evidence-bound findings | Anti-sycophancy research, adversarial ML evaluation frameworks | ✅ Shipped |
|
|
972
|
+
| **v7.5** | Intent Health Dashboard — 3-signal scoring algorithm (staleness, TODO load, decisions), comprehensive XSS hardening (10 vectors), NaN/`Infinity` guards | Proactive monitoring, defense-in-depth security | ✅ Shipped |
|
|
891
973
|
| **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
|
|
892
974
|
| **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
|
|
893
975
|
|
|
@@ -895,34 +977,26 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
|
|
|
895
977
|
|
|
896
978
|
---
|
|
897
979
|
|
|
898
|
-
## 📦
|
|
899
|
-
|
|
900
|
-
> **[Full ROADMAP.md →](ROADMAP.md)**
|
|
901
|
-
|
|
902
|
-
### v6.2: The "Synthesize & Prune" Phase ✅
|
|
903
|
-
Shipped in v6.2.0. Edge synthesis, graph pruning with SLO observability, temporal decay heatmaps, active recall prompt generation, and full dashboard metrics integration.
|
|
980
|
+
## 📦 Milestones & Roadmap
|
|
904
981
|
|
|
905
|
-
|
|
906
|
-
Shipped. Full Superposed Memory (SDM) + Hyperdimensional Computing (HDC/VSA) cognitive routing pipeline. Compositional memory states via XOR binding, Hamming resolution, and policy-gated routing (direct / clarify / fallback). 705 tests passing.
|
|
982
|
+
> **Current: v7.5.0** — Intent Health Dashboard + XSS Hardening ([CHANGELOG](CHANGELOG.md))
|
|
907
983
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
Shipped. Scientifically-grounded retrieval re-ranking via ACT-R base-level activation (`B_i = ln(Σ t_j^(-d))`), candidate-scoped spreading activation, parameterized sigmoid normalization, composite scoring, and zero-cold-start access log infrastructure. 49 dedicated unit tests, 705 total passing.
|
|
919
|
-
|
|
920
|
-
### v7.2: Verification Harness ✅
|
|
921
|
-
Shipped. Spec-frozen verification contract (`implementation_plan.md` + `verification_harness.json` + immutable `validation_result`), multi-layer machine checks (`data`, `agent`, `pipeline`), finalization gate policies (`warn` / `gate` / `abort`), and CLI `verify generate` / `verify status --json` with schema-versioned output.
|
|
984
|
+
| Release | Headline |
|
|
985
|
+
|---------|----------|
|
|
986
|
+
| **v7.5** | Intent Health scoring + 10 XSS patches |
|
|
987
|
+
| **v7.4** | Adversarial Evaluation (anti-sycophancy) |
|
|
988
|
+
| **v7.3** | Dark Factory fail-closed execution |
|
|
989
|
+
| **v7.2** | Verification Harness |
|
|
990
|
+
| **v7.1** | Task Router |
|
|
991
|
+
| **v7.0** | ACT-R Activation Memory |
|
|
992
|
+
| **v6.5** | HDC Cognitive Routing |
|
|
993
|
+
| **v6.2** | Synthesize & Prune |
|
|
922
994
|
|
|
923
995
|
### Future Tracks
|
|
924
|
-
- **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence
|
|
925
|
-
- **v8+: Zero-Search Retrieval** — Direct vector-addressed recall
|
|
996
|
+
- **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence.
|
|
997
|
+
- **v8+: Zero-Search Retrieval** — Direct vector-addressed recall reduces retrieval indirection.
|
|
998
|
+
|
|
999
|
+
👉 **[Full ROADMAP.md →](ROADMAP.md)**
|
|
926
1000
|
|
|
927
1001
|
|
|
928
1002
|
## ❓ Troubleshooting FAQ
|
|
@@ -939,18 +1013,19 @@ A: Use `session_forget_memory` for targeted soft/hard deletion. For manual clean
|
|
|
939
1013
|
**Q: How do I verify the install quickly?**
|
|
940
1014
|
A: Run `npm run build && npm test`, then open the Mind Palace dashboard (`localhost:3000`) and confirm projects load plus Graph Health renders.
|
|
941
1015
|
|
|
1016
|
+
|
|
942
1017
|
---
|
|
943
1018
|
|
|
944
1019
|
### 💡 Known Limitations & Quirks
|
|
945
1020
|
|
|
946
1021
|
- **LLM-dependent features require an API key.** Semantic search, Morning Briefings, auto-compaction, and VLM captioning need a `GOOGLE_API_KEY` (your Gemini API key) or equivalent provider key. Without one, Prism falls back to keyword-only search (FTS5).
|
|
947
|
-
- **Auto-load is model- and client-dependent.** Session auto-loading relies on both the LLM following system prompt instructions *and* the MCP client completing tool registration before the model's first turn. Prism provides platform-specific [Setup Guides](
|
|
1022
|
+
- **Auto-load is model- and client-dependent.** Session auto-loading relies on both the LLM following system prompt instructions *and* the MCP client completing tool registration before the model's first turn. Prism provides platform-specific [Setup Guides](#setup-guides) and a server-side fallback (v5.2.1) that auto-pushes context after 10 seconds.
|
|
948
1023
|
- **MCP client race conditions.** Some MCP clients may not finish tool enumeration before the model generates its first response, causing transient `unknown_tool` errors. This is a client-side timing issue — Prism's server completes the MCP handshake in ~60ms. Workaround: the server-side auto-push fallback and the startup skill's retry logic.
|
|
949
1024
|
- **No real-time sync without Supabase.** Local SQLite mode is single-machine only. Multi-device or team sync requires a Supabase backend.
|
|
950
1025
|
- **Embedding quality varies by provider.** Gemini `text-embedding-004` and OpenAI `text-embedding-3-small` produce high-quality 768-dim vectors. Prism passes `dimensions: 768` via the Matryoshka API for OpenAI models (native output is 1536-dim; this truncation is lossless and outperforms ada-002 at full 1536 dims). Ollama embeddings (e.g., `nomic-embed-text`) are usable but may reduce retrieval accuracy.
|
|
951
1026
|
- **Dashboard is HTTP-only.** The Mind Palace dashboard at `localhost:3000` does not support HTTPS. For remote access, use a reverse proxy (nginx/Caddy) or SSH tunnel. Basic auth is available via `PRISM_DASHBOARD_USER` / `PRISM_DASHBOARD_PASS`.
|
|
952
1027
|
- **Long-lived clients can accumulate zombie processes.** MCP clients that run for extended periods (e.g., Claude CLI) may leave orphaned Prism server processes. The lifecycle manager detects true orphans (PPID=1) but allows coexistence for active parent processes. Use `PRISM_INSTANCE` to isolate instances across clients.
|
|
953
|
-
- **Migration is one-way.** Universal
|
|
1028
|
+
- **Migration is one-way.** Universal Import ingests sessions *into* Prism but does not export back to Claude/Gemini/OpenAI formats. Use `session_export_memory` for portable JSON/Markdown export, or the `vault` format for Obsidian/Logseq-compatible `.zip` archives.
|
|
954
1029
|
- **Export ceiling at 10,000 ledger entries.** The `session_export_memory` tool and the dashboard export button cap vault/JSON exports at 10,000 entries per project as an OOM guard. Projects exceeding this limit should use per-project exports and time-based filtering to stay within the ceiling. This limit does not affect search or context loading.
|
|
955
1030
|
- **No Windows CI testing.** Prism is developed and tested on macOS/Linux. It should work on Windows via Node.js, but edge cases (file paths, PID locks) may surface.
|
|
956
1031
|
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/config.js
CHANGED
|
@@ -15,6 +15,9 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
* SUPABASE_KEY — (optional) Your Supabase anon/service key. Enables session memory tools.
|
|
16
16
|
* PRISM_USER_ID — (optional) Unique tenant ID for multi-user Supabase instances.
|
|
17
17
|
* Defaults to "default". Set per-user in Claude Desktop config.
|
|
18
|
+
* VOYAGE_API_KEY — (optional) API key for Voyage AI embeddings. Enables embedding_provider=voyage.
|
|
19
|
+
* Voyage AI is the embedding provider recommended by Anthropic for use with
|
|
20
|
+
* Claude. Get a free key at https://dash.voyageai.com.
|
|
18
21
|
*
|
|
19
22
|
* If a required key is missing, the process exits immediately.
|
|
20
23
|
* If an optional key is missing, a warning is logged but the server continues
|
|
@@ -60,6 +63,14 @@ export const BRAVE_ANSWERS_API_KEY = process.env.BRAVE_ANSWERS_API_KEY;
|
|
|
60
63
|
if (!BRAVE_ANSWERS_API_KEY) {
|
|
61
64
|
console.error("Warning: BRAVE_ANSWERS_API_KEY environment variable is missing. Brave Answers tool will be unavailable.");
|
|
62
65
|
}
|
|
66
|
+
// ─── Optional: Voyage AI API Key ──────────────────────────────
|
|
67
|
+
// Used when embedding_provider = "voyage" in the dashboard.
|
|
68
|
+
// Voyage AI is the embedding provider recommended by Anthropic for use
|
|
69
|
+
// alongside Claude. voyage-3 supports 768-dim output via MRL truncation,
|
|
70
|
+
// matching Prism's storage schema for zero-migration drop-in replacement.
|
|
71
|
+
// Without this, VoyageAdapter construction will throw at server start if
|
|
72
|
+
// embedding_provider=voyage is selected.
|
|
73
|
+
export const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY;
|
|
63
74
|
// ─── v2.0: Storage Backend Selection ─────────────────────────
|
|
64
75
|
// REVIEWER NOTE: Step 1 of v2.0 introduces a storage abstraction.
|
|
65
76
|
// Currently only "supabase" is implemented. "local" (SQLite) is
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function computeIntentHealth(ctx, staleThresholdDays = 30, nowMs = Date.now()) {
|
|
2
|
+
if (!Number.isFinite(staleThresholdDays) || staleThresholdDays <= 0)
|
|
3
|
+
staleThresholdDays = 30; // Guard against NaN / zero / negative
|
|
4
|
+
// 2. Compute staleness (Days since last session)
|
|
5
|
+
let stalenessDays = 0;
|
|
6
|
+
if (ctx.recent_sessions && ctx.recent_sessions.length > 0) {
|
|
7
|
+
const lastTimestamp = ctx.recent_sessions[0].created_at ? new Date(ctx.recent_sessions[0].created_at).getTime() : NaN;
|
|
8
|
+
stalenessDays = isNaN(lastTimestamp) ? 0 : Math.max(0, (nowMs - lastTimestamp) / (1000 * 60 * 60 * 24));
|
|
9
|
+
}
|
|
10
|
+
// 3. Count TODOs and Decisions
|
|
11
|
+
const todoCount = ctx.pending_todo?.length || 0;
|
|
12
|
+
const hasDecisions = ctx.recent_sessions?.some((s) => Array.isArray(s.decisions) && s.decisions.length > 0) ?? false;
|
|
13
|
+
// 4. Execute Intent Debt Scoring Algorithm
|
|
14
|
+
// Staleness (50 points): Linear decay until threshold
|
|
15
|
+
const stalenessScore = Math.max(0, 50 - (stalenessDays / staleThresholdDays) * 50);
|
|
16
|
+
// Open TODOs (30 points)
|
|
17
|
+
let todoScore = 30;
|
|
18
|
+
if (todoCount >= 10)
|
|
19
|
+
todoScore = 0;
|
|
20
|
+
else if (todoCount >= 7)
|
|
21
|
+
todoScore = 5;
|
|
22
|
+
else if (todoCount >= 4)
|
|
23
|
+
todoScore = 15;
|
|
24
|
+
else if (todoCount >= 1)
|
|
25
|
+
todoScore = 25;
|
|
26
|
+
// Decisions (20 points): 70% of max if missing
|
|
27
|
+
const decisionScore = hasDecisions ? 20 : 14;
|
|
28
|
+
const totalScore = Math.min(100, Math.round(stalenessScore + todoScore + decisionScore));
|
|
29
|
+
// 5. Generate Actionable Signals
|
|
30
|
+
const signals = [];
|
|
31
|
+
if (stalenessDays > staleThresholdDays) {
|
|
32
|
+
signals.push({ type: "staleness", message: `Stale: Last updated ${Math.round(stalenessDays)} days ago`, severity: "critical" });
|
|
33
|
+
}
|
|
34
|
+
else if (stalenessDays > staleThresholdDays / 2) {
|
|
35
|
+
signals.push({ type: "staleness", message: `Aging: Last updated ${Math.round(stalenessDays)} days ago`, severity: "warn" });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
signals.push({ type: "staleness", message: `Fresh: Last updated ${Math.round(stalenessDays)} days ago`, severity: "ok" });
|
|
39
|
+
}
|
|
40
|
+
if (todoCount >= 10) {
|
|
41
|
+
signals.push({ type: "todos", message: `${todoCount} TODOs pending (Overwhelming)`, severity: "critical" });
|
|
42
|
+
}
|
|
43
|
+
else if (todoCount >= 4) {
|
|
44
|
+
signals.push({ type: "todos", message: `${todoCount} TODOs pending`, severity: "warn" });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
signals.push({ type: "todos", message: `${todoCount} TODOs pending`, severity: "ok" });
|
|
48
|
+
}
|
|
49
|
+
if (hasDecisions) {
|
|
50
|
+
signals.push({ type: "decisions", message: "Active decisions captured", severity: "ok" });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
signals.push({ type: "decisions", message: "No active decisions documented", severity: "warn" });
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
score: totalScore,
|
|
57
|
+
staleness_days: Math.round(stalenessDays),
|
|
58
|
+
open_todo_count: todoCount,
|
|
59
|
+
has_active_decisions: hasDecisions,
|
|
60
|
+
signals: signals
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -23,7 +23,8 @@ import * as fs from "fs";
|
|
|
23
23
|
import { getStorage } from "../storage/index.js";
|
|
24
24
|
import { PRISM_USER_ID, SERVER_CONFIG } from "../config.js";
|
|
25
25
|
import { renderDashboardHTML } from "./ui.js";
|
|
26
|
-
import {
|
|
26
|
+
import { computeIntentHealth } from "./intentHealth.js";
|
|
27
|
+
import { getAllSettings, setSetting, getSetting, getSettingSync } from "../storage/configStorage.js";
|
|
27
28
|
import { compactLedgerHandler } from "../tools/compactionHandler.js";
|
|
28
29
|
import { getLLMProvider } from "../utils/llm/factory.js";
|
|
29
30
|
import { buildVaultDirectory } from "../utils/vaultExporter.js";
|
|
@@ -1095,6 +1096,39 @@ self.addEventListener('message', (e) => {
|
|
|
1095
1096
|
});
|
|
1096
1097
|
return res.end(Buffer.from(pngBase64, "base64"));
|
|
1097
1098
|
}
|
|
1099
|
+
// ─── API: Intent Health (Feature A) ───
|
|
1100
|
+
// GET /api/intent-health?project=xxx
|
|
1101
|
+
if (url.pathname === "/api/intent-health" && req.method === "GET") {
|
|
1102
|
+
const project = url.searchParams.get("project");
|
|
1103
|
+
if (!project) {
|
|
1104
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1105
|
+
return res.end(JSON.stringify({ error: "project is required" }));
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
const storage = await getStorageSafe();
|
|
1109
|
+
if (!storage) {
|
|
1110
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
1111
|
+
return res.end(JSON.stringify({ error: "Storage initializing..." }));
|
|
1112
|
+
}
|
|
1113
|
+
// Load 'standard' context to get active_decisions and recent_sessions
|
|
1114
|
+
// Note: API MUST use "standard" level (deep skips recent_sessions, which breaks staleness).
|
|
1115
|
+
const ctx = await storage.loadContext(project, "standard", PRISM_USER_ID);
|
|
1116
|
+
if (!ctx) {
|
|
1117
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1118
|
+
return res.end(JSON.stringify({ error: "Project not found" }));
|
|
1119
|
+
}
|
|
1120
|
+
// 1. Fetch configurable threshold (default 30 days, avoiding 7-day panic)
|
|
1121
|
+
const staleThresholdDays = parseInt(getSettingSync("intent_health_stale_threshold_days", "30"), 10) || 30;
|
|
1122
|
+
const healthResult = computeIntentHealth(ctx, staleThresholdDays);
|
|
1123
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1124
|
+
return res.end(JSON.stringify(healthResult));
|
|
1125
|
+
}
|
|
1126
|
+
catch (err) {
|
|
1127
|
+
console.error("[intent-health] Error computing intent health:", err);
|
|
1128
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1129
|
+
return res.end(JSON.stringify({ error: "Failed to compute intent health" }));
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1098
1132
|
// ─── 404 ───
|
|
1099
1133
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1100
1134
|
res.end("Not found");
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -625,6 +625,15 @@ export function renderDashboardHTML(version) {
|
|
|
625
625
|
<ul class="todo-list" id="todos"></ul>
|
|
626
626
|
</div>
|
|
627
627
|
|
|
628
|
+
<!-- Intent Health (Feature A) -->
|
|
629
|
+
<div class="card" id="intentHealthCard" style="display: none;">
|
|
630
|
+
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center;">
|
|
631
|
+
<div><span class="dot" style="background:var(--accent-purple)"></span> Intent Health</div>
|
|
632
|
+
<span style="font-size: 11px; color: var(--text-muted); cursor: help; border-bottom: 1px dotted var(--text-muted); letter-spacing: normal; text-transform: none;" title="Intent debt limits how AI agents can evolve the system (Storey / Fowler)">What is this?</span>
|
|
633
|
+
</div>
|
|
634
|
+
<div id="intentHealthCardContent"></div>
|
|
635
|
+
</div>
|
|
636
|
+
|
|
628
637
|
<!-- Git Metadata -->
|
|
629
638
|
<div class="card">
|
|
630
639
|
<div class="card-title"><span class="dot" style="background:var(--accent-green)"></span> Git Metadata</div>
|
|
@@ -1465,15 +1474,15 @@ function loadPipelines() {
|
|
|
1465
1474
|
html += '<span style="font-weight:600;color:var(--text-primary)">' + emoji + ' ' + p.status + '</span>';
|
|
1466
1475
|
html += '<span style="font-size:0.7rem;font-family:var(--font-mono);color:var(--text-muted)">' + p.id.slice(0, 8) + '…</span>';
|
|
1467
1476
|
html += '</div>';
|
|
1468
|
-
html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + objective + '</div>';
|
|
1477
|
+
html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + escapeHtml(objective) + '</div>';
|
|
1469
1478
|
html += '<div style="display:flex;gap:1rem;font-size:0.72rem;color:var(--text-muted);flex-wrap:wrap">';
|
|
1470
|
-
html += '<span>📁 ' + (p.project || '?') + '</span>';
|
|
1479
|
+
html += '<span>📁 ' + escapeHtml(p.project || '?') + '</span>';
|
|
1471
1480
|
html += '<span>🔄 ' + p.iteration + ' / ' + maxIter + '</span>';
|
|
1472
|
-
html += '<span>📍 ' + (p.current_step || '?') + '</span>';
|
|
1481
|
+
html += '<span>📍 ' + escapeHtml(p.current_step || '?') + '</span>';
|
|
1473
1482
|
html += '<span>🕐 ' + new Date(p.updated_at).toLocaleString() + '</span>';
|
|
1474
1483
|
html += '</div>';
|
|
1475
1484
|
if (p.error) {
|
|
1476
|
-
html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + p.error.slice(0, 200) + '</div>';
|
|
1485
|
+
html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + escapeHtml(p.error.slice(0, 200)) + '</div>';
|
|
1477
1486
|
}
|
|
1478
1487
|
if (isActive) {
|
|
1479
1488
|
html += '<div style="margin-top:0.5rem"><button onclick="abortPipeline(this.dataset.id)" data-id="' + p.id + '" class="cleanup-btn" style="font-size:0.72rem">🛑 Abort Pipeline</button></div>';
|
|
@@ -1495,7 +1504,7 @@ function loadPipelines() {
|
|
|
1495
1504
|
}
|
|
1496
1505
|
})
|
|
1497
1506
|
.catch(function (err) {
|
|
1498
|
-
document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + err.message + '</div>';
|
|
1507
|
+
document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + escapeHtml(err.message) + '</div>';
|
|
1499
1508
|
});
|
|
1500
1509
|
}
|
|
1501
1510
|
function abortPipeline(id) {
|
|
@@ -1672,11 +1681,36 @@ function loadIdentityChip() {
|
|
|
1672
1681
|
select = document.getElementById('projectSelect');
|
|
1673
1682
|
if (data.projects && data.projects.length > 0) {
|
|
1674
1683
|
select.innerHTML = '<option value="">— Select a project —</option>' +
|
|
1675
|
-
data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
|
|
1684
|
+
data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
|
|
1685
|
+
|
|
1686
|
+
// v7.5.0: Background fetch intent health to add global traffic-light dots
|
|
1687
|
+
// Fetch sequentially to prevent SQLite WAL saturation with N concurrent loadContext calls
|
|
1688
|
+
var pIndex = 0;
|
|
1689
|
+
function fetchNextHealth() {
|
|
1690
|
+
if (pIndex >= data.projects.length) return;
|
|
1691
|
+
var p = data.projects[pIndex++];
|
|
1692
|
+
fetch('/api/intent-health?project=' + encodeURIComponent(p))
|
|
1693
|
+
.then(function (res) { return res.json(); })
|
|
1694
|
+
.then(function (hData) {
|
|
1695
|
+
if (hData && typeof hData.score === 'number') {
|
|
1696
|
+
var icon = hData.score >= 80 ? "🟢" : (hData.score >= 50 ? "🟡" : "🔴");
|
|
1697
|
+
var opt = null;
|
|
1698
|
+
for (var oi = 0; oi < select.options.length; oi++) {
|
|
1699
|
+
if (select.options[oi].value === p) { opt = select.options[oi]; break; }
|
|
1700
|
+
}
|
|
1701
|
+
if (opt) opt.textContent = icon + " " + p;
|
|
1702
|
+
}
|
|
1703
|
+
fetchNextHealth();
|
|
1704
|
+
}).catch(function () { fetchNextHealth(); });
|
|
1705
|
+
}
|
|
1706
|
+
if (data.projects && data.projects.length > 0) {
|
|
1707
|
+
fetchNextHealth();
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1676
1710
|
gp = document.getElementById('graphProjectFilter');
|
|
1677
1711
|
if (gp) {
|
|
1678
1712
|
gp.innerHTML = '<option value="">All Projects</option>' +
|
|
1679
|
-
data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
|
|
1713
|
+
data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
|
|
1680
1714
|
}
|
|
1681
1715
|
// Restore last selected project from localStorage
|
|
1682
1716
|
var lastProject = localStorage.getItem('prism_last_project');
|
|
@@ -1708,8 +1742,15 @@ function loadProject() {
|
|
|
1708
1742
|
switch (_a.label) {
|
|
1709
1743
|
case 0:
|
|
1710
1744
|
project = document.getElementById('projectSelect').value;
|
|
1711
|
-
if (!project)
|
|
1745
|
+
if (!project) {
|
|
1746
|
+
var hc = document.getElementById('intentHealthCard');
|
|
1747
|
+
if (hc) hc.style.display = 'none';
|
|
1748
|
+
var welcomeEl = document.getElementById('welcome');
|
|
1749
|
+
var contentEl = document.getElementById('content');
|
|
1750
|
+
if (contentEl) contentEl.style.display = 'none';
|
|
1751
|
+
if (welcomeEl) welcomeEl.style.display = 'flex';
|
|
1712
1752
|
return [2 /*return*/];
|
|
1753
|
+
}
|
|
1713
1754
|
// Persist last selected project
|
|
1714
1755
|
try { localStorage.setItem('prism_last_project', project); } catch(e) {}
|
|
1715
1756
|
document.getElementById('welcome').style.display = 'none';
|
|
@@ -1767,7 +1808,7 @@ function loadProject() {
|
|
|
1767
1808
|
var snap = h.snapshot || {};
|
|
1768
1809
|
var summary = snap.last_summary || snap.summary || 'Snapshot';
|
|
1769
1810
|
return '<div class="timeline-item history">' +
|
|
1770
|
-
'<div class="meta"><span class="badge badge-purple">v' + h.version + '</span>' +
|
|
1811
|
+
'<div class="meta"><span class="badge badge-purple">v' + escapeHtml(h.version) + '</span>' +
|
|
1771
1812
|
'<span>' + formatDate(h.created_at) + '</span></div>' +
|
|
1772
1813
|
escapeHtml(summary) + '</div>';
|
|
1773
1814
|
}).join('');
|
|
@@ -1785,7 +1826,7 @@ function loadProject() {
|
|
|
1785
1826
|
try {
|
|
1786
1827
|
var parsed = typeof decisions === 'string' ? JSON.parse(decisions) : decisions;
|
|
1787
1828
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
1788
|
-
extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.join(', ') + '</div>';
|
|
1829
|
+
extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.map(function(d) { return escapeHtml(d); }).join(', ') + '</div>';
|
|
1789
1830
|
}
|
|
1790
1831
|
}
|
|
1791
1832
|
catch (e) { }
|
|
@@ -1851,6 +1892,10 @@ function loadProject() {
|
|
|
1851
1892
|
document.getElementById('content').className = 'grid grid-main fade-in';
|
|
1852
1893
|
document.getElementById('content').style.display = 'grid';
|
|
1853
1894
|
projectLoaded = true; // Fix 3: mark project as loaded for tab-switch restore
|
|
1895
|
+
|
|
1896
|
+
// Feature A: Run Intent Health Fetch
|
|
1897
|
+
fetchIntentHealth(project);
|
|
1898
|
+
|
|
1854
1899
|
// v3.1: Analytics + Lifecycle Controls + Import
|
|
1855
1900
|
document.getElementById('analyticsCard').style.display = 'block';
|
|
1856
1901
|
document.getElementById('lifecycleCard').style.display = 'block';
|
|
@@ -2393,7 +2438,7 @@ function showToast(msg, isErr) {
|
|
|
2393
2438
|
function escapeHtml(str) {
|
|
2394
2439
|
if (!str)
|
|
2395
2440
|
return '';
|
|
2396
|
-
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
2441
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
2397
2442
|
}
|
|
2398
2443
|
function formatDate(isoStr) {
|
|
2399
2444
|
if (!isoStr)
|
|
@@ -4060,6 +4105,67 @@ if ('serviceWorker' in navigator) {
|
|
|
4060
4105
|
});
|
|
4061
4106
|
}
|
|
4062
4107
|
|
|
4108
|
+
/* --- INTENT HEALTH COMPONENT (ES5 Safe) --- */
|
|
4109
|
+
|
|
4110
|
+
function fetchIntentHealth(project) {
|
|
4111
|
+
var container = document.getElementById('intentHealthCardContent');
|
|
4112
|
+
var card = document.getElementById('intentHealthCard');
|
|
4113
|
+
if (!container || !card) return;
|
|
4114
|
+
|
|
4115
|
+
card.style.display = 'block';
|
|
4116
|
+
container.innerHTML = '<div style="color: #8b949e; padding: 16px;">Computing intent debt...</div>';
|
|
4117
|
+
|
|
4118
|
+
fetch('/api/intent-health?project=' + encodeURIComponent(project))
|
|
4119
|
+
.then(function(res) { return res.json(); })
|
|
4120
|
+
.then(function(data) {
|
|
4121
|
+
if (data.error) {
|
|
4122
|
+
container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Error: ' + escapeHtml(data.error) + '</div>';
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
renderIntentHealthCard(data);
|
|
4126
|
+
})
|
|
4127
|
+
.catch(function(err) {
|
|
4128
|
+
container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Failed to load intent health.</div>';
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
function renderIntentHealthCard(data) {
|
|
4133
|
+
var container = document.getElementById('intentHealthCardContent');
|
|
4134
|
+
if (!container) return;
|
|
4135
|
+
|
|
4136
|
+
// Determine Gauge Color
|
|
4137
|
+
var scoreColor = '#2ea043'; // Green
|
|
4138
|
+
if (data.score < 50) {
|
|
4139
|
+
scoreColor = '#ff7b72'; // Red
|
|
4140
|
+
} else if (data.score < 80) {
|
|
4141
|
+
scoreColor = '#d29922'; // Yellow
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
// Render Signals
|
|
4145
|
+
var signalsHtml = '';
|
|
4146
|
+
for (var i = 0; i < data.signals.length; i++) {
|
|
4147
|
+
var sig = data.signals[i];
|
|
4148
|
+
var icon = '🟢';
|
|
4149
|
+
if (sig.severity === 'warn') icon = '🟡';
|
|
4150
|
+
if (sig.severity === 'critical') icon = '🔴';
|
|
4151
|
+
signalsHtml += '<div style="margin-bottom: 8px; font-size: 13px;">' + icon + ' ' + escapeHtml(sig.message) + '</div>';
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
// Render Layout
|
|
4155
|
+
var html = '' +
|
|
4156
|
+
'<div style="display: flex; gap: 24px; align-items: center; padding: 16px;">' +
|
|
4157
|
+
'<div style="text-align: center; flex-shrink: 0; min-width: 80px;">' +
|
|
4158
|
+
'<div style="font-size: 42px; font-weight: bold; color: ' + scoreColor + '; line-height: 1;">' + (typeof data.score === 'number' ? data.score : '—') + '</div>' +
|
|
4159
|
+
'<div style="font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; margin-top: 8px;">Health Score</div>' +
|
|
4160
|
+
'</div>' +
|
|
4161
|
+
'<div style="flex-grow: 1; border-left: 1px solid var(--border-glass); padding-left: 24px;">' +
|
|
4162
|
+
signalsHtml +
|
|
4163
|
+
'</div>' +
|
|
4164
|
+
'</div>';
|
|
4165
|
+
|
|
4166
|
+
container.innerHTML = html;
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4063
4169
|
</script>
|
|
4064
4170
|
</body>
|
|
4065
4171
|
</html>`;
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -1184,6 +1184,30 @@ export class SqliteStorage {
|
|
|
1184
1184
|
importance: r.importance,
|
|
1185
1185
|
}));
|
|
1186
1186
|
}
|
|
1187
|
+
// ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
|
|
1188
|
+
context.recent_validations = []; // Default empty
|
|
1189
|
+
try {
|
|
1190
|
+
const validationResult = await this.db.execute({
|
|
1191
|
+
sql: `SELECT run_at, passed, pass_rate, gate_action, critical_failures
|
|
1192
|
+
FROM verification_runs
|
|
1193
|
+
WHERE project = ? AND user_id = ?
|
|
1194
|
+
ORDER BY run_at DESC
|
|
1195
|
+
LIMIT 3`,
|
|
1196
|
+
args: [project, userId],
|
|
1197
|
+
});
|
|
1198
|
+
if (validationResult.rows.length > 0) {
|
|
1199
|
+
context.recent_validations = validationResult.rows.map(r => ({
|
|
1200
|
+
run_at: r.run_at,
|
|
1201
|
+
passed: Boolean(r.passed),
|
|
1202
|
+
pass_rate: r.pass_rate,
|
|
1203
|
+
gate_action: r.gate_action,
|
|
1204
|
+
critical_failures: Number(r.critical_failures) || 0,
|
|
1205
|
+
}));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
catch (e) {
|
|
1209
|
+
// Graceful degradation if verification_runs table hasn't been migrated yet
|
|
1210
|
+
}
|
|
1187
1211
|
if (level === "standard") {
|
|
1188
1212
|
// Add recent ledger entries (role-scoped)
|
|
1189
1213
|
const recentLedger = await this.db.execute({
|
package/dist/storage/supabase.js
CHANGED
|
@@ -170,7 +170,36 @@ export class SupabaseStorage {
|
|
|
170
170
|
p_role: role || "global", // v3.0: pass role to RPC
|
|
171
171
|
});
|
|
172
172
|
const data = Array.isArray(result) ? result[0] : result;
|
|
173
|
-
|
|
173
|
+
const context = data ?? null;
|
|
174
|
+
if (!context)
|
|
175
|
+
return null;
|
|
176
|
+
// ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
|
|
177
|
+
if (level === "standard" || level === "deep") {
|
|
178
|
+
context.recent_validations = [];
|
|
179
|
+
try {
|
|
180
|
+
const valData = await supabaseGet("verification_runs", {
|
|
181
|
+
project: `eq.${project}`,
|
|
182
|
+
user_id: `eq.${userId}`,
|
|
183
|
+
select: "run_at,passed,pass_rate,gate_action,critical_failures",
|
|
184
|
+
order: "run_at.desc",
|
|
185
|
+
limit: "3"
|
|
186
|
+
});
|
|
187
|
+
const validations = Array.isArray(valData) ? valData : [];
|
|
188
|
+
if (validations.length > 0) {
|
|
189
|
+
context.recent_validations = validations.map((r) => ({
|
|
190
|
+
run_at: r.run_at,
|
|
191
|
+
passed: Boolean(r.passed),
|
|
192
|
+
pass_rate: r.pass_rate,
|
|
193
|
+
gate_action: r.gate_action,
|
|
194
|
+
critical_failures: Number(r.critical_failures) || 0,
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
// Graceful degradation if table doesn't exist yet
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return context;
|
|
174
203
|
}
|
|
175
204
|
catch (e) {
|
|
176
205
|
debugLog("[SupabaseStorage] loadContext RPC failed: " + (e instanceof Error ? e.message : String(e)));
|
|
@@ -657,6 +657,19 @@ export async function sessionLoadContextHandler(args) {
|
|
|
657
657
|
if (d.session_history?.length) {
|
|
658
658
|
formattedContext += `\n📂 Session History (${d.session_history.length} entries):\n` + d.session_history.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
|
|
659
659
|
}
|
|
660
|
+
if (d.recent_validations?.length) {
|
|
661
|
+
formattedContext += `\n🔬 Recent Validations:\n` + d.recent_validations.map((v) => {
|
|
662
|
+
const passStr = v.passed ? "✅ PASS" : "❌ FAIL";
|
|
663
|
+
const icon = v.gate_action ? (v.passed ? "🚀" : "🛑") : "ℹ️";
|
|
664
|
+
const dateStr = (v.run_at || "unknown").split("T")[0];
|
|
665
|
+
const rateStr = Math.round((Number(v.pass_rate) || 0) * 100);
|
|
666
|
+
let out = ` ${icon} [${dateStr}] ${passStr} (${rateStr}%)`;
|
|
667
|
+
if (v.critical_failures > 0) {
|
|
668
|
+
out += ` -> Critical Blockers: ${v.critical_failures}`;
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}).join("\n") + `\n`;
|
|
672
|
+
}
|
|
660
673
|
// ─── Role-Scoped Skill Injection ─────────────────────────────
|
|
661
674
|
// If the active role has a skill document stored, append it so the
|
|
662
675
|
// agent loads its rules/conventions automatically at session start.
|
|
@@ -85,8 +85,11 @@ export class AnthropicAdapter {
|
|
|
85
85
|
// silent zero-vector or crash.
|
|
86
86
|
throw new Error("AnthropicAdapter does not support text embeddings. " +
|
|
87
87
|
"Anthropic has no native embedding API. " +
|
|
88
|
-
"
|
|
89
|
-
"
|
|
88
|
+
"Their official recommendation is Voyage AI (voyage-3, voyage-3-lite). " +
|
|
89
|
+
"In the Mind Palace dashboard, set 'Embedding Provider' to: " +
|
|
90
|
+
"'voyage' (Anthropic-recommended, set VOYAGE_API_KEY), " +
|
|
91
|
+
"'openai' (OpenAI cloud or local Ollama with nomic-embed-text), " +
|
|
92
|
+
"or 'gemini' (Google AI, set GOOGLE_API_KEY).");
|
|
90
93
|
}
|
|
91
94
|
// ─── Image Description (VLM) ─────────────────────────────────────────────
|
|
92
95
|
/**
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voyage AI Adapter (v1.0)
|
|
3
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
* PURPOSE:
|
|
5
|
+
* Implements LLMProvider using Voyage AI's REST API for text embeddings.
|
|
6
|
+
* Voyage AI is the embedding provider officially recommended by Anthropic
|
|
7
|
+
* for use alongside Claude — it fills the gap left by Anthropic's lack
|
|
8
|
+
* of a native embedding API.
|
|
9
|
+
*
|
|
10
|
+
* TEXT GENERATION:
|
|
11
|
+
* Voyage AI is an embeddings-only service. generateText() throws an explicit
|
|
12
|
+
* error, the same pattern used by AnthropicAdapter.generateEmbedding().
|
|
13
|
+
* Set text_provider separately (anthropic, openai, or gemini).
|
|
14
|
+
*
|
|
15
|
+
* EMBEDDING DIMENSION PARITY (768 dims):
|
|
16
|
+
* Prism's SQLite (sqlite-vec) and Supabase (pgvector) schemas define
|
|
17
|
+
* embedding columns as EXACTLY 768 dimensions.
|
|
18
|
+
*
|
|
19
|
+
* Voyage solution: voyage-3 and voyage-3-lite output 1024 dims by default,
|
|
20
|
+
* but both support the `output_dimension` parameter (Matryoshka Representation
|
|
21
|
+
* Learning), enabling truncation to 768 while preserving quality.
|
|
22
|
+
* voyage-3-lite at 768 dims is the fastest and most cost-efficient option.
|
|
23
|
+
*
|
|
24
|
+
* MODELS:
|
|
25
|
+
* voyage-3 — Highest quality, 1024 dims natively (MRL → 768)
|
|
26
|
+
* voyage-3-lite — Fast & cheap, 512 dims natively (MRL → 768 NOT supported)
|
|
27
|
+
* voyage-3-large — Best quality, use for offline indexing
|
|
28
|
+
* voyage-code-3 — Optimised for code (recommended for dev sessions)
|
|
29
|
+
*
|
|
30
|
+
* NOTE: voyage-3-lite natively outputs 512 dims; it does NOT support
|
|
31
|
+
* output_dimension truncation to 768. Use voyage-3 for dimension parity.
|
|
32
|
+
* Default is voyage-3 for this reason.
|
|
33
|
+
*
|
|
34
|
+
* CONFIG KEYS (Prism dashboard "AI Providers" tab OR environment variables):
|
|
35
|
+
* voyage_api_key — Required. Voyage AI API key (pa-...)
|
|
36
|
+
* voyage_model — Embedding model (default: voyage-3)
|
|
37
|
+
*
|
|
38
|
+
* USAGE WITH ANTHROPIC TEXT PROVIDER:
|
|
39
|
+
* Set text_provider=anthropic, embedding_provider=voyage in the dashboard.
|
|
40
|
+
* This pairs Claude for reasoning with Voyage for semantic memory — the
|
|
41
|
+
* combination Anthropic recommends in their documentation.
|
|
42
|
+
*
|
|
43
|
+
* API REFERENCE:
|
|
44
|
+
* https://docs.voyageai.com/reference/embeddings-api
|
|
45
|
+
*/
|
|
46
|
+
import { getSettingSync } from "../../../storage/configStorage.js";
|
|
47
|
+
import { debugLog } from "../../logger.js";
|
|
48
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
49
|
+
// Must match Prism's DB schema (sqlite-vec and pgvector column sizes).
|
|
50
|
+
const EMBEDDING_DIMS = 768;
|
|
51
|
+
// voyage-3 supports up to 32,000 tokens. Character-based cap (consistent
|
|
52
|
+
// with OpenAI and Gemini adapters) avoids tokenizer dependency.
|
|
53
|
+
// 8000 chars ≈ 1500-2000 tokens for typical session summaries.
|
|
54
|
+
const MAX_EMBEDDING_CHARS = 8000;
|
|
55
|
+
// Default model: voyage-3 (supports output_dimension=768 via MRL)
|
|
56
|
+
// voyage-3-lite is NOT recommended as its native 512 dims < 768.
|
|
57
|
+
const DEFAULT_MODEL = "voyage-3";
|
|
58
|
+
const VOYAGE_API_BASE = "https://api.voyageai.com/v1";
|
|
59
|
+
// ─── Adapter ─────────────────────────────────────────────────────────────────
|
|
60
|
+
export class VoyageAdapter {
|
|
61
|
+
apiKey;
|
|
62
|
+
constructor() {
|
|
63
|
+
const apiKey = getSettingSync("voyage_api_key", process.env.VOYAGE_API_KEY ?? "");
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
throw new Error("VoyageAdapter requires a Voyage AI API key. " +
|
|
66
|
+
"Get one free at https://dash.voyageai.com — then set VOYAGE_API_KEY " +
|
|
67
|
+
"or configure it in the Mind Palace dashboard under 'AI Providers'.");
|
|
68
|
+
}
|
|
69
|
+
this.apiKey = apiKey;
|
|
70
|
+
debugLog("[VoyageAdapter] Initialized");
|
|
71
|
+
}
|
|
72
|
+
// ─── Text Generation (Not Supported) ────────────────────────────────────
|
|
73
|
+
async generateText(_prompt, _systemInstruction) {
|
|
74
|
+
// Voyage AI is an embeddings-only service.
|
|
75
|
+
// Use text_provider=anthropic, openai, or gemini for text generation.
|
|
76
|
+
throw new Error("VoyageAdapter does not support text generation. " +
|
|
77
|
+
"Voyage AI is an embeddings-only service. " +
|
|
78
|
+
"Set text_provider to 'anthropic', 'openai', or 'gemini' in the dashboard.");
|
|
79
|
+
}
|
|
80
|
+
// ─── Embedding Generation ────────────────────────────────────────────────
|
|
81
|
+
async generateEmbedding(text) {
|
|
82
|
+
if (!text || !text.trim()) {
|
|
83
|
+
throw new Error("[VoyageAdapter] generateEmbedding called with empty text");
|
|
84
|
+
}
|
|
85
|
+
// Truncate to character limit (consistent with other adapters)
|
|
86
|
+
const truncated = text.length > MAX_EMBEDDING_CHARS
|
|
87
|
+
? text.slice(0, MAX_EMBEDDING_CHARS).replace(/\s+\S*$/, "")
|
|
88
|
+
: text;
|
|
89
|
+
const model = getSettingSync("voyage_model", DEFAULT_MODEL);
|
|
90
|
+
debugLog(`[VoyageAdapter] generateEmbedding — model=${model}, chars=${truncated.length}`);
|
|
91
|
+
const requestBody = {
|
|
92
|
+
input: [truncated],
|
|
93
|
+
model,
|
|
94
|
+
// Request exactly 768 dims via Matryoshka truncation.
|
|
95
|
+
// Supported by voyage-3, voyage-3-large, voyage-code-3.
|
|
96
|
+
// voyage-3-lite (native 512 dims) will ignore this and return 512,
|
|
97
|
+
// which will be caught by the dimension guard below.
|
|
98
|
+
output_dimension: EMBEDDING_DIMS,
|
|
99
|
+
};
|
|
100
|
+
const response = await fetch(`${VOYAGE_API_BASE}/embeddings`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify(requestBody),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const errorText = await response.text().catch(() => "unknown error");
|
|
110
|
+
throw new Error(`[VoyageAdapter] API request failed — status=${response.status}: ${errorText}`);
|
|
111
|
+
}
|
|
112
|
+
const data = (await response.json());
|
|
113
|
+
const embedding = data?.data?.[0]?.embedding;
|
|
114
|
+
if (!Array.isArray(embedding)) {
|
|
115
|
+
throw new Error("[VoyageAdapter] Unexpected response format — no embedding array found");
|
|
116
|
+
}
|
|
117
|
+
// Dimension guard: Prism's DB schema requires exactly 768 dims.
|
|
118
|
+
// This catches voyage-3-lite (512) or future API changes silently early.
|
|
119
|
+
if (embedding.length !== EMBEDDING_DIMS) {
|
|
120
|
+
throw new Error(`[VoyageAdapter] Embedding dimension mismatch: expected ${EMBEDDING_DIMS}, ` +
|
|
121
|
+
`got ${embedding.length}. ` +
|
|
122
|
+
`Use voyage-3 (not voyage-3-lite) to get 768-dim output via MRL truncation. ` +
|
|
123
|
+
`Change voyage_model in the Mind Palace dashboard.`);
|
|
124
|
+
}
|
|
125
|
+
debugLog(`[VoyageAdapter] Embedding generated — dims=${embedding.length}, ` +
|
|
126
|
+
`tokens_used=${data.usage?.total_tokens ?? "unknown"}`);
|
|
127
|
+
return embedding;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LLM Provider Factory (v4.
|
|
2
|
+
* LLM Provider Factory (v4.5 — Voyage AI Embedding Support)
|
|
3
3
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
4
4
|
* PURPOSE:
|
|
5
5
|
* Single point of resolution for the active LLMProvider.
|
|
@@ -11,19 +11,21 @@
|
|
|
11
11
|
* Two independent settings control text and embedding routing:
|
|
12
12
|
*
|
|
13
13
|
* text_provider — "gemini" (default) | "openai" | "anthropic"
|
|
14
|
-
* embedding_provider — "auto" (default) | "gemini" | "openai"
|
|
14
|
+
* embedding_provider — "auto" (default) | "gemini" | "openai" | "voyage"
|
|
15
15
|
*
|
|
16
16
|
* When embedding_provider = "auto":
|
|
17
17
|
* * If text_provider is gemini or openai → use same provider for embeddings
|
|
18
18
|
* * If text_provider is anthropic → auto-fallback to gemini for embeddings
|
|
19
|
-
* (Anthropic has no native embedding API
|
|
19
|
+
* (Anthropic has no native embedding API; consider setting
|
|
20
|
+
* embedding_provider=voyage for the Anthropic-recommended pairing)
|
|
20
21
|
*
|
|
21
22
|
* EXAMPLE CONFIGURATIONS:
|
|
22
23
|
* text_provider=gemini, embedding_provider=auto → Gemini+Gemini (default)
|
|
23
24
|
* text_provider=openai, embedding_provider=auto → OpenAI+OpenAI
|
|
24
25
|
* text_provider=anthropic, embedding_provider=auto → Claude+Gemini (auto-bridge)
|
|
26
|
+
* text_provider=anthropic, embedding_provider=voyage → Claude+Voyage (Anthropic-recommended)
|
|
25
27
|
* text_provider=anthropic, embedding_provider=openai → Claude+Ollama (cost-optimized)
|
|
26
|
-
* text_provider=gemini, embedding_provider=
|
|
28
|
+
* text_provider=gemini, embedding_provider=voyage → Gemini+Voyage (mixed)
|
|
27
29
|
*
|
|
28
30
|
* SINGLETON + GRACEFUL DEGRADATION:
|
|
29
31
|
* Same as before — instance cached per process, errors fall back to Gemini.
|
|
@@ -41,6 +43,7 @@ import { getSettingSync } from "../../storage/configStorage.js";
|
|
|
41
43
|
import { GeminiAdapter } from "./adapters/gemini.js";
|
|
42
44
|
import { OpenAIAdapter } from "./adapters/openai.js";
|
|
43
45
|
import { AnthropicAdapter } from "./adapters/anthropic.js";
|
|
46
|
+
import { VoyageAdapter } from "./adapters/voyage.js";
|
|
44
47
|
import { TracingLLMProvider } from "./adapters/traced.js";
|
|
45
48
|
// Module-level singleton — one composed provider per MCP server process.
|
|
46
49
|
let providerInstance = null;
|
|
@@ -59,8 +62,10 @@ function buildEmbeddingAdapter(type) {
|
|
|
59
62
|
// Note: "anthropic" is intentionally absent from this switch.
|
|
60
63
|
// Anthropic has no embedding API, so it can never be an embedding provider.
|
|
61
64
|
// The factory resolves "auto" away from "anthropic" before calling this.
|
|
65
|
+
// For Anthropic text users, "voyage" is the Anthropic-recommended pairing.
|
|
62
66
|
switch (type) {
|
|
63
67
|
case "openai": return new OpenAIAdapter();
|
|
68
|
+
case "voyage": return new VoyageAdapter();
|
|
64
69
|
case "gemini":
|
|
65
70
|
default: return new GeminiAdapter();
|
|
66
71
|
}
|
|
@@ -90,7 +95,9 @@ export function getLLMProvider() {
|
|
|
90
95
|
if (textType === "anthropic") {
|
|
91
96
|
console.info("[LLMFactory] text_provider=anthropic with embedding_provider=auto: " +
|
|
92
97
|
"routing embeddings to GeminiAdapter (Anthropic has no native embedding API). " +
|
|
93
|
-
"
|
|
98
|
+
"For the Anthropic-recommended pairing, set embedding_provider=voyage in the dashboard " +
|
|
99
|
+
"(voyage-3 supports 768-dim output via MRL). " +
|
|
100
|
+
"Alternatively, set embedding_provider=openai to use Ollama/OpenAI.");
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-mcp",
|
|
5
5
|
"description": "The Mind Palace for AI Agents — adversarial evaluation (PLAN_CONTRACT→EVALUATE anti-sycophancy), fail-closed Dark Factory autonomous pipelines (3-gate parse→type→scope), persistent memory (SQLite/Supabase), ACT-R cognitive retrieval, behavioral learning & IDE rules sync, multi-agent Hivemind, time travel, visual dashboard. Zero-config local mode.",
|
|
6
6
|
"module": "index.ts",
|