prism-mcp-server 4.3.0 → 4.6.1
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 +259 -81
- package/dist/dashboard/ui.js +333 -2
- package/dist/lifecycle.js +6 -0
- package/dist/server.js +234 -147
- package/dist/storage/sqlite.js +21 -0
- package/dist/storage/supabase.js +49 -15
- package/dist/storage/supabaseMigrations.js +42 -1
- package/dist/tools/compactionHandler.js +7 -14
- package/dist/tools/handlers.js +26 -3
- package/dist/tools/index.js +2 -2
- package/dist/tools/sessionMemoryDefinitions.js +53 -0
- package/dist/tools/sessionMemoryHandlers.js +263 -30
- package/dist/utils/briefing.js +9 -10
- package/dist/utils/factMerger.js +11 -16
- package/dist/utils/healthCheck.js +19 -22
- package/dist/utils/imageCaptioner.js +240 -0
- package/dist/utils/llm/adapters/anthropic.js +128 -0
- package/dist/utils/llm/adapters/gemini.js +152 -0
- package/dist/utils/llm/adapters/openai.js +183 -0
- package/dist/utils/llm/adapters/traced.js +190 -0
- package/dist/utils/llm/factory.js +143 -0
- package/dist/utils/llm/provider.js +25 -0
- package/dist/utils/telemetry.js +174 -0
- package/package.json +9 -2
package/dist/server.js
CHANGED
|
@@ -70,6 +70,8 @@ import { acquireLock, registerShutdownHandlers } from "./lifecycle.js";
|
|
|
70
70
|
// correct backend (Supabase or SQLite) with proper error handling.
|
|
71
71
|
import { getStorage } from "./storage/index.js";
|
|
72
72
|
import { getSettingSync, initConfigStorage } from "./storage/configStorage.js";
|
|
73
|
+
import { getTracer, initTelemetry } from "./utils/telemetry.js";
|
|
74
|
+
import { context as otelContext, trace, SpanStatusCode } from "@opentelemetry/api";
|
|
73
75
|
// ─── Import Tool Definitions (schemas) and Handlers (implementations) ─────
|
|
74
76
|
import { WEB_SEARCH_TOOL, BRAVE_WEB_SEARCH_CODE_MODE_TOOL, LOCAL_SEARCH_TOOL, BRAVE_LOCAL_SEARCH_CODE_MODE_TOOL, CODE_MODE_TRANSFORM_TOOL, BRAVE_ANSWERS_TOOL, RESEARCH_PAPER_ANALYSIS_TOOL, webSearchHandler, braveWebSearchCodeModeHandler, localSearchHandler, braveLocalSearchCodeModeHandler, codeModeTransformHandler, braveAnswersHandler, researchPaperAnalysisHandler, } from "./tools/index.js";
|
|
75
77
|
// Session memory tools — only used if Supabase is configured
|
|
@@ -84,6 +86,8 @@ SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL,
|
|
|
84
86
|
SESSION_HEALTH_CHECK_TOOL,
|
|
85
87
|
// ─── Phase 2: GDPR Memory Deletion tool definition ───
|
|
86
88
|
SESSION_FORGET_MEMORY_TOOL,
|
|
89
|
+
// ─── Phase 2: GDPR Export tool definition ───
|
|
90
|
+
SESSION_EXPORT_MEMORY_TOOL,
|
|
87
91
|
// ─── v3.1: TTL Retention tool ───
|
|
88
92
|
KNOWLEDGE_SET_RETENTION_TOOL,
|
|
89
93
|
// v4.0: Active Behavioral Memory tools
|
|
@@ -98,6 +102,8 @@ sessionSaveImageHandler, sessionViewImageHandler,
|
|
|
98
102
|
sessionHealthCheckHandler,
|
|
99
103
|
// ─── Phase 2: GDPR Memory Deletion handler ───
|
|
100
104
|
sessionForgetMemoryHandler,
|
|
105
|
+
// ─── Phase 2: GDPR Export handler ───
|
|
106
|
+
sessionExportMemoryHandler,
|
|
101
107
|
// ─── v3.1: TTL Retention handler ───
|
|
102
108
|
knowledgeSetRetentionHandler,
|
|
103
109
|
// v4.0: Active Behavioral Memory handlers
|
|
@@ -128,14 +134,20 @@ const BASE_TOOLS = [
|
|
|
128
134
|
// initConfigStorage() has run, so getSettingSync() returns real
|
|
129
135
|
// dashboard config values.
|
|
130
136
|
function buildSessionMemoryTools(autoloadList) {
|
|
131
|
-
// Dynamically inject auto-load instruction into session_load_context description
|
|
137
|
+
// Dynamically inject auto-load instruction into session_load_context description.
|
|
138
|
+
// CRITICAL: The instruction is PREPENDED so it appears FIRST in the tool description.
|
|
139
|
+
// LLMs read top-to-bottom — appending the instruction at the end caused it to be
|
|
140
|
+
// deprioritized and skipped by both Antigravity and Claude CLI.
|
|
132
141
|
let loadContextTool = SESSION_LOAD_CONTEXT_TOOL;
|
|
133
142
|
if (autoloadList.length > 0) {
|
|
134
143
|
const projectList = autoloadList.join(", ");
|
|
144
|
+
const autoLoadPrefix = `IMPORTANT: At the start of every conversation, you MUST call this tool ` +
|
|
145
|
+
`for each of these projects: ${projectList}. Do this before any other work. ` +
|
|
146
|
+
`Load level should be "standard" unless the user specifies otherwise. ` +
|
|
147
|
+
`Do NOT skip this. Do NOT claim the tool is unavailable. The tool IS available — just call it.\n\n`;
|
|
135
148
|
loadContextTool = {
|
|
136
149
|
...SESSION_LOAD_CONTEXT_TOOL,
|
|
137
|
-
description: SESSION_LOAD_CONTEXT_TOOL.description
|
|
138
|
-
`\n\nIMPORTANT: At the start of every conversation, you MUST call this tool for each of these projects: ${projectList}. Do this before any other work. Load level should be "standard" unless the user specifies otherwise.`,
|
|
150
|
+
description: autoLoadPrefix + SESSION_LOAD_CONTEXT_TOOL.description,
|
|
139
151
|
};
|
|
140
152
|
}
|
|
141
153
|
return [
|
|
@@ -163,6 +175,8 @@ function buildSessionMemoryTools(autoloadList) {
|
|
|
163
175
|
KNOWLEDGE_DOWNVOTE_TOOL, // knowledge_downvote — decrease entry importance
|
|
164
176
|
// ─── v4.2: Knowledge Sync Rules tool ───
|
|
165
177
|
KNOWLEDGE_SYNC_RULES_TOOL, // knowledge_sync_rules — sync graduated insights to IDE rules files
|
|
178
|
+
// ─── Phase 2: GDPR Export tool ───
|
|
179
|
+
SESSION_EXPORT_MEMORY_TOOL, // session_export_memory — full portability export (Article 20)
|
|
166
180
|
];
|
|
167
181
|
}
|
|
168
182
|
// ─── v0.4.0: Resource Subscription Tracking ──────────────────────
|
|
@@ -538,153 +552,221 @@ export function createServer() {
|
|
|
538
552
|
// - session_search_memory (Enhancement #4)
|
|
539
553
|
// The server reference is passed to sessionSaveHandoffHandler so it
|
|
540
554
|
// can trigger resource update notifications on successful saves.
|
|
555
|
+
//
|
|
556
|
+
// v4.6.0: Every tool call is wrapped in a root OTel span (mcp.call_tool).
|
|
557
|
+
// The span is parented via AsyncLocalStorage context propagation — all
|
|
558
|
+
// child spans from LLM adapters and background workers are automatically
|
|
559
|
+
// nested under this root span in Jaeger/Zipkin without explicit ref-passing.
|
|
560
|
+
// When otel_enabled=false, getTracer() returns a no-op tracer — zero overhead.
|
|
541
561
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
562
|
+
const { name, arguments: args } = request.params;
|
|
563
|
+
// Start the root span for this MCP tool invocation.
|
|
564
|
+
// All child spans (llm.generate_text, worker.vlm_caption, etc.) are
|
|
565
|
+
// automatically parented to this span via the propagated context.
|
|
566
|
+
const rootSpan = getTracer().startSpan("mcp.call_tool", {
|
|
567
|
+
attributes: {
|
|
568
|
+
"tool.name": name,
|
|
569
|
+
// Capture the project attribute if present (most memory tools have it)
|
|
570
|
+
"project": args?.project ?? "unknown",
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
// context.with() sets the root span as the active span for the duration
|
|
574
|
+
// of this async operation. AsyncLocalStorage ensures the context flows
|
|
575
|
+
// through await chains — including fire-and-forget workers launched
|
|
576
|
+
// within the handler body (e.g. imageCaptioner, embeddings backfill).
|
|
577
|
+
return otelContext.with(trace.setSpan(otelContext.active(), rootSpan), async () => {
|
|
578
|
+
try {
|
|
579
|
+
if (!args) {
|
|
580
|
+
throw new Error("No arguments provided");
|
|
581
|
+
}
|
|
582
|
+
let result;
|
|
583
|
+
switch (name) {
|
|
584
|
+
// ── Search & Analysis Tools (always available) ──
|
|
585
|
+
case "brave_web_search":
|
|
586
|
+
result = await webSearchHandler(args);
|
|
587
|
+
break;
|
|
588
|
+
case "brave_web_search_code_mode":
|
|
589
|
+
result = await braveWebSearchCodeModeHandler(args);
|
|
590
|
+
break;
|
|
591
|
+
case "brave_local_search":
|
|
592
|
+
result = await localSearchHandler(args);
|
|
593
|
+
break;
|
|
594
|
+
case "brave_local_search_code_mode":
|
|
595
|
+
result = await braveLocalSearchCodeModeHandler(args);
|
|
596
|
+
break;
|
|
597
|
+
case "code_mode_transform":
|
|
598
|
+
result = await codeModeTransformHandler(args);
|
|
599
|
+
break;
|
|
600
|
+
case "brave_answers":
|
|
601
|
+
result = await braveAnswersHandler(args);
|
|
602
|
+
break;
|
|
603
|
+
case "gemini_research_paper_analysis":
|
|
604
|
+
result = await researchPaperAnalysisHandler(args);
|
|
605
|
+
break;
|
|
606
|
+
// ── Session Memory Tools (only callable when Supabase is configured) ──
|
|
607
|
+
// REVIEWER NOTE: Even though these tools won't appear in the
|
|
608
|
+
// tool list without Supabase, we still guard each handler call
|
|
609
|
+
// in case of direct invocation.
|
|
610
|
+
case "session_save_ledger":
|
|
611
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
612
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
613
|
+
result = await sessionSaveLedgerHandler(args);
|
|
614
|
+
break;
|
|
615
|
+
case "session_save_handoff":
|
|
616
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
617
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
618
|
+
// REVIEWER NOTE: v0.4.0 passes the server reference so the
|
|
619
|
+
// handler can trigger resource update notifications after
|
|
620
|
+
// a successful save. See notifyResourceUpdate() above.
|
|
621
|
+
result = await sessionSaveHandoffHandler(args, server);
|
|
622
|
+
break;
|
|
623
|
+
case "session_load_context":
|
|
624
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
625
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
626
|
+
result = await sessionLoadContextHandler(args);
|
|
627
|
+
break;
|
|
628
|
+
case "knowledge_search":
|
|
629
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
630
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
631
|
+
result = await knowledgeSearchHandler(args);
|
|
632
|
+
break;
|
|
633
|
+
case "knowledge_forget":
|
|
634
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
635
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
636
|
+
result = await knowledgeForgetHandler(args);
|
|
637
|
+
break;
|
|
638
|
+
// ─── v0.4.0: New Session Memory Tools ───
|
|
639
|
+
case "session_compact_ledger":
|
|
640
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
641
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
642
|
+
result = await compactLedgerHandler(args);
|
|
643
|
+
break;
|
|
644
|
+
case "session_search_memory":
|
|
645
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
646
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
647
|
+
result = await sessionSearchMemoryHandler(args);
|
|
648
|
+
break;
|
|
649
|
+
// ─── v2.0: Time Travel Tools ───
|
|
650
|
+
case "memory_history":
|
|
651
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
652
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
653
|
+
result = await memoryHistoryHandler(args);
|
|
654
|
+
break;
|
|
655
|
+
case "memory_checkout":
|
|
656
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
657
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
658
|
+
result = await memoryCheckoutHandler(args);
|
|
659
|
+
break;
|
|
660
|
+
// ─── v2.0: Visual Memory Tools ───
|
|
661
|
+
case "session_save_image":
|
|
662
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
663
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
664
|
+
result = await sessionSaveImageHandler(args);
|
|
665
|
+
break;
|
|
666
|
+
case "session_view_image":
|
|
667
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
668
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
669
|
+
result = await sessionViewImageHandler(args);
|
|
670
|
+
break;
|
|
671
|
+
// ─── v2.2.0: Health Check Tool ───
|
|
672
|
+
case "session_health_check":
|
|
673
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
674
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
675
|
+
result = await sessionHealthCheckHandler(args);
|
|
676
|
+
break;
|
|
677
|
+
// ─── Phase 2: GDPR Memory Deletion Tool ───
|
|
678
|
+
case "session_forget_memory":
|
|
679
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
680
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
681
|
+
result = await sessionForgetMemoryHandler(args);
|
|
682
|
+
break;
|
|
683
|
+
// ─── Phase 2: GDPR Export Tool ───
|
|
684
|
+
case "session_export_memory":
|
|
685
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
686
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
687
|
+
result = await sessionExportMemoryHandler(args);
|
|
688
|
+
break;
|
|
689
|
+
case "knowledge_set_retention":
|
|
690
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
691
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
692
|
+
result = await knowledgeSetRetentionHandler(args);
|
|
693
|
+
break;
|
|
694
|
+
// ─── v4.0: Active Behavioral Memory Tools ───
|
|
695
|
+
case "session_save_experience":
|
|
696
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
697
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
698
|
+
result = await sessionSaveExperienceHandler(args);
|
|
699
|
+
break;
|
|
700
|
+
case "knowledge_upvote":
|
|
701
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
702
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
703
|
+
result = await knowledgeUpvoteHandler(args);
|
|
704
|
+
break;
|
|
705
|
+
case "knowledge_downvote":
|
|
706
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
707
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
708
|
+
result = await knowledgeDownvoteHandler(args);
|
|
709
|
+
break;
|
|
710
|
+
// ─── v4.2: Knowledge Sync Rules Tool ───
|
|
711
|
+
case "knowledge_sync_rules":
|
|
712
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
713
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
714
|
+
result = await knowledgeSyncRulesHandler(args);
|
|
715
|
+
break;
|
|
716
|
+
// ─── v3.0: Agent Hivemind Tools ───
|
|
717
|
+
case "agent_register":
|
|
718
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
719
|
+
throw new Error("Session memory not configured.");
|
|
720
|
+
if (!PRISM_ENABLE_HIVEMIND)
|
|
721
|
+
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
722
|
+
result = await agentRegisterHandler(args);
|
|
723
|
+
break;
|
|
724
|
+
case "agent_heartbeat":
|
|
725
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
726
|
+
throw new Error("Session memory not configured.");
|
|
727
|
+
if (!PRISM_ENABLE_HIVEMIND)
|
|
728
|
+
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
729
|
+
result = await agentHeartbeatHandler(args);
|
|
730
|
+
break;
|
|
731
|
+
case "agent_list_team":
|
|
732
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
733
|
+
throw new Error("Session memory not configured.");
|
|
734
|
+
if (!PRISM_ENABLE_HIVEMIND)
|
|
735
|
+
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
736
|
+
result = await agentListTeamHandler(args);
|
|
737
|
+
break;
|
|
738
|
+
default:
|
|
739
|
+
result = {
|
|
740
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
741
|
+
isError: true,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
rootSpan.setStatus({ code: SpanStatusCode.OK });
|
|
745
|
+
return result;
|
|
546
746
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// ── Session Memory Tools (only callable when Supabase is configured) ──
|
|
564
|
-
// REVIEWER NOTE: Even though these tools won't appear in the
|
|
565
|
-
// tool list without Supabase, we still guard each handler call
|
|
566
|
-
// in case of direct invocation.
|
|
567
|
-
case "session_save_ledger":
|
|
568
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
569
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
570
|
-
return await sessionSaveLedgerHandler(args);
|
|
571
|
-
case "session_save_handoff":
|
|
572
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
573
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
574
|
-
// REVIEWER NOTE: v0.4.0 passes the server reference so the
|
|
575
|
-
// handler can trigger resource update notifications after
|
|
576
|
-
// a successful save. See notifyResourceUpdate() above.
|
|
577
|
-
return await sessionSaveHandoffHandler(args, server);
|
|
578
|
-
case "session_load_context":
|
|
579
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
580
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
581
|
-
return await sessionLoadContextHandler(args);
|
|
582
|
-
case "knowledge_search":
|
|
583
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
584
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
585
|
-
return await knowledgeSearchHandler(args);
|
|
586
|
-
case "knowledge_forget":
|
|
587
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
588
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
589
|
-
return await knowledgeForgetHandler(args);
|
|
590
|
-
// ─── v0.4.0: New Session Memory Tools ───
|
|
591
|
-
case "session_compact_ledger":
|
|
592
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
593
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
594
|
-
return await compactLedgerHandler(args);
|
|
595
|
-
case "session_search_memory":
|
|
596
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
597
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
598
|
-
return await sessionSearchMemoryHandler(args);
|
|
599
|
-
// ─── v2.0: Time Travel Tools ───
|
|
600
|
-
case "memory_history":
|
|
601
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
602
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
603
|
-
return await memoryHistoryHandler(args);
|
|
604
|
-
case "memory_checkout":
|
|
605
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
606
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
607
|
-
return await memoryCheckoutHandler(args);
|
|
608
|
-
// ─── v2.0: Visual Memory Tools ───
|
|
609
|
-
case "session_save_image":
|
|
610
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
611
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
612
|
-
return await sessionSaveImageHandler(args);
|
|
613
|
-
case "session_view_image":
|
|
614
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
615
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
616
|
-
return await sessionViewImageHandler(args);
|
|
617
|
-
// ─── v2.2.0: Health Check Tool ───
|
|
618
|
-
case "session_health_check":
|
|
619
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
620
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
621
|
-
return await sessionHealthCheckHandler(args);
|
|
622
|
-
// ─── Phase 2: GDPR Memory Deletion Tool ───
|
|
623
|
-
case "session_forget_memory":
|
|
624
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
625
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
626
|
-
return await sessionForgetMemoryHandler(args);
|
|
627
|
-
// ─── v3.1: TTL Retention Tool ───
|
|
628
|
-
case "knowledge_set_retention":
|
|
629
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
630
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
631
|
-
return await knowledgeSetRetentionHandler(args);
|
|
632
|
-
// ─── v4.0: Active Behavioral Memory Tools ───
|
|
633
|
-
case "session_save_experience":
|
|
634
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
635
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
636
|
-
return await sessionSaveExperienceHandler(args);
|
|
637
|
-
case "knowledge_upvote":
|
|
638
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
639
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
640
|
-
return await knowledgeUpvoteHandler(args);
|
|
641
|
-
case "knowledge_downvote":
|
|
642
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
643
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
644
|
-
return await knowledgeDownvoteHandler(args);
|
|
645
|
-
// ─── v4.2: Knowledge Sync Rules Tool ───
|
|
646
|
-
case "knowledge_sync_rules":
|
|
647
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
648
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
649
|
-
return await knowledgeSyncRulesHandler(args);
|
|
650
|
-
// ─── v3.0: Agent Hivemind Tools ───
|
|
651
|
-
case "agent_register":
|
|
652
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
653
|
-
throw new Error("Session memory not configured.");
|
|
654
|
-
if (!PRISM_ENABLE_HIVEMIND)
|
|
655
|
-
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
656
|
-
return await agentRegisterHandler(args);
|
|
657
|
-
case "agent_heartbeat":
|
|
658
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
659
|
-
throw new Error("Session memory not configured.");
|
|
660
|
-
if (!PRISM_ENABLE_HIVEMIND)
|
|
661
|
-
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
662
|
-
return await agentHeartbeatHandler(args);
|
|
663
|
-
case "agent_list_team":
|
|
664
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
665
|
-
throw new Error("Session memory not configured.");
|
|
666
|
-
if (!PRISM_ENABLE_HIVEMIND)
|
|
667
|
-
throw new Error("Hivemind not enabled. Set PRISM_ENABLE_HIVEMIND=true.");
|
|
668
|
-
return await agentListTeamHandler(args);
|
|
669
|
-
default:
|
|
670
|
-
return {
|
|
671
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
672
|
-
isError: true,
|
|
673
|
-
};
|
|
747
|
+
catch (error) {
|
|
748
|
+
console.error(`Error in tool handler: ${error instanceof Error ? error.message : String(error)}`);
|
|
749
|
+
rootSpan.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
750
|
+
rootSpan.setStatus({
|
|
751
|
+
code: SpanStatusCode.ERROR,
|
|
752
|
+
message: error instanceof Error ? error.message : String(error),
|
|
753
|
+
});
|
|
754
|
+
return {
|
|
755
|
+
content: [
|
|
756
|
+
{
|
|
757
|
+
type: "text",
|
|
758
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
759
|
+
},
|
|
760
|
+
],
|
|
761
|
+
isError: true,
|
|
762
|
+
};
|
|
674
763
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
type: "text",
|
|
682
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
683
|
-
},
|
|
684
|
-
],
|
|
685
|
-
isError: true,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
764
|
+
finally {
|
|
765
|
+
// Always end the root span — even on error — to avoid span leaks
|
|
766
|
+
// in the BatchSpanProcessor's in-memory queue.
|
|
767
|
+
rootSpan.end();
|
|
768
|
+
}
|
|
769
|
+
});
|
|
688
770
|
});
|
|
689
771
|
return server;
|
|
690
772
|
}
|
|
@@ -763,6 +845,11 @@ export async function startServer() {
|
|
|
763
845
|
// during the Initialize handshake — zero extra latency for resource reads.
|
|
764
846
|
// initConfigStorage() is local SQLite only (~5ms), safe to await.
|
|
765
847
|
await initConfigStorage();
|
|
848
|
+
// v4.6.0: Initialize OTel AFTER the settings cache is warm so that
|
|
849
|
+
// initTelemetry() can read otel_enabled/otel_endpoint from getSettingSync()
|
|
850
|
+
// synchronously. This is a synchronous call — no await needed.
|
|
851
|
+
// No-op when otel_enabled=false (the default).
|
|
852
|
+
initTelemetry();
|
|
766
853
|
const server = createServer();
|
|
767
854
|
const transport = new StdioServerTransport();
|
|
768
855
|
await server.connect(transport);
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -1422,4 +1422,25 @@ export class SqliteStorage {
|
|
|
1422
1422
|
conversation_id: "",
|
|
1423
1423
|
}));
|
|
1424
1424
|
}
|
|
1425
|
+
// ─── v4.3: Standalone Importance Decay ────────────────────
|
|
1426
|
+
//
|
|
1427
|
+
// Extracted from expireByTTL so decay can be triggered independently
|
|
1428
|
+
// (e.g. fire-and-forget from session_save_ledger) without needing
|
|
1429
|
+
// an active TTL retention policy.
|
|
1430
|
+
async decayImportance(project, userId, decayDays) {
|
|
1431
|
+
const result = await this.db.execute({
|
|
1432
|
+
sql: `UPDATE session_ledger
|
|
1433
|
+
SET importance = MAX(0, importance - 1)
|
|
1434
|
+
WHERE project = ? AND user_id = ?
|
|
1435
|
+
AND importance > 0
|
|
1436
|
+
AND event_type != 'session'
|
|
1437
|
+
AND created_at < datetime('now', '-' || ? || ' days')
|
|
1438
|
+
AND deleted_at IS NULL`,
|
|
1439
|
+
args: [project, userId, decayDays],
|
|
1440
|
+
});
|
|
1441
|
+
const decayed = result.rowsAffected || 0;
|
|
1442
|
+
if (decayed > 0) {
|
|
1443
|
+
debugLog(`[SqliteStorage] decayImportance: reduced ${decayed} entries for "${project}" (>${decayDays}d old)`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1425
1446
|
}
|
package/dist/storage/supabase.js
CHANGED
|
@@ -356,31 +356,45 @@ export class SupabaseStorage {
|
|
|
356
356
|
catch (e) {
|
|
357
357
|
debugLog("[SupabaseStorage] expireByTTL failed: " + (e instanceof Error ? e.message : String(e)));
|
|
358
358
|
}
|
|
359
|
+
// Fix #5: Decay importance parity with SQLite.
|
|
360
|
+
// NOTE: Unlike SQLite (which decays on every session_save_ledger health sweep),
|
|
361
|
+
// Supabase decay is TTL-gated — it only runs when knowledge_set_retention has
|
|
362
|
+
// been configured for this project. Projects without a TTL policy will not
|
|
363
|
+
// experience importance decay. Future improvement: fire this from
|
|
364
|
+
// sessionSaveLedgerHandler (fire-and-forget) to achieve full parity.
|
|
365
|
+
try {
|
|
366
|
+
await supabaseRpc("prism_decay_importance", {
|
|
367
|
+
p_project: project,
|
|
368
|
+
p_user_id: userId,
|
|
369
|
+
p_days: 30,
|
|
370
|
+
});
|
|
371
|
+
debugLog(`[SupabaseStorage] Importance decay sweep completed for "${project}"`);
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
// Non-fatal: decay is a best-effort background operation
|
|
375
|
+
debugLog("[SupabaseStorage] prism_decay_importance failed (non-fatal): " + (e instanceof Error ? e.message : String(e)));
|
|
376
|
+
}
|
|
359
377
|
// Supabase PATCH doesn't return rowsAffected — return 0 (UI doesn't need exact count)
|
|
360
378
|
debugLog(`[SupabaseStorage] TTL sweep completed for "${project}" (cutoff: ${cutoffStr})`);
|
|
361
379
|
return { expired: 0 };
|
|
362
380
|
}
|
|
363
381
|
// ─── v4.0: Insight Graduation ──────────────────────────────────
|
|
364
382
|
async adjustImportance(id, delta, userId) {
|
|
365
|
-
//
|
|
366
|
-
//
|
|
383
|
+
// Fix #4: Use atomic RPC instead of read-then-write.
|
|
384
|
+
// prism_adjust_importance computes MAX(0, importance + delta) in one
|
|
385
|
+
// SQL UPDATE, eliminating the race condition in the old pattern.
|
|
367
386
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
373
|
-
const rows = Array.isArray(data) ? data : [];
|
|
374
|
-
const current = rows[0]?.importance ?? 0;
|
|
375
|
-
const newVal = Math.max(0, current + delta);
|
|
376
|
-
await supabasePatch("session_ledger", { importance: newVal }, {
|
|
377
|
-
id: `eq.${id}`,
|
|
378
|
-
user_id: `eq.${userId}`,
|
|
387
|
+
await supabaseRpc("prism_adjust_importance", {
|
|
388
|
+
p_id: id,
|
|
389
|
+
p_user_id: userId,
|
|
390
|
+
p_delta: delta,
|
|
379
391
|
});
|
|
380
|
-
debugLog(`[SupabaseStorage] Adjusted importance for ${id} by ${delta > 0 ? "+" : ""}${delta}
|
|
392
|
+
debugLog(`[SupabaseStorage] Adjusted importance for ${id} by ${delta > 0 ? "+" : ""}${delta} via RPC`);
|
|
381
393
|
}
|
|
382
394
|
catch (e) {
|
|
383
|
-
|
|
395
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
396
|
+
debugLog("[SupabaseStorage] adjustImportance failed: " + msg);
|
|
397
|
+
throw e; // Fix #3: rethrow so handlers can surface isError:true
|
|
384
398
|
}
|
|
385
399
|
}
|
|
386
400
|
// ─── v4.2: Graduated Insights Query ──────────────────────────
|
|
@@ -408,4 +422,24 @@ export class SupabaseStorage {
|
|
|
408
422
|
conversation_id: "",
|
|
409
423
|
}));
|
|
410
424
|
}
|
|
425
|
+
// ─── v4.3: Standalone Importance Decay ─────────────────────
|
|
426
|
+
//
|
|
427
|
+
// Calls the prism_decay_importance RPC (migration 028) directly,
|
|
428
|
+
// decoupled from expireByTTL so it can be triggered fire-and-forget
|
|
429
|
+
// from session_save_ledger without a TTL policy being active.
|
|
430
|
+
async decayImportance(project, userId, decayDays) {
|
|
431
|
+
try {
|
|
432
|
+
await supabaseRpc("prism_decay_importance", {
|
|
433
|
+
p_project: project,
|
|
434
|
+
p_user_id: userId,
|
|
435
|
+
p_days: decayDays,
|
|
436
|
+
});
|
|
437
|
+
debugLog(`[SupabaseStorage] decayImportance: sweep completed for "${project}" (>${decayDays}d old)`);
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
// Non-fatal: decay is best-effort — log and rethrow so fire-and-forget caller can see it
|
|
441
|
+
debugLog("[SupabaseStorage] decayImportance failed: " + (e instanceof Error ? e.message : String(e)));
|
|
442
|
+
throw e;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
411
445
|
}
|
|
@@ -56,7 +56,48 @@ export const MIGRATIONS = [
|
|
|
56
56
|
AND deleted_at IS NULL AND archived_at IS NULL;
|
|
57
57
|
`,
|
|
58
58
|
},
|
|
59
|
-
|
|
59
|
+
{
|
|
60
|
+
version: 28,
|
|
61
|
+
name: "importance_rpcs",
|
|
62
|
+
sql: `
|
|
63
|
+
-- Fix #4: Atomic importance adjustment — eliminates read-then-write race condition
|
|
64
|
+
CREATE OR REPLACE FUNCTION prism_adjust_importance(
|
|
65
|
+
p_id TEXT, p_user_id TEXT, p_delta INTEGER
|
|
66
|
+
)
|
|
67
|
+
RETURNS void
|
|
68
|
+
LANGUAGE plpgsql
|
|
69
|
+
SECURITY DEFINER
|
|
70
|
+
AS $$
|
|
71
|
+
BEGIN
|
|
72
|
+
UPDATE session_ledger
|
|
73
|
+
SET importance = GREATEST(0, importance + p_delta)
|
|
74
|
+
WHERE id = p_id AND user_id = p_user_id;
|
|
75
|
+
END;
|
|
76
|
+
$$;
|
|
77
|
+
|
|
78
|
+
-- Fix #5: Importance decay — parity with SQLite backend
|
|
79
|
+
CREATE OR REPLACE FUNCTION prism_decay_importance(
|
|
80
|
+
p_project TEXT, p_user_id TEXT, p_days INTEGER
|
|
81
|
+
)
|
|
82
|
+
RETURNS void
|
|
83
|
+
LANGUAGE plpgsql
|
|
84
|
+
SECURITY DEFINER
|
|
85
|
+
AS $$
|
|
86
|
+
BEGIN
|
|
87
|
+
UPDATE session_ledger
|
|
88
|
+
SET importance = GREATEST(0, importance - 1)
|
|
89
|
+
WHERE project = p_project
|
|
90
|
+
AND user_id = p_user_id
|
|
91
|
+
AND importance > 0
|
|
92
|
+
AND event_type <> 'session'
|
|
93
|
+
AND created_at < now() - (p_days || ' days')::interval
|
|
94
|
+
AND deleted_at IS NULL
|
|
95
|
+
AND archived_at IS NULL;
|
|
96
|
+
END;
|
|
97
|
+
$$;
|
|
98
|
+
`,
|
|
99
|
+
},
|
|
100
|
+
// Future migrations go here (version 29+)
|
|
60
101
|
];
|
|
61
102
|
/**
|
|
62
103
|
* Current schema version — derived from the MIGRATIONS array.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* ═══════════════════════════════════════════════════════════════════
|
|
8
8
|
*/
|
|
9
9
|
import { getStorage } from "../storage/index.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { PRISM_USER_ID } from "../config.js";
|
|
11
|
+
import { getLLMProvider } from "../utils/llm/factory.js";
|
|
12
12
|
import { debugLog } from "../utils/logger.js";
|
|
13
13
|
// ─── Constants ────────────────────────────────────────────────
|
|
14
14
|
const COMPACTION_CHUNK_SIZE = 10;
|
|
@@ -17,27 +17,20 @@ const MAX_ENTRIES_PER_RUN = 100;
|
|
|
17
17
|
export function isCompactLedgerArgs(args) {
|
|
18
18
|
return typeof args === "object" && args !== null;
|
|
19
19
|
}
|
|
20
|
-
// ───
|
|
20
|
+
// ─── LLM Summarization ────────────────────────────────────────
|
|
21
21
|
async function summarizeEntries(entries) {
|
|
22
|
-
|
|
23
|
-
throw new Error("Cannot compact ledger: GOOGLE_API_KEY required for Gemini summarization");
|
|
24
|
-
}
|
|
25
|
-
const genAI = new GoogleGenerativeAI(GOOGLE_API_KEY);
|
|
26
|
-
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
22
|
+
const llm = getLLMProvider(); // throws if no API key configured
|
|
27
23
|
const entriesText = entries.map((e, i) => `[${i + 1}] ${e.session_date || "unknown date"}: ${e.summary || "no summary"}\n` +
|
|
28
24
|
(e.decisions?.length ? ` Decisions: ${e.decisions.join("; ")}\n` : "") +
|
|
29
25
|
(e.files_changed?.length ? ` Files: ${e.files_changed.join(", ")}\n` : "")).join("\n");
|
|
30
|
-
const prompt = `You are compressing a session history log. Summarize these ${entries.length} ` +
|
|
26
|
+
const prompt = (`You are compressing a session history log. Summarize these ${entries.length} ` +
|
|
31
27
|
`work sessions into a single concise paragraph (max 500 words).\n\n` +
|
|
32
28
|
`PRESERVE: key decisions, important file changes, error resolutions, ` +
|
|
33
29
|
`architecture changes, and any recurring patterns.\n` +
|
|
34
30
|
`OMIT: routine operations, intermediate debugging steps, and redundant details.\n\n` +
|
|
35
31
|
`Sessions to summarize:\n${entriesText}\n\n` +
|
|
36
|
-
`Provide ONLY the summary paragraph, no headers or formatting
|
|
37
|
-
|
|
38
|
-
const result = await model.generateContent(truncatedPrompt);
|
|
39
|
-
const response = result.response;
|
|
40
|
-
return response.text();
|
|
32
|
+
`Provide ONLY the summary paragraph, no headers or formatting.`).substring(0, 30000);
|
|
33
|
+
return llm.generateText(prompt);
|
|
41
34
|
}
|
|
42
35
|
// ─── Main Handler ─────────────────────────────────────────────
|
|
43
36
|
export async function compactLedgerHandler(args) {
|