prism-mcp-server 2.5.1 → 3.0.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 +58 -3
- package/dist/config.js +8 -0
- package/dist/dashboard/server.js +96 -32
- package/dist/dashboard/ui.js +322 -1
- package/dist/server.js +35 -28
- package/dist/storage/configStorage.js +73 -0
- package/dist/storage/index.js +8 -5
- package/dist/storage/sqlite.js +237 -20
- package/dist/storage/supabase.js +84 -106
- package/dist/tools/agentRegistryDefinitions.js +104 -0
- package/dist/tools/agentRegistryHandlers.js +114 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/sessionMemoryDefinitions.js +12 -0
- package/dist/tools/sessionMemoryHandlers.js +7 -4
- package/dist/utils/googleAi.js +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## Table of Contents
|
|
16
16
|
|
|
17
|
-
- [What's New (
|
|
17
|
+
- [What's New (v3.0.0)](#whats-new-in-v300---agent-hivemind-)
|
|
18
18
|
- [How Prism Compares](#how-prism-compares)
|
|
19
19
|
- [Quick Start](#quick-start-zero-config--local-mode)
|
|
20
20
|
- [Mind Palace Dashboard](#-the-mind-palace-dashboard)
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- [Tool Reference](#tool-reference)
|
|
25
25
|
- [LangChain / LangGraph Integration](#langchain--langgraph-integration)
|
|
26
26
|
- [Environment Variables](#environment-variables)
|
|
27
|
+
- [Boot Settings (Restart Required)](#-boot-settings-restart-required)
|
|
27
28
|
- [Progressive Context Loading](#progressive-context-loading)
|
|
28
29
|
- [Time Travel](#time-travel-version-history)
|
|
29
30
|
- [Agent Telepathy](#agent-telepathy-multi-client-sync)
|
|
@@ -37,7 +38,20 @@
|
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
40
|
-
## What's New in
|
|
41
|
+
## What's New in v3.0.0 — Agent Hivemind 🐝
|
|
42
|
+
|
|
43
|
+
| Feature | Description |
|
|
44
|
+
|---|---|
|
|
45
|
+
| 🐝 **Role-Scoped Memory** | Optional `role` parameter on ledger, handoff, and context loading — each agent role (dev, qa, pm, lead, security, ux) gets its own isolated memory lane within a project. Defaults to `'global'` for full backward compatibility. |
|
|
46
|
+
| 👥 **Agent Registry** | New `agent_register`, `agent_heartbeat`, `agent_list_team` tools — agents announce their presence, pulse their status, and discover who else is working on the team. Stale agents are auto-pruned after 30 minutes. |
|
|
47
|
+
| 🎯 **Team Roster Injection** | When loading context with a role, Prism automatically injects a "Team Roster" showing active teammates, their roles, current tasks, and last heartbeat — true multi-agent awareness without extra tool calls. |
|
|
48
|
+
| ⚙️ **Dashboard Settings** | New Settings modal with runtime toggles (auto-capture, theme, context depth) backed by a persistent `system_settings` key-value store. Environment variables override DB settings for safety. |
|
|
49
|
+
| 📡 **Hivemind Radar** | New dashboard widget showing active agents, their roles (with icons), current tasks, and heartbeat timestamps — a real-time team coordination dashboard. |
|
|
50
|
+
| 🔒 **Conditional Tool Registration** | `PRISM_ENABLE_HIVEMIND` env var gates Hivemind tools — users who don't need multi-agent features keep the same lean tool count as v2.x. |
|
|
51
|
+
| ✅ **Test Suite** | 58 tests across 4 suites (storage, tools, dashboard, load) with Vitest — includes concurrent write stress tests, role isolation verification, and 0.2ms/write performance benchmarks. |
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><strong>What's in v2.5.0 — Enterprise Memory 🏗️</strong></summary>
|
|
41
55
|
|
|
42
56
|
| Feature | Description |
|
|
43
57
|
|---|---|
|
|
@@ -46,6 +60,8 @@
|
|
|
46
60
|
| 🔗 **LangChain Integration (Phase 3)** | `PrismMemoryRetriever` and `PrismKnowledgeRetriever` — async-first `BaseRetriever` subclasses that wrap Prism MCP's traced search endpoints. Trace metadata flows automatically into `Document.metadata["trace"]` for LangSmith visibility. |
|
|
47
61
|
| 🧩 **LangGraph Research Agent** | Full example in `examples/langgraph-agent/` — a 5-node agentic research loop with MCP bridge, persistent memory, and `EnsembleRetriever` hybrid search. |
|
|
48
62
|
|
|
63
|
+
</details>
|
|
64
|
+
|
|
49
65
|
<details>
|
|
50
66
|
<summary><strong>What's in v2.5.1 — Version Sync & Embedding Safety</strong></summary>
|
|
51
67
|
|
|
@@ -528,7 +544,8 @@ The retrievers use `_aget_relevant_documents` as the primary path with `asyncio.
|
|
|
528
544
|
| Variable | Required | Description |
|
|
529
545
|
|----------|----------|-------------|
|
|
530
546
|
| `BRAVE_API_KEY` | No | Brave Search Pro API key (enables web/local search tools) |
|
|
531
|
-
| `PRISM_STORAGE` | No | `"local"` (default) or `"supabase"` |
|
|
547
|
+
| `PRISM_STORAGE` | No | `"local"` (default) or `"supabase"` — **requires restart** |
|
|
548
|
+
| `PRISM_ENABLE_HIVEMIND` | No | Set `"true"` to enable multi-agent Hivemind tools — **requires restart** |
|
|
532
549
|
| `GOOGLE_API_KEY` | No | Google AI / Gemini — enables paper analysis, Morning Briefings, compaction |
|
|
533
550
|
| `BRAVE_ANSWERS_API_KEY` | No | Separate Brave Answers key for AI-grounded answers |
|
|
534
551
|
| `SUPABASE_URL` | If cloud mode | Supabase project URL |
|
|
@@ -540,6 +557,39 @@ The retrievers use `_aget_relevant_documents` as the primary path with `asyncio.
|
|
|
540
557
|
|
|
541
558
|
---
|
|
542
559
|
|
|
560
|
+
## ⚡ Boot Settings (Restart Required)
|
|
561
|
+
|
|
562
|
+
Some settings affect how Prism **initializes at startup** and cannot be changed at runtime. Prism stores these in a lightweight, dedicated SQLite database (`~/.prism-mcp/prism-config.db`) that is read **before** the main storage backend is selected — solving the chicken-and-egg problem of needing config before the config store is ready.
|
|
563
|
+
|
|
564
|
+
> **⚠️ You must restart the Prism MCP server after changing any Boot Setting.** The Mind Palace dashboard labels these with a **"Restart Required"** badge.
|
|
565
|
+
|
|
566
|
+
| Setting | Dashboard Control | Environment Override | Description |
|
|
567
|
+
|---------|------------------|---------------------|-------------|
|
|
568
|
+
| `PRISM_STORAGE` | ⚙️ Storage Backend dropdown | `PRISM_STORAGE=supabase` | Switch between `local` (SQLite) and `supabase` (cloud) |
|
|
569
|
+
| `PRISM_ENABLE_HIVEMIND` | ⚙️ Hivemind Mode toggle | `PRISM_ENABLE_HIVEMIND=true` | Enable/disable multi-agent coordination tools |
|
|
570
|
+
|
|
571
|
+
### How Boot Settings Work
|
|
572
|
+
|
|
573
|
+
1. **Dashboard saves the setting** → written to `~/.prism-mcp/prism-config.db` immediately
|
|
574
|
+
2. **You restart the MCP server** → server reads the config DB at startup, selects backend/features
|
|
575
|
+
3. **Environment variables always win** → if `PRISM_STORAGE` is set in your MCP config JSON, it overrides the dashboard value
|
|
576
|
+
|
|
577
|
+
```
|
|
578
|
+
Priority: env var in MCP config JSON > Dashboard (prism-config.db) > default (local)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Runtime Settings (no restart needed)
|
|
582
|
+
|
|
583
|
+
These settings take effect immediately without a restart:
|
|
584
|
+
|
|
585
|
+
| Setting | Description |
|
|
586
|
+
|---------|-------------|
|
|
587
|
+
| Dashboard Theme | Visual theme for the Mind Palace (`dark`, `midnight`, `purple`) |
|
|
588
|
+
| Context Depth | Default level for `session_load_context` (`quick`, `standard`, `deep`) |
|
|
589
|
+
| Auto-Capture HTML | Snapshot local dev server HTML on every handoff save |
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
543
593
|
## Progressive Context Loading
|
|
544
594
|
|
|
545
595
|
Load only what you need — saves tokens and speeds up boot:
|
|
@@ -880,6 +930,7 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
880
930
|
│ │ ├── interface.ts # StorageBackend abstraction (+ GDPR delete methods)
|
|
881
931
|
│ │ ├── sqlite.ts # SQLite local storage (libSQL + F32_BLOB + deleted_at migration)
|
|
882
932
|
│ │ ├── supabase.ts # Supabase cloud storage (+ soft/hard delete)
|
|
933
|
+
│ │ ├── configStorage.ts # Boot config micro-DB (~/.prism-mcp/prism-config.db)
|
|
883
934
|
│ │ └── index.ts # Backend factory (auto-selects based on PRISM_STORAGE)
|
|
884
935
|
│ ├── sync/
|
|
885
936
|
│ │ ├── interface.ts # SyncBus abstraction (Telepathy)
|
|
@@ -928,6 +979,10 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
928
979
|
|
|
929
980
|
> **[View the full project board →](https://github.com/users/dcostenco/projects/1/views/1)**
|
|
930
981
|
|
|
982
|
+
### ✅ v3.0 — Agent Hivemind (Shipped!)
|
|
983
|
+
|
|
984
|
+
See [What's New in v3.0.0](#whats-new-in-v300---agent-hivemind-) above.
|
|
985
|
+
|
|
931
986
|
### 🚀 Future Ideas
|
|
932
987
|
|
|
933
988
|
| Feature | Issue | Description |
|
package/dist/config.js
CHANGED
|
@@ -106,6 +106,14 @@ export const PRISM_CAPTURE_PORTS = (process.env.PRISM_CAPTURE_PORTS || "3000,300
|
|
|
106
106
|
// Optionally enable verbose output (stderr) for Prism initialization,
|
|
107
107
|
// memory indexing, and background tasks.
|
|
108
108
|
export const PRISM_DEBUG_LOGGING = process.env.PRISM_DEBUG_LOGGING === "true";
|
|
109
|
+
// ─── v3.0: Agent Hivemind Feature Flag ───────────────────────
|
|
110
|
+
// When enabled, registers 3 additional MCP tools for multi-agent
|
|
111
|
+
// coordination: agent_register, agent_heartbeat, agent_list_team.
|
|
112
|
+
// The role parameter on existing tools (session_save_ledger, etc.)
|
|
113
|
+
// is always available regardless of this flag — adding a parameter
|
|
114
|
+
// doesn't increase tool count.
|
|
115
|
+
// Set PRISM_ENABLE_HIVEMIND=true to unlock the Agent Registry tools.
|
|
116
|
+
export const PRISM_ENABLE_HIVEMIND = process.env.PRISM_ENABLE_HIVEMIND === "true";
|
|
109
117
|
if (PRISM_AUTO_CAPTURE) {
|
|
110
118
|
// Use console.error instead of debugLog here to prevent circular dependency
|
|
111
119
|
if (PRISM_DEBUG_LOGGING) {
|
package/dist/dashboard/server.js
CHANGED
|
@@ -17,54 +17,69 @@
|
|
|
17
17
|
* ═══════════════════════════════════════════════════════════════════
|
|
18
18
|
*/
|
|
19
19
|
import * as http from "http";
|
|
20
|
-
import {
|
|
20
|
+
import { exec } from "child_process";
|
|
21
21
|
import { getStorage } from "../storage/index.js";
|
|
22
22
|
import { PRISM_USER_ID, SERVER_CONFIG } from "../config.js";
|
|
23
23
|
import { renderDashboardHTML } from "./ui.js";
|
|
24
24
|
const PORT = parseInt(process.env.PRISM_DASHBOARD_PORT || "3000", 10);
|
|
25
|
+
/** Read HTTP request body as string */
|
|
26
|
+
function readBody(req) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
let data = "";
|
|
29
|
+
req.on("data", chunk => { data += chunk; });
|
|
30
|
+
req.on("end", () => resolve(data));
|
|
31
|
+
req.on("error", reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
25
34
|
/**
|
|
26
35
|
* Kill any existing process holding the dashboard port.
|
|
27
36
|
* This prevents zombie dashboard processes from surviving IDE restarts
|
|
28
37
|
* and serving stale versions of the UI.
|
|
38
|
+
*
|
|
39
|
+
* CRITICAL: Uses async exec() instead of execSync() to avoid blocking
|
|
40
|
+
* the Node.js event loop. Blocking during startup prevents the MCP
|
|
41
|
+
* stdio transport from responding to the initialize handshake in time,
|
|
42
|
+
* causing Antigravity to report MCP_SERVER_INIT_ERROR.
|
|
29
43
|
*/
|
|
30
|
-
function killPortHolder(port) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
async function killPortHolder(port) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
exec(`lsof -ti tcp:${port}`, { encoding: "utf-8" }, (err, stdout) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
// lsof exits with code 1 when no matches found — that's expected.
|
|
49
|
+
// Any other failure (lsof missing, permission denied, etc.) gets a warning.
|
|
50
|
+
const isNoMatch = err.code === 1;
|
|
51
|
+
if (!isNoMatch) {
|
|
52
|
+
console.error(`[Dashboard] killPortHolder: could not check port ${port} (lsof may not be installed) — skipping.`);
|
|
53
|
+
}
|
|
54
|
+
return resolve();
|
|
55
|
+
}
|
|
56
|
+
const pids = stdout.trim().split("\n").filter(Boolean);
|
|
57
|
+
if (pids.length === 0)
|
|
58
|
+
return resolve();
|
|
59
|
+
// Don't kill ourselves
|
|
60
|
+
const myPid = String(process.pid);
|
|
61
|
+
const stalePids = pids.filter(p => p !== myPid);
|
|
62
|
+
if (stalePids.length > 0) {
|
|
63
|
+
console.error(`[Dashboard] Killing stale process(es) on port ${port}: ${stalePids.join(", ")}`);
|
|
64
|
+
exec(`kill ${stalePids.join(" ")}`, () => {
|
|
65
|
+
// Brief pause to let the OS release the port
|
|
66
|
+
setTimeout(resolve, 300);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
resolve();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
59
74
|
}
|
|
60
75
|
export async function startDashboardServer() {
|
|
61
76
|
// Clean up any zombie dashboard process from a previous session
|
|
62
|
-
killPortHolder(PORT);
|
|
77
|
+
await killPortHolder(PORT);
|
|
63
78
|
const storage = await getStorage();
|
|
64
79
|
const httpServer = http.createServer(async (req, res) => {
|
|
65
80
|
// CORS headers for local dev
|
|
66
81
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
67
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
82
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
68
83
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
69
84
|
if (req.method === "OPTIONS") {
|
|
70
85
|
res.writeHead(204);
|
|
@@ -193,6 +208,55 @@ export async function startDashboardServer() {
|
|
|
193
208
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
194
209
|
return res.end(JSON.stringify({ nodes, edges }));
|
|
195
210
|
}
|
|
211
|
+
// ─── API: Hivemind Team Roster (v3.0) ───
|
|
212
|
+
if (url.pathname === "/api/team") {
|
|
213
|
+
const projectName = url.searchParams.get("project");
|
|
214
|
+
if (!projectName) {
|
|
215
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
216
|
+
return res.end(JSON.stringify({ error: "Missing ?project= parameter" }));
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const team = await storage.listTeam(projectName, PRISM_USER_ID);
|
|
220
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
221
|
+
return res.end(JSON.stringify({ team }));
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
225
|
+
return res.end(JSON.stringify({ team: [] }));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ─── API: Settings — GET (v3.0 Dashboard Settings) ───
|
|
229
|
+
if (url.pathname === "/api/settings" && req.method === "GET") {
|
|
230
|
+
try {
|
|
231
|
+
const { getAllSettings } = await import("../storage/configStorage.js");
|
|
232
|
+
const settings = await getAllSettings();
|
|
233
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
234
|
+
return res.end(JSON.stringify({ settings }));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
238
|
+
return res.end(JSON.stringify({ settings: {} }));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ─── API: Settings — POST (v3.0 Dashboard Settings) ───
|
|
242
|
+
if (url.pathname === "/api/settings" && req.method === "POST") {
|
|
243
|
+
try {
|
|
244
|
+
const body = await readBody(req);
|
|
245
|
+
const parsed = JSON.parse(body);
|
|
246
|
+
if (parsed.key && parsed.value !== undefined) {
|
|
247
|
+
const { setSetting } = await import("../storage/configStorage.js");
|
|
248
|
+
await setSetting(parsed.key, String(parsed.value));
|
|
249
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
250
|
+
return res.end(JSON.stringify({ ok: true, key: parsed.key, value: parsed.value }));
|
|
251
|
+
}
|
|
252
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
253
|
+
return res.end(JSON.stringify({ error: "Missing key or value" }));
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
257
|
+
return res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
196
260
|
// ─── 404 ───
|
|
197
261
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
198
262
|
res.end("Not found");
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -27,7 +27,8 @@ export function renderDashboardHTML(version) {
|
|
|
27
27
|
<style>
|
|
28
28
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
29
29
|
|
|
30
|
-
:
|
|
30
|
+
/* ─── Theme: Dark (Default) ─── */
|
|
31
|
+
:root, [data-theme="dark"] {
|
|
31
32
|
--bg-primary: #0a0e1a;
|
|
32
33
|
--bg-secondary: #111827;
|
|
33
34
|
--bg-glass: rgba(17, 24, 39, 0.6);
|
|
@@ -49,6 +50,44 @@ export function renderDashboardHTML(version) {
|
|
|
49
50
|
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/* ─── Theme: Midnight — deeper blacks, blue-shifted accents ─── */
|
|
54
|
+
[data-theme="midnight"] {
|
|
55
|
+
--bg-primary: #020617;
|
|
56
|
+
--bg-secondary: #0f172a;
|
|
57
|
+
--bg-glass: rgba(2, 6, 23, 0.7);
|
|
58
|
+
--border-glass: rgba(59, 130, 246, 0.15);
|
|
59
|
+
--border-glow: rgba(59, 130, 246, 0.35);
|
|
60
|
+
--text-primary: #e2e8f0;
|
|
61
|
+
--text-secondary: #94a3b8;
|
|
62
|
+
--text-muted: #475569;
|
|
63
|
+
--accent-purple: #818cf8;
|
|
64
|
+
--accent-blue: #60a5fa;
|
|
65
|
+
--accent-cyan: #22d3ee;
|
|
66
|
+
--accent-green: #34d399;
|
|
67
|
+
--accent-amber: #fbbf24;
|
|
68
|
+
--accent-rose: #fb7185;
|
|
69
|
+
--gradient-hero: linear-gradient(135deg, #818cf8 0%, #60a5fa 50%, #22d3ee 100%);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ─── Theme: Purple Haze — warm violet tones ─── */
|
|
73
|
+
[data-theme="purple"] {
|
|
74
|
+
--bg-primary: #0c0515;
|
|
75
|
+
--bg-secondary: #1a0a2e;
|
|
76
|
+
--bg-glass: rgba(26, 10, 46, 0.65);
|
|
77
|
+
--border-glass: rgba(168, 85, 247, 0.2);
|
|
78
|
+
--border-glow: rgba(168, 85, 247, 0.4);
|
|
79
|
+
--text-primary: #f5f3ff;
|
|
80
|
+
--text-secondary: #c4b5fd;
|
|
81
|
+
--text-muted: #7c3aed;
|
|
82
|
+
--accent-purple: #a855f7;
|
|
83
|
+
--accent-blue: #7c3aed;
|
|
84
|
+
--accent-cyan: #c084fc;
|
|
85
|
+
--accent-green: #a78bfa;
|
|
86
|
+
--accent-amber: #e879f9;
|
|
87
|
+
--accent-rose: #f472b6;
|
|
88
|
+
--gradient-hero: linear-gradient(135deg, #a855f7 0%, #7c3aed 50%, #c084fc 100%);
|
|
89
|
+
}
|
|
90
|
+
|
|
52
91
|
body {
|
|
53
92
|
background: var(--bg-primary);
|
|
54
93
|
color: var(--text-primary);
|
|
@@ -270,6 +309,87 @@ export function renderDashboardHTML(version) {
|
|
|
270
309
|
transition: color 0.2s;
|
|
271
310
|
}
|
|
272
311
|
.refresh-btn:hover { color: var(--accent-purple); }
|
|
312
|
+
|
|
313
|
+
/* ─── Settings Modal (v3.0) ─── */
|
|
314
|
+
.settings-btn {
|
|
315
|
+
background: none; border: 1px solid var(--border-glass);
|
|
316
|
+
color: var(--text-secondary); cursor: pointer; font-size: 1.1rem;
|
|
317
|
+
padding: 0.4rem 0.7rem; border-radius: var(--radius-sm);
|
|
318
|
+
transition: all 0.2s;
|
|
319
|
+
}
|
|
320
|
+
.settings-btn:hover { border-color: var(--border-glow); color: var(--accent-purple); }
|
|
321
|
+
.modal-overlay {
|
|
322
|
+
display: none; position: fixed; inset: 0; z-index: 100;
|
|
323
|
+
background: rgba(0,0,0,0.6); backdrop-filter: blur(4px);
|
|
324
|
+
justify-content: center; align-items: center;
|
|
325
|
+
}
|
|
326
|
+
.modal-overlay.active { display: flex; }
|
|
327
|
+
.modal {
|
|
328
|
+
background: var(--bg-secondary); border: 1px solid var(--border-glow);
|
|
329
|
+
border-radius: var(--radius); padding: 2rem; width: 480px; max-width: 90vw;
|
|
330
|
+
max-height: 85vh; overflow-y: auto; position: relative;
|
|
331
|
+
}
|
|
332
|
+
.modal h2 { font-size: 1.1rem; margin-bottom: 1.5rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
333
|
+
.modal-close {
|
|
334
|
+
position: absolute; top: 1rem; right: 1rem; background: none;
|
|
335
|
+
border: none; color: var(--text-muted); cursor: pointer; font-size: 1.25rem;
|
|
336
|
+
}
|
|
337
|
+
.modal-close:hover { color: var(--text-primary); }
|
|
338
|
+
.setting-row {
|
|
339
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
340
|
+
padding: 0.75rem 0; border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
341
|
+
}
|
|
342
|
+
.setting-row:last-child { border-bottom: none; }
|
|
343
|
+
.setting-label { font-size: 0.85rem; color: var(--text-secondary); }
|
|
344
|
+
.setting-desc { font-size: 0.7rem; color: var(--text-muted); margin-top: 0.2rem; }
|
|
345
|
+
.toggle {
|
|
346
|
+
position: relative; width: 44px; height: 24px;
|
|
347
|
+
background: rgba(100,116,139,0.3); border-radius: 12px;
|
|
348
|
+
cursor: pointer; transition: background 0.3s; flex-shrink: 0;
|
|
349
|
+
}
|
|
350
|
+
.toggle.active { background: var(--accent-purple); }
|
|
351
|
+
.toggle::after {
|
|
352
|
+
content: ''; position: absolute; top: 2px; left: 2px;
|
|
353
|
+
width: 20px; height: 20px; border-radius: 50%;
|
|
354
|
+
background: white; transition: transform 0.3s;
|
|
355
|
+
}
|
|
356
|
+
.toggle.active::after { transform: translateX(20px); }
|
|
357
|
+
.setting-select {
|
|
358
|
+
background: var(--bg-primary); border: 1px solid var(--border-glass);
|
|
359
|
+
color: var(--text-primary); padding: 0.4rem 0.6rem;
|
|
360
|
+
border-radius: 6px; font-size: 0.8rem; font-family: var(--font-sans);
|
|
361
|
+
}
|
|
362
|
+
.setting-section {
|
|
363
|
+
font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
|
|
364
|
+
letter-spacing: 0.1em; color: var(--accent-purple); margin: 1rem 0 0.5rem;
|
|
365
|
+
}
|
|
366
|
+
.setting-saved {
|
|
367
|
+
font-size: 0.75rem; color: var(--accent-green); opacity: 0;
|
|
368
|
+
transition: opacity 0.3s; margin-left: 0.5rem;
|
|
369
|
+
}
|
|
370
|
+
.setting-saved.show { opacity: 1; }
|
|
371
|
+
.boot-badge {
|
|
372
|
+
font-size: 0.6rem; padding: 0.15rem 0.5rem; border-radius: 4px;
|
|
373
|
+
background: rgba(245,158,11,0.15); color: var(--accent-amber);
|
|
374
|
+
font-weight: 600; text-transform: uppercase;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* ─── Hivemind Radar (v3.0) ─── */
|
|
378
|
+
.team-list { list-style: none; padding: 0; }
|
|
379
|
+
.team-item {
|
|
380
|
+
display: flex; align-items: center; gap: 0.75rem;
|
|
381
|
+
padding: 0.6rem 0; border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
382
|
+
font-size: 0.85rem;
|
|
383
|
+
}
|
|
384
|
+
.team-item:last-child { border-bottom: none; }
|
|
385
|
+
.team-role { font-weight: 600; color: var(--text-primary); min-width: 60px; }
|
|
386
|
+
.team-task { color: var(--text-secondary); flex: 1; }
|
|
387
|
+
.team-heartbeat { font-size: 0.7rem; color: var(--text-muted); font-family: var(--font-mono); }
|
|
388
|
+
.pulse-dot {
|
|
389
|
+
width: 8px; height: 8px; border-radius: 50%; background: var(--accent-green);
|
|
390
|
+
flex-shrink: 0; animation: pulseDot 2s ease-in-out infinite;
|
|
391
|
+
}
|
|
392
|
+
@keyframes pulseDot { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
273
393
|
</style>
|
|
274
394
|
</head>
|
|
275
395
|
<body>
|
|
@@ -286,6 +406,7 @@ export function renderDashboardHTML(version) {
|
|
|
286
406
|
<option value="">Loading projects...</option>
|
|
287
407
|
</select>
|
|
288
408
|
<button onclick="loadProject()">Inspect</button>
|
|
409
|
+
<button class="settings-btn" onclick="openSettings()" title="Settings">⚙️</button>
|
|
289
410
|
</div>
|
|
290
411
|
</header>
|
|
291
412
|
|
|
@@ -367,6 +488,84 @@ export function renderDashboardHTML(version) {
|
|
|
367
488
|
<div class="card-title"><span class="dot" style="background:var(--accent-amber)"></span> Session Ledger</div>
|
|
368
489
|
<div class="timeline" id="ledgerTimeline"></div>
|
|
369
490
|
</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<!-- Hivemind Radar (v3.0) -->
|
|
494
|
+
<div class="card" id="hivemindCard" style="display:none">
|
|
495
|
+
<div class="card-title">
|
|
496
|
+
<span class="dot" style="background:var(--accent-cyan)"></span>
|
|
497
|
+
Hivemind Radar 🐝
|
|
498
|
+
<button onclick="loadTeam()" class="refresh-btn">↻</button>
|
|
499
|
+
</div>
|
|
500
|
+
<ul class="team-list" id="teamList">
|
|
501
|
+
<li style="color:var(--text-muted);font-size:0.85rem;text-align:center;padding:1rem">
|
|
502
|
+
No active agents. Set PRISM_ENABLE_HIVEMIND=true to enable.
|
|
503
|
+
</li>
|
|
504
|
+
</ul>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<!-- Settings Modal (v3.0) -->
|
|
510
|
+
<div class="modal-overlay" id="settingsModal">
|
|
511
|
+
<div class="modal">
|
|
512
|
+
<button class="modal-close" onclick="closeSettings()">✕</button>
|
|
513
|
+
<h2>⚙️ Settings</h2>
|
|
514
|
+
|
|
515
|
+
<div class="setting-section">Runtime Settings</div>
|
|
516
|
+
|
|
517
|
+
<div class="setting-row">
|
|
518
|
+
<div>
|
|
519
|
+
<div class="setting-label">Auto-Capture HTML</div>
|
|
520
|
+
<div class="setting-desc">Capture local dev server UI on handoff save</div>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="toggle" id="toggle-auto-capture" onclick="toggleSetting('auto_capture', this)"></div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div class="setting-row">
|
|
526
|
+
<div>
|
|
527
|
+
<div class="setting-label">Dashboard Theme</div>
|
|
528
|
+
<div class="setting-desc">Visual theme for Mind Palace</div>
|
|
529
|
+
</div>
|
|
530
|
+
<select class="setting-select" id="select-theme" onchange="saveSetting('dashboard_theme', this.value)">
|
|
531
|
+
<option value="dark">Dark (Default)</option>
|
|
532
|
+
<option value="midnight">Midnight</option>
|
|
533
|
+
<option value="purple">Purple Haze</option>
|
|
534
|
+
</select>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
<div class="setting-row">
|
|
538
|
+
<div>
|
|
539
|
+
<div class="setting-label">Context Depth</div>
|
|
540
|
+
<div class="setting-desc">Default level for session_load_context</div>
|
|
541
|
+
</div>
|
|
542
|
+
<select class="setting-select" id="select-context-depth" onchange="saveSetting('default_context_depth', this.value)">
|
|
543
|
+
<option value="standard">Standard (~200 tokens)</option>
|
|
544
|
+
<option value="quick">Quick (~50 tokens)</option>
|
|
545
|
+
<option value="deep">Deep (~1000+ tokens)</option>
|
|
546
|
+
</select>
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
<div class="setting-section">Boot Settings <span class="boot-badge">Restart Required</span></div>
|
|
550
|
+
|
|
551
|
+
<div class="setting-row">
|
|
552
|
+
<div>
|
|
553
|
+
<div class="setting-label">Hivemind Mode</div>
|
|
554
|
+
<div class="setting-desc">Multi-agent coordination (PRISM_ENABLE_HIVEMIND)</div>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="toggle" id="toggle-hivemind" onclick="toggleBootSetting('hivemind_enabled', this)"></div>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="setting-row">
|
|
559
|
+
<div>
|
|
560
|
+
<div class="setting-label">Storage Backend</div>
|
|
561
|
+
<div class="setting-desc">Switch between SQLite and Supabase</div>
|
|
562
|
+
</div>
|
|
563
|
+
<select id="storageBackendSelect" onchange="window.saveBootSetting('PRISM_STORAGE', this.value)" style="padding: 0.2rem 0.4rem; 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); cursor: pointer;">
|
|
564
|
+
<option value="local">SQLite</option>
|
|
565
|
+
<option value="supabase">Supabase</option>
|
|
566
|
+
</select>
|
|
567
|
+
</div>
|
|
568
|
+
<span class="setting-saved" id="savedToast">Saved ✓</span>
|
|
370
569
|
</div>
|
|
371
570
|
</div>
|
|
372
571
|
</div>
|
|
@@ -528,6 +727,7 @@ export function renderDashboardHTML(version) {
|
|
|
528
727
|
|
|
529
728
|
document.getElementById('content').className = 'grid grid-main fade-in';
|
|
530
729
|
document.getElementById('content').style.display = 'grid';
|
|
730
|
+
loadTeam(); // v3.0: auto-load Hivemind team
|
|
531
731
|
} catch(e) {
|
|
532
732
|
alert('Failed to load project data: ' + e.message);
|
|
533
733
|
} finally {
|
|
@@ -618,6 +818,127 @@ export function renderDashboardHTML(version) {
|
|
|
618
818
|
|
|
619
819
|
// Initialize the graph on page load
|
|
620
820
|
loadGraph();
|
|
821
|
+
|
|
822
|
+
// ─── Settings Modal (v3.0) ───
|
|
823
|
+
function openSettings() {
|
|
824
|
+
document.getElementById('settingsModal').classList.add('active');
|
|
825
|
+
loadSettings();
|
|
826
|
+
}
|
|
827
|
+
function closeSettings() {
|
|
828
|
+
document.getElementById('settingsModal').classList.remove('active');
|
|
829
|
+
}
|
|
830
|
+
// Close on overlay click
|
|
831
|
+
document.getElementById('settingsModal').addEventListener('click', function(e) {
|
|
832
|
+
if (e.target === this) closeSettings();
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
async function loadSettings() {
|
|
836
|
+
try {
|
|
837
|
+
var res = await fetch('/api/settings');
|
|
838
|
+
var data = await res.json();
|
|
839
|
+
var s = data.settings || {};
|
|
840
|
+
// Runtime toggles
|
|
841
|
+
if (s.auto_capture === 'true') document.getElementById('toggle-auto-capture').classList.add('active');
|
|
842
|
+
else document.getElementById('toggle-auto-capture').classList.remove('active');
|
|
843
|
+
// Context depth
|
|
844
|
+
if (s.default_context_depth) document.getElementById('select-context-depth').value = s.default_context_depth;
|
|
845
|
+
// Theme
|
|
846
|
+
if (s.dashboard_theme) {
|
|
847
|
+
document.getElementById('select-theme').value = s.dashboard_theme;
|
|
848
|
+
applyTheme(s.dashboard_theme);
|
|
849
|
+
}
|
|
850
|
+
// Boot toggles
|
|
851
|
+
if (s.hivemind_enabled === 'true') document.getElementById('toggle-hivemind').classList.add('active');
|
|
852
|
+
else document.getElementById('toggle-hivemind').classList.remove('active');
|
|
853
|
+
|
|
854
|
+
// Storage Backend
|
|
855
|
+
if (s.PRISM_STORAGE) {
|
|
856
|
+
document.getElementById('storageBackendSelect').value = s.PRISM_STORAGE;
|
|
857
|
+
}
|
|
858
|
+
} catch(e) { console.warn('Settings load failed:', e); }
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function toggleSetting(key, el) {
|
|
862
|
+
var isActive = el.classList.toggle('active');
|
|
863
|
+
saveSetting(key, isActive ? 'true' : 'false');
|
|
864
|
+
}
|
|
865
|
+
function toggleBootSetting(key, el) {
|
|
866
|
+
var isActive = el.classList.toggle('active');
|
|
867
|
+
saveSetting(key, isActive ? 'true' : 'false');
|
|
868
|
+
showToast('Saved. Restart your AI client for this to take effect.');
|
|
869
|
+
}
|
|
870
|
+
function saveBootSetting(key, value) {
|
|
871
|
+
saveSetting(key, value);
|
|
872
|
+
showToast('Saved. Restart your AI client for this to take effect.');
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async function saveSetting(key, value) {
|
|
876
|
+
try {
|
|
877
|
+
await fetch('/api/settings', {
|
|
878
|
+
method: 'POST',
|
|
879
|
+
headers: { 'Content-Type': 'application/json' },
|
|
880
|
+
body: JSON.stringify({ key: key, value: value })
|
|
881
|
+
});
|
|
882
|
+
// Apply theme instantly on change
|
|
883
|
+
if (key === 'dashboard_theme') applyTheme(value);
|
|
884
|
+
showToast('Saved ✓');
|
|
885
|
+
} catch(e) { console.error('Setting save failed:', e); }
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* applyTheme — sets the data-theme attribute on <html>
|
|
890
|
+
* CSS custom properties in [data-theme="..."] blocks
|
|
891
|
+
* override :root defaults instantly, no page reload needed.
|
|
892
|
+
*/
|
|
893
|
+
function applyTheme(theme) {
|
|
894
|
+
document.documentElement.setAttribute('data-theme', theme || 'dark');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function showToast(msg) {
|
|
898
|
+
var toast = document.getElementById('savedToast');
|
|
899
|
+
toast.textContent = msg || 'Saved ✓';
|
|
900
|
+
toast.classList.add('show');
|
|
901
|
+
setTimeout(function() { toast.classList.remove('show'); }, 2000);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// ─── Hivemind Radar (v3.0) ───
|
|
905
|
+
async function loadTeam() {
|
|
906
|
+
var project = document.getElementById('projectSelect').value;
|
|
907
|
+
if (!project) return;
|
|
908
|
+
var card = document.getElementById('hivemindCard');
|
|
909
|
+
try {
|
|
910
|
+
var res = await fetch('/api/team?project=' + encodeURIComponent(project));
|
|
911
|
+
var data = await res.json();
|
|
912
|
+
var team = data.team || [];
|
|
913
|
+
var list = document.getElementById('teamList');
|
|
914
|
+
if (team.length > 0) {
|
|
915
|
+
var roleIcons = {dev:'🛠️',qa:'🔍',pm:'📋',lead:'🏗️',security:'🔒',ux:'🎨',cmo:'📢'};
|
|
916
|
+
list.innerHTML = team.map(function(a) {
|
|
917
|
+
var icon = roleIcons[a.role] || '🤖';
|
|
918
|
+
var ago = a.last_heartbeat ? timeAgo(a.last_heartbeat) : '?';
|
|
919
|
+
return '<li class="team-item">' +
|
|
920
|
+
'<span class="pulse-dot"></span>' +
|
|
921
|
+
'<span class="team-role">' + icon + ' ' + escapeHtml(a.role) + '</span>' +
|
|
922
|
+
'<span class="team-task">' + escapeHtml(a.current_task || 'idle') + '</span>' +
|
|
923
|
+
'<span class="team-heartbeat">' + ago + '</span></li>';
|
|
924
|
+
}).join('');
|
|
925
|
+
card.style.display = 'block';
|
|
926
|
+
} else {
|
|
927
|
+
list.innerHTML = '<li style="color:var(--text-muted);font-size:0.85rem;text-align:center;padding:1rem">No active agents on this project.</li>';
|
|
928
|
+
card.style.display = 'block';
|
|
929
|
+
}
|
|
930
|
+
} catch(e) {
|
|
931
|
+
console.warn('Team load failed:', e);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function timeAgo(iso) {
|
|
936
|
+
var diff = Date.now() - new Date(iso).getTime();
|
|
937
|
+
var mins = Math.floor(diff / 60000);
|
|
938
|
+
if (mins < 1) return 'just now';
|
|
939
|
+
if (mins < 60) return mins + 'm ago';
|
|
940
|
+
return Math.floor(mins/60) + 'h ago';
|
|
941
|
+
}
|
|
621
942
|
</script>
|
|
622
943
|
</body>
|
|
623
944
|
</html>`;
|