prism-mcp-server 2.3.9 → 2.3.11
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 +12 -1
- package/dist/config.js +7 -10
- package/dist/server.js +1 -19
- package/dist/storage/index.js +2 -1
- package/dist/storage/sqlite.js +3 -2
- package/dist/storage/supabase.js +3 -2
- package/dist/sync/factory.js +2 -1
- package/dist/sync/sqliteSync.js +5 -4
- package/dist/sync/supabaseSync.js +5 -4
- package/dist/tools/compactionHandler.js +3 -2
- package/dist/tools/handlers.js +7 -6
- package/dist/tools/sessionMemoryHandlers.js +56 -32
- package/dist/utils/briefing.js +2 -1
- package/dist/utils/embeddingApi.js +3 -2
- package/dist/utils/factMerger.js +4 -3
- package/dist/utils/healthCheck.js +3 -2
- package/dist/utils/logger.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,16 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## What's New in v2.3.
|
|
17
|
+
## What's New in v2.3.10 — Stability & Fixes 🛠️
|
|
18
|
+
|
|
19
|
+
| Feature | Description |
|
|
20
|
+
|---|---|
|
|
21
|
+
| 🪲 **Windows Black Screen Fix** | Fixed Python `subprocess.Popen` spawning visible Node.js terminal windows on Windows. |
|
|
22
|
+
| 📝 **Debug Logging** | Gated verbose startup logs behind `PRISM_DEBUG_LOGGING` for a cleaner default experience. |
|
|
23
|
+
| ⚡ **Excess Loading Fixes** | Performance improvements to resolve excess loading loops. |
|
|
24
|
+
|
|
25
|
+
<details>
|
|
26
|
+
<summary><strong>What's in v2.3.8 — LangGraph Research Agent</strong></summary>
|
|
18
27
|
|
|
19
28
|
| Feature | Description |
|
|
20
29
|
|---|---|
|
|
@@ -24,6 +33,8 @@
|
|
|
24
33
|
| 🔧 **Storage Abstraction Fix** | Resource/Prompt handlers now route through `getStorage()` instead of calling Supabase directly — eliminates EOF crashes when reading `memory://` resources. |
|
|
25
34
|
| 🛡️ **Error Boundaries** | Resource handlers catch errors gracefully and return proper MCP error responses (`isError: true`) instead of crashing the server process. |
|
|
26
35
|
|
|
36
|
+
</details>
|
|
37
|
+
|
|
27
38
|
<details>
|
|
28
39
|
<summary><strong>What's in v2.2.0</strong></summary>
|
|
29
40
|
|
package/dist/config.js
CHANGED
|
@@ -85,16 +85,13 @@ export const PRISM_CAPTURE_PORTS = (process.env.PRISM_CAPTURE_PORTS || "3000,300
|
|
|
85
85
|
.split(",")
|
|
86
86
|
.map(p => parseInt(p.trim(), 10))
|
|
87
87
|
.filter(p => !isNaN(p));
|
|
88
|
+
// ─── v2.3: Debug Logging ──────────────────────────────────────
|
|
89
|
+
// Optionally enable verbose output (stderr) for Prism initialization,
|
|
90
|
+
// memory indexing, and background tasks.
|
|
91
|
+
export const PRISM_DEBUG_LOGGING = process.env.PRISM_DEBUG_LOGGING === "true";
|
|
88
92
|
if (PRISM_AUTO_CAPTURE) {
|
|
89
|
-
console.error
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Set PRISM_DEBUG=true to enable verbose startup and operational logs.
|
|
93
|
-
// When disabled (default), only warnings and errors are printed.
|
|
94
|
-
export const PRISM_DEBUG = process.env.PRISM_DEBUG === "true";
|
|
95
|
-
/** Conditional debug logger — only prints when PRISM_DEBUG=true. */
|
|
96
|
-
export function debug(...args) {
|
|
97
|
-
if (PRISM_DEBUG) {
|
|
98
|
-
console.error(...args);
|
|
93
|
+
// Use console.error instead of debugLog here to prevent circular dependency
|
|
94
|
+
if (PRISM_DEBUG_LOGGING) {
|
|
95
|
+
console.error(`[AutoCapture] Enabled for ports: ${PRISM_CAPTURE_PORTS.join(", ")}`);
|
|
99
96
|
}
|
|
100
97
|
}
|
package/dist/server.js
CHANGED
|
@@ -58,7 +58,7 @@ ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequ
|
|
|
58
58
|
// Claude Desktop that the attached resource has changed.
|
|
59
59
|
// Without this, the paperclipped context becomes stale.
|
|
60
60
|
SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
61
|
-
import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID
|
|
61
|
+
import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID } from "./config.js";
|
|
62
62
|
import { getSyncBus } from "./sync/factory.js";
|
|
63
63
|
import { startDashboardServer } from "./dashboard/server.js";
|
|
64
64
|
// ─── v2.3.6 FIX: Use Storage Abstraction for Prompts/Resources ───
|
|
@@ -147,7 +147,6 @@ const activeSubscriptions = new Set();
|
|
|
147
147
|
export function notifyResourceUpdate(project, server) {
|
|
148
148
|
const uri = `memory://${project}/handoff`;
|
|
149
149
|
if (activeSubscriptions.has(uri)) {
|
|
150
|
-
debug(`[resource-subscription] Notifying update for ${uri}`);
|
|
151
150
|
server.notification({
|
|
152
151
|
method: "notifications/resources/updated",
|
|
153
152
|
params: { uri },
|
|
@@ -165,8 +164,6 @@ export function notifyResourceUpdate(project, server) {
|
|
|
165
164
|
* with subscribe support for live refresh
|
|
166
165
|
*/
|
|
167
166
|
export function createServer() {
|
|
168
|
-
debug(`Creating Prism MCP server v${SERVER_CONFIG.version}`);
|
|
169
|
-
debug(`Registering ${ALL_TOOLS.length} tools (${BASE_TOOLS.length} base + ${SESSION_MEMORY_ENABLED ? SESSION_MEMORY_TOOLS.length : 0} session memory)`);
|
|
170
167
|
const server = new Server({
|
|
171
168
|
name: SERVER_CONFIG.name,
|
|
172
169
|
version: SERVER_CONFIG.version,
|
|
@@ -196,7 +193,6 @@ export function createServer() {
|
|
|
196
193
|
// REVIEWER NOTE: The initialize handler is unchanged from v0.3.0
|
|
197
194
|
// except that it now reports the expanded capabilities.
|
|
198
195
|
server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
199
|
-
debug(`Client connected: ${request.params.clientInfo?.name || 'unknown'}`);
|
|
200
196
|
return {
|
|
201
197
|
protocolVersion: request.params.protocolVersion,
|
|
202
198
|
serverInfo: {
|
|
@@ -214,7 +210,6 @@ export function createServer() {
|
|
|
214
210
|
});
|
|
215
211
|
// ── Handler: List Tools ──
|
|
216
212
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
217
|
-
debug("Received list tools request");
|
|
218
213
|
return {
|
|
219
214
|
tools: ALL_TOOLS,
|
|
220
215
|
};
|
|
@@ -271,7 +266,6 @@ export function createServer() {
|
|
|
271
266
|
}
|
|
272
267
|
const project = promptArgs?.project || "default";
|
|
273
268
|
const level = promptArgs?.level || "standard";
|
|
274
|
-
debug(`[prompt:resume_session] Loading ${level} context for "${project}"`);
|
|
275
269
|
// v2.3.6 FIX: Use storage abstraction instead of direct supabaseRpc
|
|
276
270
|
const storage = await getStorage();
|
|
277
271
|
const result = await storage.loadContext(project, level, PRISM_USER_ID);
|
|
@@ -365,7 +359,6 @@ export function createServer() {
|
|
|
365
359
|
throw new Error(`Unknown resource URI: ${uri}. Expected format: memory://{project}/handoff`);
|
|
366
360
|
}
|
|
367
361
|
const project = decodeURIComponent(match[1]);
|
|
368
|
-
debug(`[resource:read] Fetching handoff for "${project}"`);
|
|
369
362
|
try {
|
|
370
363
|
// v2.3.6 FIX: Use storage abstraction instead of direct supabaseRpc
|
|
371
364
|
const storage = await getStorage();
|
|
@@ -404,13 +397,11 @@ export function createServer() {
|
|
|
404
397
|
// for projects the client hasn't attached.
|
|
405
398
|
server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
|
406
399
|
const uri = request.params.uri;
|
|
407
|
-
debug(`[resource-subscription] Client subscribed to ${uri}`);
|
|
408
400
|
activeSubscriptions.add(uri);
|
|
409
401
|
return {};
|
|
410
402
|
});
|
|
411
403
|
server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
|
|
412
404
|
const uri = request.params.uri;
|
|
413
|
-
debug(`[resource-subscription] Client unsubscribed from ${uri}`);
|
|
414
405
|
activeSubscriptions.delete(uri);
|
|
415
406
|
return {};
|
|
416
407
|
});
|
|
@@ -422,13 +413,11 @@ export function createServer() {
|
|
|
422
413
|
// The server reference is passed to sessionSaveHandoffHandler so it
|
|
423
414
|
// can trigger resource update notifications on successful saves.
|
|
424
415
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
425
|
-
debug(`Tool call: ${request.params.name}`);
|
|
426
416
|
try {
|
|
427
417
|
const { name, arguments: args } = request.params;
|
|
428
418
|
if (!args) {
|
|
429
419
|
throw new Error("No arguments provided");
|
|
430
420
|
}
|
|
431
|
-
debug(`Processing ${name} with arguments: ${JSON.stringify(args)}`);
|
|
432
421
|
switch (name) {
|
|
433
422
|
// ── Search & Analysis Tools (always available) ──
|
|
434
423
|
case "brave_web_search":
|
|
@@ -595,11 +584,8 @@ export function createSandboxServer() {
|
|
|
595
584
|
* responses to stdout. Log messages go to stderr.
|
|
596
585
|
*/
|
|
597
586
|
export async function startServer() {
|
|
598
|
-
debug("Initializing Prism MCP server...");
|
|
599
587
|
const server = createServer();
|
|
600
|
-
debug("Creating stdio transport...");
|
|
601
588
|
const transport = new StdioServerTransport();
|
|
602
|
-
debug("Connecting server to transport...");
|
|
603
589
|
await server.connect(transport);
|
|
604
590
|
// ─── v2.0 Step 6: Initialize SyncBus (Telepathy) ───
|
|
605
591
|
if (SESSION_MEMORY_ENABLED) {
|
|
@@ -607,8 +593,6 @@ export async function startServer() {
|
|
|
607
593
|
const syncBus = await getSyncBus();
|
|
608
594
|
await syncBus.startListening();
|
|
609
595
|
syncBus.on("update", (event) => {
|
|
610
|
-
debug(`[Telepathy] Memory for project '${event.project}' ` +
|
|
611
|
-
`updated to v${event.version} by another agent!`);
|
|
612
596
|
// Send an MCP logging notification to the IDE
|
|
613
597
|
try {
|
|
614
598
|
server.sendLoggingMessage({
|
|
@@ -622,7 +606,6 @@ export async function startServer() {
|
|
|
622
606
|
console.error(`[Telepathy] Failed to send notification: ${err}`);
|
|
623
607
|
}
|
|
624
608
|
});
|
|
625
|
-
debug("[Telepathy] SyncBus active — multi-client sync enabled");
|
|
626
609
|
}
|
|
627
610
|
catch (err) {
|
|
628
611
|
console.error(`[Telepathy] SyncBus init failed (non-fatal): ${err}`);
|
|
@@ -632,7 +615,6 @@ export async function startServer() {
|
|
|
632
615
|
startDashboardServer().catch(err => {
|
|
633
616
|
console.error(`[Dashboard] Mind Palace startup failed (non-fatal): ${err}`);
|
|
634
617
|
});
|
|
635
|
-
debug(`Prism MCP Server v${SERVER_CONFIG.version} running on stdio`);
|
|
636
618
|
// Keep the process alive — without this, Node.js would exit
|
|
637
619
|
// because there are no active event loop handles after the
|
|
638
620
|
// synchronous setup completes.
|
package/dist/storage/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* // Pass `storage` to all session memory handlers
|
|
11
11
|
*/
|
|
12
12
|
import { PRISM_STORAGE } from "../config.js";
|
|
13
|
+
import { debugLog } from "../utils/logger.js";
|
|
13
14
|
import { SupabaseStorage } from "./supabase.js";
|
|
14
15
|
let storageInstance = null;
|
|
15
16
|
/**
|
|
@@ -24,7 +25,7 @@ let storageInstance = null;
|
|
|
24
25
|
export async function getStorage() {
|
|
25
26
|
if (storageInstance)
|
|
26
27
|
return storageInstance;
|
|
27
|
-
|
|
28
|
+
debugLog(`[Prism Storage] Initializing backend: ${PRISM_STORAGE}`);
|
|
28
29
|
if (PRISM_STORAGE === "local") {
|
|
29
30
|
const { SqliteStorage } = await import("./sqlite.js");
|
|
30
31
|
storageInstance = new SqliteStorage();
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -20,6 +20,7 @@ import * as fs from "fs";
|
|
|
20
20
|
import * as path from "path";
|
|
21
21
|
import * as os from "os";
|
|
22
22
|
import { randomUUID } from "crypto";
|
|
23
|
+
import { debugLog } from "../utils/logger.js";
|
|
23
24
|
export class SqliteStorage {
|
|
24
25
|
db;
|
|
25
26
|
dbPath;
|
|
@@ -38,11 +39,11 @@ export class SqliteStorage {
|
|
|
38
39
|
await this.db.execute("PRAGMA journal_mode=WAL");
|
|
39
40
|
// Run all migrations
|
|
40
41
|
await this.runMigrations();
|
|
41
|
-
|
|
42
|
+
debugLog(`[SqliteStorage] Initialized at ${this.dbPath}`);
|
|
42
43
|
}
|
|
43
44
|
async close() {
|
|
44
45
|
this.db.close();
|
|
45
|
-
|
|
46
|
+
debugLog("[SqliteStorage] Closed");
|
|
46
47
|
}
|
|
47
48
|
// ─── Migrations ────────────────────────────────────────────
|
|
48
49
|
async runMigrations() {
|
package/dist/storage/supabase.js
CHANGED
|
@@ -13,17 +13,18 @@
|
|
|
13
13
|
* ═══════════════════════════════════════════════════════════════════
|
|
14
14
|
*/
|
|
15
15
|
import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
|
|
16
|
+
import { debugLog } from "../utils/logger.js";
|
|
16
17
|
export class SupabaseStorage {
|
|
17
18
|
// ─── Lifecycle ─────────────────────────────────────────────
|
|
18
19
|
async initialize() {
|
|
19
20
|
// Supabase is always ready — connection is stateless (REST API).
|
|
20
21
|
// The SUPABASE_URL and SUPABASE_KEY are validated at import time
|
|
21
22
|
// by supabaseApi.ts's guard clause.
|
|
22
|
-
|
|
23
|
+
debugLog("[SupabaseStorage] Initialized (REST API, stateless)");
|
|
23
24
|
}
|
|
24
25
|
async close() {
|
|
25
26
|
// No-op for Supabase — connections are stateless.
|
|
26
|
-
|
|
27
|
+
debugLog("[SupabaseStorage] Closed (no-op for REST)");
|
|
27
28
|
}
|
|
28
29
|
// ─── Ledger Operations ─────────────────────────────────────
|
|
29
30
|
async saveLedger(entry) {
|
package/dist/sync/factory.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Singleton pattern — only one bus per process (prevents duplicate watchers).
|
|
8
8
|
*/
|
|
9
9
|
import { PRISM_STORAGE } from "../config.js";
|
|
10
|
+
import { debugLog } from "../utils/logger.js";
|
|
10
11
|
let _bus = null;
|
|
11
12
|
export async function getSyncBus() {
|
|
12
13
|
if (_bus)
|
|
@@ -28,6 +29,6 @@ export async function getSyncBus() {
|
|
|
28
29
|
_bus = new SupabaseSyncBus(url, key);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
debugLog(`[SyncBus] Initialized: ${_bus.constructor.name} (client=${_bus.clientId.substring(0, 8)})`);
|
|
32
33
|
return _bus;
|
|
33
34
|
}
|
package/dist/sync/sqliteSync.js
CHANGED
|
@@ -29,6 +29,7 @@ import * as fs from "fs";
|
|
|
29
29
|
import * as path from "path";
|
|
30
30
|
import * as os from "os";
|
|
31
31
|
import { SyncBus } from "./index.js";
|
|
32
|
+
import { debugLog } from "../utils/logger.js";
|
|
32
33
|
export class SqliteSyncBus extends SyncBus {
|
|
33
34
|
lockFilePath;
|
|
34
35
|
watching = false;
|
|
@@ -53,7 +54,7 @@ export class SqliteSyncBus extends SyncBus {
|
|
|
53
54
|
};
|
|
54
55
|
// Atomic write — content is < 200 bytes, well within single-page write
|
|
55
56
|
fs.writeFileSync(this.lockFilePath, JSON.stringify(payload), "utf8");
|
|
56
|
-
|
|
57
|
+
debugLog(`[SyncBus] Broadcast: project=${project}, version=${version}, ` +
|
|
57
58
|
`client=${this.clientId.substring(0, 8)}`);
|
|
58
59
|
}
|
|
59
60
|
async startListening() {
|
|
@@ -69,7 +70,7 @@ export class SqliteSyncBus extends SyncBus {
|
|
|
69
70
|
const event = JSON.parse(data);
|
|
70
71
|
// Only emit if it came from a DIFFERENT Prism MCP instance
|
|
71
72
|
if (event.client_id && event.client_id !== this.clientId) {
|
|
72
|
-
|
|
73
|
+
debugLog(`[SyncBus] Received update from client ${event.client_id.substring(0, 8)}: ` +
|
|
73
74
|
`project=${event.project}, version=${event.version}`);
|
|
74
75
|
this.emit("update", event);
|
|
75
76
|
}
|
|
@@ -79,7 +80,7 @@ export class SqliteSyncBus extends SyncBus {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
});
|
|
82
|
-
|
|
83
|
+
debugLog(`[SyncBus] Listening for updates on ${this.lockFilePath} ` +
|
|
83
84
|
`(client=${this.clientId.substring(0, 8)})`);
|
|
84
85
|
}
|
|
85
86
|
async stopListening() {
|
|
@@ -87,6 +88,6 @@ export class SqliteSyncBus extends SyncBus {
|
|
|
87
88
|
return;
|
|
88
89
|
this.watching = false;
|
|
89
90
|
fs.unwatchFile(this.lockFilePath);
|
|
90
|
-
|
|
91
|
+
debugLog("[SyncBus] Stopped listening");
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* ═══════════════════════════════════════════════════════════════════
|
|
18
18
|
*/
|
|
19
19
|
import { SyncBus } from "./index.js";
|
|
20
|
+
import { debugLog } from "../utils/logger.js";
|
|
20
21
|
export class SupabaseSyncBus extends SyncBus {
|
|
21
22
|
supabaseUrl;
|
|
22
23
|
supabaseKey;
|
|
@@ -47,7 +48,7 @@ export class SupabaseSyncBus extends SyncBus {
|
|
|
47
48
|
}, (payload) => {
|
|
48
49
|
const newRow = payload.new;
|
|
49
50
|
if (newRow) {
|
|
50
|
-
|
|
51
|
+
debugLog(`[SyncBus:Supabase] Received update: project=${newRow.project}, version=${newRow.version}`);
|
|
51
52
|
this.emit("update", {
|
|
52
53
|
project: newRow.project,
|
|
53
54
|
version: newRow.version,
|
|
@@ -57,9 +58,9 @@ export class SupabaseSyncBus extends SyncBus {
|
|
|
57
58
|
}
|
|
58
59
|
})
|
|
59
60
|
.subscribe((status) => {
|
|
60
|
-
|
|
61
|
+
debugLog(`[SyncBus:Supabase] Channel status: ${status}`);
|
|
61
62
|
});
|
|
62
|
-
|
|
63
|
+
debugLog(`[SyncBus:Supabase] Listening for realtime updates ` +
|
|
63
64
|
`(client=${this.clientId.substring(0, 8)})`);
|
|
64
65
|
}
|
|
65
66
|
catch (err) {
|
|
@@ -72,7 +73,7 @@ export class SupabaseSyncBus extends SyncBus {
|
|
|
72
73
|
if (this.channel && this.supabase) {
|
|
73
74
|
try {
|
|
74
75
|
await this.supabase.removeChannel(this.channel);
|
|
75
|
-
|
|
76
|
+
debugLog("[SyncBus:Supabase] Stopped listening");
|
|
76
77
|
}
|
|
77
78
|
catch {
|
|
78
79
|
// Ignore cleanup errors
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { getStorage } from "../storage/index.js";
|
|
10
10
|
import { GOOGLE_API_KEY, PRISM_USER_ID } from "../config.js";
|
|
11
11
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
12
|
+
import { debugLog } from "../utils/logger.js";
|
|
12
13
|
// ─── Constants ────────────────────────────────────────────────
|
|
13
14
|
const COMPACTION_CHUNK_SIZE = 10;
|
|
14
15
|
const MAX_ENTRIES_PER_RUN = 100;
|
|
@@ -44,7 +45,7 @@ export async function compactLedgerHandler(args) {
|
|
|
44
45
|
throw new Error("Invalid arguments for session_compact_ledger");
|
|
45
46
|
}
|
|
46
47
|
const { project, threshold = 50, keep_recent = 10, dry_run = false, } = args;
|
|
47
|
-
|
|
48
|
+
debugLog(`[compact_ledger] ${dry_run ? "DRY RUN: " : ""}` +
|
|
48
49
|
`project=${project || "auto-detect"}, threshold=${threshold}, keep_recent=${keep_recent}`);
|
|
49
50
|
const storage = await getStorage();
|
|
50
51
|
// Step 1: Find candidates
|
|
@@ -102,7 +103,7 @@ export async function compactLedgerHandler(args) {
|
|
|
102
103
|
for (const candidate of candidates) {
|
|
103
104
|
const proj = candidate.project;
|
|
104
105
|
const toCompact = Math.min(candidate.to_compact, MAX_ENTRIES_PER_RUN);
|
|
105
|
-
|
|
106
|
+
debugLog(`[compact_ledger] Compacting ${toCompact} entries for "${proj}"`);
|
|
106
107
|
// Fetch oldest entries (the ones to be rolled up)
|
|
107
108
|
const oldEntries = await storage.getLedgerEntries({
|
|
108
109
|
project: `eq.${proj}`,
|
package/dist/tools/handlers.js
CHANGED
|
@@ -24,6 +24,7 @@ import { analyzePaperWithGemini } from "../utils/googleAi.js";
|
|
|
24
24
|
import { isBraveWebSearchArgs, isBraveLocalSearchArgs, isBraveAnswersArgs, isGeminiResearchPaperAnalysisArgs, isBraveWebSearchCodeModeArgs, isBraveLocalSearchCodeModeArgs, isCodeModeTransformArgs } from "./definitions.js";
|
|
25
25
|
import { runInSandbox } from "../utils/executor.js";
|
|
26
26
|
import { CODE_MODE_TEMPLATES, getTemplateNames } from "../templates/codeMode.js";
|
|
27
|
+
import { debugLog } from "../utils/logger.js";
|
|
27
28
|
// ─── Simple Search Handlers ──────────────────────────────────
|
|
28
29
|
/** Performs a standard web search and returns formatted text results. */
|
|
29
30
|
export async function webSearchHandler(args) {
|
|
@@ -56,11 +57,11 @@ export async function braveWebSearchCodeModeHandler(args) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
// 1. Fetch raw data
|
|
59
|
-
|
|
60
|
+
debugLog(`Fetching web search for code mode: "${query}"`);
|
|
60
61
|
const rawDataStr = await performWebSearchRaw(query, count, offset);
|
|
61
62
|
const beforeSizeKB = (Buffer.byteLength(rawDataStr, 'utf8') / 1024).toFixed(1);
|
|
62
63
|
// 2. Run code mode sandbox
|
|
63
|
-
|
|
64
|
+
debugLog(`Executing code mode sandbox...`);
|
|
64
65
|
const { stdout, error, executionTimeMs } = await runInSandbox(rawDataStr, code);
|
|
65
66
|
if (error) {
|
|
66
67
|
return {
|
|
@@ -93,10 +94,10 @@ export async function braveLocalSearchCodeModeHandler(args) {
|
|
|
93
94
|
isError: true,
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
+
debugLog(`Fetching local search for code mode: "${query}"`);
|
|
97
98
|
const rawDataStr = await performLocalSearchRaw(query, count);
|
|
98
99
|
const beforeSizeKB = (Buffer.byteLength(rawDataStr, "utf8") / 1024).toFixed(1);
|
|
99
|
-
|
|
100
|
+
debugLog("Executing local search code mode sandbox...");
|
|
100
101
|
const { stdout, error, executionTimeMs } = await runInSandbox(rawDataStr, code);
|
|
101
102
|
if (error) {
|
|
102
103
|
return {
|
|
@@ -151,7 +152,7 @@ export async function codeModeTransformHandler(args) {
|
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
154
|
const beforeSizeKB = (Buffer.byteLength(data, "utf8") / 1024).toFixed(1);
|
|
154
|
-
|
|
155
|
+
debugLog(`[code_mode_transform] source=${source_tool}, template=${templateUsed}, input=${beforeSizeKB}KB`);
|
|
155
156
|
const { stdout, error, executionTimeMs } = await runInSandbox(data, scriptToRun);
|
|
156
157
|
if (error) {
|
|
157
158
|
return {
|
|
@@ -215,7 +216,7 @@ export async function researchPaperAnalysisHandler(args) {
|
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
218
|
try {
|
|
218
|
-
|
|
219
|
+
debugLog(`Analyzing research paper with Gemini (${analysisType} analysis)...`);
|
|
219
220
|
const analysis = await analyzePaperWithGemini(paperContent, analysisType, additionalContext);
|
|
220
221
|
return {
|
|
221
222
|
content: [{ type: "text", text: analysis }],
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* without any code changes.
|
|
16
16
|
* ═══════════════════════════════════════════════════════════════════
|
|
17
17
|
*/
|
|
18
|
+
import { debugLog } from "../utils/logger.js";
|
|
18
19
|
import { getStorage } from "../storage/index.js";
|
|
19
20
|
import { toKeywordArray } from "../utils/keywordExtractor.js";
|
|
20
21
|
import { generateEmbedding } from "../utils/embeddingApi.js";
|
|
@@ -39,11 +40,11 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
39
40
|
}
|
|
40
41
|
const { project, conversation_id, summary, todos, files_changed, decisions } = args;
|
|
41
42
|
const storage = await getStorage();
|
|
42
|
-
|
|
43
|
+
debugLog(`[session_save_ledger] Saving ledger entry for project="${project}"`);
|
|
43
44
|
// Auto-extract keywords from summary + decisions for knowledge accumulation
|
|
44
45
|
const combinedText = [summary, ...(decisions || [])].join(" ");
|
|
45
46
|
const keywords = toKeywordArray(combinedText);
|
|
46
|
-
|
|
47
|
+
debugLog(`[session_save_ledger] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
|
|
47
48
|
// Save via storage backend
|
|
48
49
|
const result = await storage.saveLedger({
|
|
49
50
|
project,
|
|
@@ -66,7 +67,7 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
66
67
|
await storage.patchLedger(entryId, {
|
|
67
68
|
embedding: JSON.stringify(embedding),
|
|
68
69
|
});
|
|
69
|
-
|
|
70
|
+
debugLog(`[session_save_ledger] Embedding saved for entry ${entryId}`);
|
|
70
71
|
})
|
|
71
72
|
.catch((err) => {
|
|
72
73
|
console.error(`[session_save_ledger] Embedding generation failed (non-fatal): ${err.message}`);
|
|
@@ -97,13 +98,13 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
97
98
|
}
|
|
98
99
|
const { project, expected_version, open_todos, active_branch, last_summary, key_context, } = args;
|
|
99
100
|
const storage = await getStorage();
|
|
100
|
-
|
|
101
|
+
debugLog(`[session_save_handoff] Saving handoff for project="${project}" ` +
|
|
101
102
|
`(expected_version=${expected_version ?? "none"})`);
|
|
102
103
|
// Auto-extract keywords from summary + context for knowledge accumulation
|
|
103
104
|
const combinedText = [last_summary || "", key_context || ""].filter(Boolean).join(" ");
|
|
104
105
|
const keywords = combinedText ? toKeywordArray(combinedText) : undefined;
|
|
105
106
|
if (keywords) {
|
|
106
|
-
|
|
107
|
+
debugLog(`[session_save_handoff] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
|
|
107
108
|
}
|
|
108
109
|
// Auto-capture Git state for Reality Drift Detection (v2.0 Step 5)
|
|
109
110
|
const gitState = getCurrentGitState();
|
|
@@ -111,7 +112,7 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
111
112
|
if (gitState.isRepo) {
|
|
112
113
|
metadata.git_branch = gitState.branch;
|
|
113
114
|
metadata.last_commit_sha = gitState.commitSha;
|
|
114
|
-
|
|
115
|
+
debugLog(`[session_save_handoff] Git state captured: branch=${gitState.branch}, sha=${gitState.commitSha?.substring(0, 8)}`);
|
|
115
116
|
}
|
|
116
117
|
// Save via storage backend (OCC-aware)
|
|
117
118
|
const data = await storage.saveHandoff({
|
|
@@ -127,7 +128,7 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
127
128
|
}, expected_version ?? null);
|
|
128
129
|
// ─── Handle version conflict ───
|
|
129
130
|
if (data.status === "conflict") {
|
|
130
|
-
|
|
131
|
+
debugLog(`[session_save_handoff] VERSION CONFLICT for "${project}": ` +
|
|
131
132
|
`expected=${expected_version}, current=${data.current_version}`);
|
|
132
133
|
return {
|
|
133
134
|
content: [{
|
|
@@ -213,7 +214,7 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
213
214
|
key_context: ctx.key_context ?? null,
|
|
214
215
|
active_branch: ctx.active_branch ?? null,
|
|
215
216
|
}, newVersion);
|
|
216
|
-
|
|
217
|
+
debugLog(`[AutoCapture] HTML snapshot indexed in visual memory for "${project}"`);
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
220
|
catch (err) {
|
|
@@ -244,14 +245,14 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
244
245
|
const oldKeyContext = oldState?.key_context || ""; // extract old key_context
|
|
245
246
|
// Step 2: Skip merge if old context is empty (nothing to merge with)
|
|
246
247
|
if (!oldKeyContext || oldKeyContext.trim().length === 0) {
|
|
247
|
-
|
|
248
|
+
debugLog("[FactMerger] No old context to merge — skipping");
|
|
248
249
|
return; // first handoff for this project, no merge needed
|
|
249
250
|
}
|
|
250
251
|
// Step 3: Call Gemini to intelligently merge old + new context
|
|
251
252
|
const mergedContext = await consolidateFacts(oldKeyContext, key_context);
|
|
252
253
|
// Step 4: Skip patch if merged result is same as current key_context
|
|
253
254
|
if (mergedContext === key_context) {
|
|
254
|
-
|
|
255
|
+
debugLog("[FactMerger] No changes after merge — skipping patch");
|
|
255
256
|
return; // Gemini determined no contradictions existed
|
|
256
257
|
}
|
|
257
258
|
// Step 5: Silently patch the database with the merged context
|
|
@@ -268,14 +269,14 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
268
269
|
active_branch: active_branch ?? null, // preserve existing branch
|
|
269
270
|
metadata: {}, // no metadata changes
|
|
270
271
|
}, newVersion); // use current version for OCC
|
|
271
|
-
|
|
272
|
+
debugLog("[FactMerger] Context merged and patched for \"" + project + "\"");
|
|
272
273
|
}
|
|
273
274
|
catch (err) {
|
|
274
275
|
// OCC conflict = user saved again while merge was running (expected)
|
|
275
276
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
276
277
|
if (errMsg.includes("conflict") || errMsg.includes("version")) {
|
|
277
278
|
// This is GOOD behavior — user's active input takes precedence
|
|
278
|
-
|
|
279
|
+
debugLog("[FactMerger] Merge skipped due to active session (OCC conflict)");
|
|
279
280
|
}
|
|
280
281
|
else {
|
|
281
282
|
// Unexpected error — log but don't crash
|
|
@@ -319,7 +320,7 @@ export async function sessionLoadContextHandler(args) {
|
|
|
319
320
|
isError: true,
|
|
320
321
|
};
|
|
321
322
|
}
|
|
322
|
-
|
|
323
|
+
debugLog(`[session_load_context] Loading ${level} context for project="${project}"`);
|
|
323
324
|
const storage = await getStorage();
|
|
324
325
|
const data = await storage.loadContext(project, level, PRISM_USER_ID);
|
|
325
326
|
if (!data) {
|
|
@@ -348,7 +349,7 @@ export async function sessionLoadContextHandler(args) {
|
|
|
348
349
|
driftReport = `\n\n⚠️ **CONTEXT SHIFT:** This memory was saved on branch ` +
|
|
349
350
|
`\`${meta.git_branch}\`, but you are currently on branch \`${currentGit.branch}\`. ` +
|
|
350
351
|
`Code may have diverged — review carefully before making changes.`;
|
|
351
|
-
|
|
352
|
+
debugLog(`[session_load_context] Context shift detected: ${meta.git_branch} → ${currentGit.branch}`);
|
|
352
353
|
}
|
|
353
354
|
else if (currentGit.commitSha !== meta.last_commit_sha) {
|
|
354
355
|
// Same branch, different commits — calculate drift
|
|
@@ -358,11 +359,11 @@ export async function sessionLoadContextHandler(args) {
|
|
|
358
359
|
`Since this memory was saved (commit ${meta.last_commit_sha.substring(0, 8)}), ` +
|
|
359
360
|
`the following files were modified outside of agent sessions:\n\`\`\`\n${changes}\n\`\`\`\n` +
|
|
360
361
|
`Please review these files if they overlap with your current task.`;
|
|
361
|
-
|
|
362
|
+
debugLog(`[session_load_context] Reality drift detected! ${changes.split("\n").length} files changed`);
|
|
362
363
|
}
|
|
363
364
|
}
|
|
364
365
|
else {
|
|
365
|
-
|
|
366
|
+
debugLog(`[session_load_context] No drift — repo matches saved state`);
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
369
|
}
|
|
@@ -415,7 +416,7 @@ export async function sessionLoadContextHandler(args) {
|
|
|
415
416
|
if (currentVersion) {
|
|
416
417
|
storage.saveHandoff(handoffUpdate, currentVersion).catch(err => console.error(`[Morning Briefing] Cache save failed (non-fatal): ${err}`));
|
|
417
418
|
}
|
|
418
|
-
|
|
419
|
+
debugLog(`[session_load_context] Morning Briefing generated for "${project}"`);
|
|
419
420
|
}
|
|
420
421
|
catch (err) {
|
|
421
422
|
console.error(`[session_load_context] Morning Briefing failed (non-fatal): ${err}`);
|
|
@@ -424,7 +425,7 @@ export async function sessionLoadContextHandler(args) {
|
|
|
424
425
|
else if (meta?.morning_briefing) {
|
|
425
426
|
// Show the cached briefing (generated within last 4 hours)
|
|
426
427
|
briefingBlock = `\n\n[🌅 MORNING BRIEFING]\n${meta.morning_briefing}`;
|
|
427
|
-
|
|
428
|
+
debugLog(`[session_load_context] Showing cached Morning Briefing for "${project}"`);
|
|
428
429
|
}
|
|
429
430
|
// ─── Visual Memory Index (v2.0 Step 9) ───
|
|
430
431
|
// Show lightweight index of saved images — never loads actual image data
|
|
@@ -436,10 +437,33 @@ export async function sessionLoadContextHandler(args) {
|
|
|
436
437
|
visualMemoryBlock += `- [ID: ${v.id}] ${v.description} (${v.timestamp?.split("T")[0] || "unknown"})\n`;
|
|
437
438
|
});
|
|
438
439
|
}
|
|
440
|
+
const d = data;
|
|
441
|
+
let formattedContext = ``;
|
|
442
|
+
if (d.last_summary)
|
|
443
|
+
formattedContext += `📝 Last Summary: ${d.last_summary}\n`;
|
|
444
|
+
if (d.active_branch)
|
|
445
|
+
formattedContext += `🌿 Active Branch: ${d.active_branch}\n`;
|
|
446
|
+
if (d.key_context)
|
|
447
|
+
formattedContext += `💡 Key Context: ${d.key_context}\n`;
|
|
448
|
+
if (d.pending_todo?.length) {
|
|
449
|
+
formattedContext += `\n✅ Open TODOs:\n` + d.pending_todo.map((t) => ` - ${t}`).join("\n") + `\n`;
|
|
450
|
+
}
|
|
451
|
+
if (d.active_decisions?.length) {
|
|
452
|
+
formattedContext += `\n⚖️ Active Decisions:\n` + d.active_decisions.map((dec) => ` - ${dec}`).join("\n") + `\n`;
|
|
453
|
+
}
|
|
454
|
+
if (d.keywords?.length) {
|
|
455
|
+
formattedContext += `\n🔑 Keywords: ${d.keywords.join(", ")}\n`;
|
|
456
|
+
}
|
|
457
|
+
if (d.recent_sessions?.length) {
|
|
458
|
+
formattedContext += `\n⏳ Recent Sessions:\n` + d.recent_sessions.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
|
|
459
|
+
}
|
|
460
|
+
if (d.session_history?.length) {
|
|
461
|
+
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`;
|
|
462
|
+
}
|
|
439
463
|
return {
|
|
440
464
|
content: [{
|
|
441
465
|
type: "text",
|
|
442
|
-
text: `📋 Session context for "${project}" (${level}):\n\n${
|
|
466
|
+
text: `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${driftReport}${briefingBlock}${visualMemoryBlock}${versionNote}`,
|
|
443
467
|
}],
|
|
444
468
|
isError: false,
|
|
445
469
|
};
|
|
@@ -453,7 +477,7 @@ export async function knowledgeSearchHandler(args) {
|
|
|
453
477
|
throw new Error("Invalid arguments for knowledge_search");
|
|
454
478
|
}
|
|
455
479
|
const { project, query, category, limit = 10 } = args;
|
|
456
|
-
|
|
480
|
+
debugLog(`[knowledge_search] Searching: project=${project || "all"}, query="${query || ""}", category=${category || "any"}, limit=${limit}`);
|
|
457
481
|
const searchKeywords = query ? toKeywordArray(query) : [];
|
|
458
482
|
const storage = await getStorage();
|
|
459
483
|
const data = await storage.searchKnowledge({
|
|
@@ -506,7 +530,7 @@ export async function knowledgeForgetHandler(args) {
|
|
|
506
530
|
isError: true,
|
|
507
531
|
};
|
|
508
532
|
}
|
|
509
|
-
|
|
533
|
+
debugLog(`[knowledge_forget] ${dry_run ? "DRY RUN: " : ""}Forgetting: ` +
|
|
510
534
|
`project=${project || "ALL"}, category=${category || "any"}, ` +
|
|
511
535
|
`older_than=${older_than_days || "any"}d, clear_handoff=${clear_handoff}`);
|
|
512
536
|
const storage = await getStorage();
|
|
@@ -564,7 +588,7 @@ export async function sessionSearchMemoryHandler(args) {
|
|
|
564
588
|
throw new Error("Invalid arguments for session_search_memory");
|
|
565
589
|
}
|
|
566
590
|
const { query, project, limit = 5, similarity_threshold = 0.7, } = args;
|
|
567
|
-
|
|
591
|
+
debugLog(`[session_search_memory] Semantic search: query="${query}", ` +
|
|
568
592
|
`project=${project || "all"}, limit=${limit}, threshold=${similarity_threshold}`);
|
|
569
593
|
// Step 1: Generate embedding for the search query
|
|
570
594
|
if (!GOOGLE_API_KEY) {
|
|
@@ -672,7 +696,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
672
696
|
}
|
|
673
697
|
const { project, limit = 20, dry_run = false } = args;
|
|
674
698
|
const safeLimit = Math.min(limit, 50);
|
|
675
|
-
|
|
699
|
+
debugLog(`[backfill_embeddings] ${dry_run ? "DRY RUN: " : ""}` +
|
|
676
700
|
`project=${project || "all"}, limit=${safeLimit}`);
|
|
677
701
|
const storage = await getStorage();
|
|
678
702
|
// Find entries missing embeddings
|
|
@@ -721,7 +745,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
721
745
|
...(e.decisions || []),
|
|
722
746
|
].filter(Boolean).join(" | ");
|
|
723
747
|
if (!textToEmbed.trim()) {
|
|
724
|
-
|
|
748
|
+
debugLog(`[backfill] Skipping entry ${e.id}: no text content`);
|
|
725
749
|
failed++;
|
|
726
750
|
continue;
|
|
727
751
|
}
|
|
@@ -730,7 +754,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
730
754
|
embedding: JSON.stringify(embedding),
|
|
731
755
|
});
|
|
732
756
|
repaired++;
|
|
733
|
-
|
|
757
|
+
debugLog(`[backfill] ✅ Repaired ${e.id} (${e.project})`);
|
|
734
758
|
}
|
|
735
759
|
catch (err) {
|
|
736
760
|
failed++;
|
|
@@ -762,7 +786,7 @@ export async function memoryHistoryHandler(args) {
|
|
|
762
786
|
}
|
|
763
787
|
const { project, limit = 10 } = args;
|
|
764
788
|
const storage = await getStorage();
|
|
765
|
-
|
|
789
|
+
debugLog(`[memory_history] Fetching history for project="${project}" (limit=${limit})`);
|
|
766
790
|
const history = await storage.getHistory(project, PRISM_USER_ID, Math.min(limit, 50));
|
|
767
791
|
if (history.length === 0) {
|
|
768
792
|
return {
|
|
@@ -803,7 +827,7 @@ export async function memoryCheckoutHandler(args) {
|
|
|
803
827
|
}
|
|
804
828
|
const { project, target_version } = args;
|
|
805
829
|
const storage = await getStorage();
|
|
806
|
-
|
|
830
|
+
debugLog(`[memory_checkout] Reverting project="${project}" to version ${target_version}`);
|
|
807
831
|
// 1. Find the target snapshot
|
|
808
832
|
const history = await storage.getHistory(project, PRISM_USER_ID, 50);
|
|
809
833
|
const targetState = history.find(h => h.version === target_version);
|
|
@@ -955,7 +979,7 @@ export async function sessionSaveImageHandler(args) {
|
|
|
955
979
|
await storage.saveHandoff(handoffUpdate, currentVersion);
|
|
956
980
|
const fileSize = fs.statSync(vaultPath).size;
|
|
957
981
|
const sizeKB = (fileSize / 1024).toFixed(1);
|
|
958
|
-
|
|
982
|
+
debugLog(`[Visual Memory] Saved image [${imageId}] for "${project}" (${sizeKB}KB, ${ext})`);
|
|
959
983
|
return {
|
|
960
984
|
content: [{
|
|
961
985
|
type: "text",
|
|
@@ -1025,7 +1049,7 @@ export async function sessionViewImageHandler(args) {
|
|
|
1025
1049
|
};
|
|
1026
1050
|
const mimeType = MIME_MAP[ext] || "image/png";
|
|
1027
1051
|
const fileSize = fs.statSync(vaultPath).size;
|
|
1028
|
-
|
|
1052
|
+
debugLog(`[Visual Memory] Retrieved image [${image_id}] for "${project}" (${(fileSize / 1024).toFixed(1)}KB)`);
|
|
1029
1053
|
// Return MCP content array with text + image
|
|
1030
1054
|
return {
|
|
1031
1055
|
content: [
|
|
@@ -1069,7 +1093,7 @@ export async function sessionHealthCheckHandler(args) {
|
|
|
1069
1093
|
};
|
|
1070
1094
|
}
|
|
1071
1095
|
const autoFix = args.auto_fix || false; // default: read-only scan
|
|
1072
|
-
|
|
1096
|
+
debugLog("[Health Check] Running fsck (auto_fix=" + autoFix + ")");
|
|
1073
1097
|
try {
|
|
1074
1098
|
// Get the storage backend (SQLite or Supabase)
|
|
1075
1099
|
const storage = await getStorage();
|
|
@@ -1082,11 +1106,11 @@ export async function sessionHealthCheckHandler(args) {
|
|
|
1082
1106
|
if (autoFix && report.issues.length > 0) {
|
|
1083
1107
|
const embeddingIssue = report.issues.find(i => i.check === "missing_embeddings");
|
|
1084
1108
|
if (embeddingIssue && embeddingIssue.count > 0) {
|
|
1085
|
-
|
|
1109
|
+
debugLog("[Health Check] Auto-fixing " + embeddingIssue.count + " missing embeddings...");
|
|
1086
1110
|
try {
|
|
1087
1111
|
await backfillEmbeddingsHandler({ dry_run: false, limit: 50 });
|
|
1088
1112
|
fixedCount += embeddingIssue.count;
|
|
1089
|
-
|
|
1113
|
+
debugLog("[Health Check] Backfill complete.");
|
|
1090
1114
|
}
|
|
1091
1115
|
catch (err) {
|
|
1092
1116
|
console.error("[Health Check] Backfill failed: " + err);
|
package/dist/utils/briefing.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
16
16
|
import { GOOGLE_API_KEY } from "../config.js";
|
|
17
|
+
import { debugLog } from "./logger.js";
|
|
17
18
|
/**
|
|
18
19
|
* Generates a 3-bullet Morning Briefing using Gemini.
|
|
19
20
|
*
|
|
@@ -58,7 +59,7 @@ Rules:
|
|
|
58
59
|
try {
|
|
59
60
|
const result = await model.generateContent(prompt);
|
|
60
61
|
const text = result.response.text().trim();
|
|
61
|
-
|
|
62
|
+
debugLog(`[Morning Briefing] Generated for "${context.project}" (${text.length} chars)`);
|
|
62
63
|
return text;
|
|
63
64
|
}
|
|
64
65
|
catch (error) {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import { GoogleGenerativeAI, TaskType } from "@google/generative-ai";
|
|
36
36
|
import { GOOGLE_API_KEY } from "../config.js";
|
|
37
|
+
import { debugLog } from "./logger.js";
|
|
37
38
|
// ─── Constants ────────────────────────────────────────────────
|
|
38
39
|
// REVIEWER NOTE: Maximum characters to send to the embedding API.
|
|
39
40
|
// gemini-embedding-001 supports up to 2048 tokens. At ~4 chars/token,
|
|
@@ -68,7 +69,7 @@ export async function generateEmbedding(text) {
|
|
|
68
69
|
// Fix: truncate at the last word boundary before the limit.
|
|
69
70
|
let inputText = text;
|
|
70
71
|
if (inputText.length > MAX_EMBEDDING_CHARS) {
|
|
71
|
-
|
|
72
|
+
debugLog(`[embedding] Input text truncated from ${inputText.length} to ~${MAX_EMBEDDING_CHARS} chars (word-safe)`);
|
|
72
73
|
inputText = inputText.substring(0, MAX_EMBEDDING_CHARS);
|
|
73
74
|
// Snap back to the last space to avoid splitting a word or surrogate pair
|
|
74
75
|
const lastSpace = inputText.lastIndexOf(' ');
|
|
@@ -83,7 +84,7 @@ export async function generateEmbedding(text) {
|
|
|
83
84
|
const genAI = new GoogleGenerativeAI(GOOGLE_API_KEY);
|
|
84
85
|
const model = genAI.getGenerativeModel({ model: "gemini-embedding-001" }, { apiVersion: "v1beta" } // gemini-embedding-001 requires v1beta
|
|
85
86
|
);
|
|
86
|
-
|
|
87
|
+
debugLog(`[embedding] Generating 768-dim embedding for ${inputText.length} chars`);
|
|
87
88
|
const result = await model.embedContent({
|
|
88
89
|
content: {
|
|
89
90
|
role: "user",
|
package/dist/utils/factMerger.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { GoogleGenerativeAI } from "@google/generative-ai"; // Gemini SDK for LLM calls
|
|
37
37
|
import { GOOGLE_API_KEY } from "../config.js"; // API key from environment
|
|
38
|
+
import { debugLog } from "./logger.js";
|
|
38
39
|
/**
|
|
39
40
|
* Merge old and new key_context using Gemini to resolve contradictions.
|
|
40
41
|
*
|
|
@@ -57,7 +58,7 @@ import { GOOGLE_API_KEY } from "../config.js"; // API key from environment
|
|
|
57
58
|
export async function consolidateFacts(oldContext, newContext) {
|
|
58
59
|
// Guard: need API key to call Gemini
|
|
59
60
|
if (!GOOGLE_API_KEY) {
|
|
60
|
-
|
|
61
|
+
debugLog("[FactMerger] Skipped — no GOOGLE_API_KEY configured");
|
|
61
62
|
return newContext; // fallback: just use the new context as-is
|
|
62
63
|
}
|
|
63
64
|
// Guard: if either context is empty, no merging needed
|
|
@@ -69,7 +70,7 @@ export async function consolidateFacts(oldContext, newContext) {
|
|
|
69
70
|
}
|
|
70
71
|
// Guard: if old and new are identical, skip the LLM call entirely
|
|
71
72
|
if (oldContext.trim() === newContext.trim()) {
|
|
72
|
-
|
|
73
|
+
debugLog("[FactMerger] Old and new context are identical — skipping merge");
|
|
73
74
|
return newContext; // no changes needed
|
|
74
75
|
}
|
|
75
76
|
// Initialize Gemini with the configured API key
|
|
@@ -96,7 +97,7 @@ export async function consolidateFacts(oldContext, newContext) {
|
|
|
96
97
|
// Extract and trim the merged text from Gemini's response
|
|
97
98
|
const mergedText = result.response.text().trim();
|
|
98
99
|
// Log the merge result for debugging (to stderr, not stdout)
|
|
99
|
-
|
|
100
|
+
debugLog("[FactMerger] Merged context (" +
|
|
100
101
|
oldContext.length + " chars old + " +
|
|
101
102
|
newContext.length + " chars new → " +
|
|
102
103
|
mergedText.length + " chars merged)");
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { GoogleGenerativeAI } from "@google/generative-ai"; // Gemini SDK
|
|
25
25
|
import { GOOGLE_API_KEY } from "../config.js"; // API key from env
|
|
26
|
+
import { debugLog } from "./logger.js";
|
|
26
27
|
/**
|
|
27
28
|
* Scan agent memory for prompt injection attacks.
|
|
28
29
|
*
|
|
@@ -44,7 +45,7 @@ import { GOOGLE_API_KEY } from "../config.js"; // API key from env
|
|
|
44
45
|
export async function scanForPromptInjection(projectContext) {
|
|
45
46
|
// No API key = skip scan gracefully (don't block health check)
|
|
46
47
|
if (!GOOGLE_API_KEY) {
|
|
47
|
-
|
|
48
|
+
debugLog("[Security Scan] Skipped — no GOOGLE_API_KEY configured");
|
|
48
49
|
return { safe: true }; // assume safe when we can't check
|
|
49
50
|
}
|
|
50
51
|
// Don't scan empty context — nothing to analyze
|
|
@@ -81,7 +82,7 @@ export async function scanForPromptInjection(projectContext) {
|
|
|
81
82
|
.replace(/```/g, "") // remove ```
|
|
82
83
|
.trim(); // trim whitespace
|
|
83
84
|
const parsed = JSON.parse(cleaned); // parse JSON
|
|
84
|
-
|
|
85
|
+
debugLog("[Security Scan] Result: safe=" + parsed.safe +
|
|
85
86
|
(parsed.reason ? ", reason=" + parsed.reason : ""));
|
|
86
87
|
return {
|
|
87
88
|
safe: Boolean(parsed.safe), // normalize to boolean
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PRISM_DEBUG_LOGGING } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Logs a message to stderr only if PRISM_DEBUG_LOGGING is true.
|
|
4
|
+
* Use this for verbose traces (e.g., initialization, request tracking)
|
|
5
|
+
* that should be hidden from users by default but remain available
|
|
6
|
+
* for troubleshooting.
|
|
7
|
+
*/
|
|
8
|
+
export function debugLog(message) {
|
|
9
|
+
if (PRISM_DEBUG_LOGGING) {
|
|
10
|
+
console.error(message);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.11",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-mcp",
|
|
5
5
|
"description": "The Mind Palace for AI Agents — local-first MCP server with persistent memory (SQLite/Supabase), visual dashboard, time travel, multi-agent sync, Morning Briefings, reality drift detection, code mode templates, semantic vector search, and Brave Search + Gemini analysis. Zero-config local mode.",
|
|
6
6
|
"module": "index.ts",
|