@plur-ai/mcp 0.15.0 → 0.16.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 +4 -3
- package/dist/{server-7JVOIBJV.js → chunk-TPL7QPPO.js} +206 -532
- package/dist/index.js +6 -6
- package/dist/server-VHSO4XMV.js +378 -0
- package/dist/tools-export.d.ts +76 -0
- package/dist/tools-export.js +21 -0
- package/package.json +6 -2
- package/packs/effective-memory/SKILL.md +1 -1
- package/packs/effective-memory/engrams.yaml +3 -3
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
// src/server.ts
|
|
2
|
-
import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
4
|
-
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
5
|
-
import { join as join2 } from "path";
|
|
6
|
-
import { homedir as homedir2 } from "os";
|
|
7
|
-
import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
|
|
8
|
-
|
|
9
|
-
// src/tools.ts
|
|
10
|
-
import { existsSync, unlinkSync } from "fs";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation, getCachedUpdateCheck, minorVersionsBehind, scanForTensions, CapabilityCanary, readProjectConfig, isSharedScope, resolveRerankerName, getReranker, classifyRerankerFailure, hfCacheDirName, SUGGEST_DISPLAY_MIN_CONFIDENCE } from "@plur-ai/core";
|
|
14
|
-
|
|
15
1
|
// src/telemetry.ts
|
|
16
2
|
import { recordEvent, flushIfNeeded, registerFlushOnExit } from "@plur-ai/core";
|
|
17
3
|
function recordTelemetry(event) {
|
|
@@ -24,9 +10,13 @@ function recordTelemetry(event) {
|
|
|
24
10
|
}
|
|
25
11
|
|
|
26
12
|
// src/version.ts
|
|
27
|
-
var VERSION = "0.
|
|
13
|
+
var VERSION = "0.16.0";
|
|
28
14
|
|
|
29
15
|
// src/tools.ts
|
|
16
|
+
import { existsSync, unlinkSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation, getCachedUpdateCheck, minorVersionsBehind, scanForTensions, CapabilityCanary, readProjectConfig, isSharedScope, resolveRerankerName, getReranker, classifyRerankerFailure, hfCacheDirName, SUGGEST_DISPLAY_MIN_CONFIDENCE } from "@plur-ai/core";
|
|
30
20
|
import { z } from "zod";
|
|
31
21
|
function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
32
22
|
return async (prompt) => {
|
|
@@ -49,6 +39,95 @@ function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
|
49
39
|
return data.choices?.[0]?.message?.content ?? "";
|
|
50
40
|
};
|
|
51
41
|
}
|
|
42
|
+
var recallHandler = async (args, plur) => {
|
|
43
|
+
const mode = args.mode ?? "hybrid";
|
|
44
|
+
if (mode === "keyword") {
|
|
45
|
+
const results = await plur.recall(args.query, {
|
|
46
|
+
scope: args.scope,
|
|
47
|
+
domain: args.domain,
|
|
48
|
+
limit: args.limit
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
results: results.map((e) => {
|
|
52
|
+
const supersededBy = e.relations?.superseded_by;
|
|
53
|
+
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
54
|
+
return {
|
|
55
|
+
id: e.id,
|
|
56
|
+
statement: e.statement + annotation,
|
|
57
|
+
type: e.type,
|
|
58
|
+
scope: e.scope,
|
|
59
|
+
domain: e.domain,
|
|
60
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
count: results.length,
|
|
64
|
+
mode: "keyword"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const budget = args.budget;
|
|
68
|
+
const cap = budget?.max_results ?? args.limit ?? 20;
|
|
69
|
+
const fetchLimit = budget?.max_results != null ? cap + 1 : cap;
|
|
70
|
+
const meta = await plur.recallHybridWithMeta(args.query, {
|
|
71
|
+
scope: args.scope,
|
|
72
|
+
domain: args.domain,
|
|
73
|
+
limit: fetchLimit
|
|
74
|
+
});
|
|
75
|
+
recordTelemetry("recall");
|
|
76
|
+
const truncatedByCount = budget?.max_results != null && meta.engrams.length > cap;
|
|
77
|
+
let truncated = truncatedByCount;
|
|
78
|
+
let boundedResults = truncatedByCount ? meta.engrams.slice(0, cap) : meta.engrams;
|
|
79
|
+
if (budget?.max_tokens) {
|
|
80
|
+
let tokenCount = 0;
|
|
81
|
+
const withinBudget = [];
|
|
82
|
+
for (const e of boundedResults) {
|
|
83
|
+
const tokens = Math.ceil(e.statement.length / 4) + 20;
|
|
84
|
+
if (tokenCount + tokens > budget.max_tokens) {
|
|
85
|
+
truncated = true;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
withinBudget.push(e);
|
|
89
|
+
tokenCount += tokens;
|
|
90
|
+
}
|
|
91
|
+
boundedResults = withinBudget;
|
|
92
|
+
}
|
|
93
|
+
const includeEpisodes = args.include_episodes === true;
|
|
94
|
+
const response = {
|
|
95
|
+
results: boundedResults.map((e) => {
|
|
96
|
+
const raw = e;
|
|
97
|
+
const supersededBy = e.relations?.superseded_by;
|
|
98
|
+
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
99
|
+
const base = {
|
|
100
|
+
id: e.id,
|
|
101
|
+
statement: e.statement + annotation,
|
|
102
|
+
type: e.type,
|
|
103
|
+
scope: e.scope,
|
|
104
|
+
domain: e.domain,
|
|
105
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
106
|
+
};
|
|
107
|
+
if (includeEpisodes && raw.episode_ids?.length > 0) {
|
|
108
|
+
const episodes = plur.timeline({ search: "" });
|
|
109
|
+
base.episodes = episodes.filter((ep) => raw.episode_ids.includes(ep.id)).map((ep) => ({ id: ep.id, summary: ep.summary, timestamp: ep.timestamp }));
|
|
110
|
+
}
|
|
111
|
+
return base;
|
|
112
|
+
}),
|
|
113
|
+
count: boundedResults.length,
|
|
114
|
+
truncated,
|
|
115
|
+
mode: meta.mode
|
|
116
|
+
};
|
|
117
|
+
if (meta.mode === "hybrid-degraded") {
|
|
118
|
+
response.warning = `Embedding layer unavailable \u2014 results are BM25-only. Run plur_doctor for diagnosis. Last error: ${meta.embedderError ?? "unknown"}`;
|
|
119
|
+
}
|
|
120
|
+
if (resolveRerankerName() !== "off") {
|
|
121
|
+
response.reranked = meta.reranked ?? 0;
|
|
122
|
+
const rr = plur.rerankerStatus();
|
|
123
|
+
if (boundedResults.length > 0 && (meta.reranked ?? 0) === 0 && rr.lastError) {
|
|
124
|
+
const corruptNote = rr.lastErrorKind === "corrupt-cache" ? " The model cache looks corrupt (truncated download) \u2014 purge and re-download, see plur_doctor." : "";
|
|
125
|
+
response.reranker_warning = `PLUR_RERANKER is set but the reranker did not engage \u2014 results are RRF-only (fusion order, no cross-encoder rerank).${corruptNote} Last error: ${rr.lastError}. Run plur_doctor for diagnosis.`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return response;
|
|
129
|
+
};
|
|
130
|
+
var RECALL_HYBRID_DEPRECATION = "plur_recall_hybrid is deprecated since 0.16 \u2014 use plur_recall (mode defaults to hybrid). This alias will be removed in 0.18.";
|
|
52
131
|
function jsonSchemaPropToZod(prop) {
|
|
53
132
|
if (!prop || typeof prop !== "object") return z.unknown();
|
|
54
133
|
const variants = prop.anyOf ?? prop.oneOf;
|
|
@@ -73,7 +152,10 @@ function jsonSchemaPropToZod(prop) {
|
|
|
73
152
|
return val;
|
|
74
153
|
}
|
|
75
154
|
}
|
|
76
|
-
|
|
155
|
+
const items = prop.items;
|
|
156
|
+
const itemVariants = items?.anyOf ?? items?.oneOf;
|
|
157
|
+
const itemsAcceptString = items?.type === "string" || Array.isArray(itemVariants) && itemVariants.some((v) => v?.type === "string");
|
|
158
|
+
if (itemsAcceptString) {
|
|
77
159
|
return trimmed.length === 0 ? [] : trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
78
160
|
}
|
|
79
161
|
return val;
|
|
@@ -102,7 +184,10 @@ function validateToolArgs(tool, rawArgs) {
|
|
|
102
184
|
const receivedFields = Object.keys(rawArgs);
|
|
103
185
|
const details = parsed.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join(", ");
|
|
104
186
|
const hasArrayParam = Object.values(schema.properties ?? {}).some((p) => p?.type === "array");
|
|
105
|
-
const
|
|
187
|
+
const missingArrayParams = parsed.error.issues.filter((i) => i.code === "invalid_type" && i.received === "undefined").map((i) => String(i.path[0] ?? "")).filter((k) => schema.properties?.[k]?.type === "array");
|
|
188
|
+
const totalDrop = receivedFields.length === 0 && hasArrayParam;
|
|
189
|
+
const partialDrop = missingArrayParams.length > 0;
|
|
190
|
+
const arrayBugHint = totalDrop || partialDrop ? " Known client-side bug (plur-ai/plur#297): some MCP clients drop " + (partialDrop ? `array-typed parameters from a large arguments payload while keeping the earlier fields (here: ${missingArrayParams.join(", ")}). This is size-sensitive \u2014 the same call often succeeds with a shorter payload, so shrink the other fields (e.g. a briefer summary) as well. ` : "the entire arguments payload when an array-typed parameter is included. ") + 'Retry passing array parameters as a JSON string (e.g. tags: "[\\"a\\",\\"b\\"]") or a comma-separated string (tags: "a, b") \u2014 the server coerces both back into arrays.' : "";
|
|
106
191
|
const receivedNote = receivedFields.length > 0 ? `Received fields: [${receivedFields.join(", ")}].` : "Received no fields (the arguments object was empty).";
|
|
107
192
|
return {
|
|
108
193
|
ok: false,
|
|
@@ -143,7 +228,7 @@ var PLUR_GUIDE = `## PLUR Quick Start
|
|
|
143
228
|
|
|
144
229
|
### Core Tools
|
|
145
230
|
- **plur_learn** \u2014 record corrections, preferences, patterns (CALL THIS OFTEN)
|
|
146
|
-
- **
|
|
231
|
+
- **plur_recall** \u2014 search engrams by topic (default: hybrid BM25 + embeddings; use mode:"keyword" for BM25-only)
|
|
147
232
|
- **plur_forget** \u2014 retire an outdated engram`;
|
|
148
233
|
function getLlmFunction() {
|
|
149
234
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
@@ -180,12 +265,22 @@ function _cleanExpiredSessions() {
|
|
|
180
265
|
if (new Date(state.started_at).getTime() < cutoff) _sessionTelemetry.delete(id);
|
|
181
266
|
}
|
|
182
267
|
}
|
|
183
|
-
|
|
268
|
+
function _implicitSessionId() {
|
|
269
|
+
_cleanExpiredSessions();
|
|
270
|
+
if (_sessionTelemetry.size !== 1) return void 0;
|
|
271
|
+
return _sessionTelemetry.keys().next().value;
|
|
272
|
+
}
|
|
273
|
+
function _resolveInjectionSession(args) {
|
|
274
|
+
const explicit = args.session_id;
|
|
275
|
+
if (typeof explicit === "string" && explicit.length > 0) return explicit;
|
|
276
|
+
return _implicitSessionId();
|
|
277
|
+
}
|
|
184
278
|
function _recordInjectionTelemetry(session_id, injected_packs) {
|
|
185
|
-
if (!session_id
|
|
279
|
+
if (!session_id) return;
|
|
186
280
|
const state = _sessionTelemetry.get(session_id);
|
|
187
281
|
if (!state) return;
|
|
188
282
|
state.injection_calls++;
|
|
283
|
+
if (!injected_packs) return;
|
|
189
284
|
for (const [pack, count] of Object.entries(injected_packs)) {
|
|
190
285
|
state.pack_counts[pack] = (state.pack_counts[pack] ?? 0) + count;
|
|
191
286
|
}
|
|
@@ -194,7 +289,7 @@ var CURSOR_CORE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
194
289
|
"plur_session_start",
|
|
195
290
|
"plur_session_end",
|
|
196
291
|
"plur_learn",
|
|
197
|
-
"
|
|
292
|
+
"plur_recall",
|
|
198
293
|
"plur_feedback",
|
|
199
294
|
"plur_forget",
|
|
200
295
|
"plur_status",
|
|
@@ -368,7 +463,7 @@ function getAllToolDefinitions() {
|
|
|
368
463
|
...routed ? { routed: { scope: routed.scope, confidence: routed.confidence, reason: routed.reason }, info: `No scope was provided; auto-routed to "${routed.scope}" (confidence ${routed.confidence}) because its content matched that scope's covers. Pass an explicit scope to override.` } : {}
|
|
369
464
|
};
|
|
370
465
|
} catch (err) {
|
|
371
|
-
const engram = plur.learn(statement, context);
|
|
466
|
+
const engram = await plur.learn(statement, context);
|
|
372
467
|
const isOutbox = !!engram.structured_data?._outbox;
|
|
373
468
|
const routedFallback = engram.structured_data?._routed;
|
|
374
469
|
mcpCanary.signal("learn_activity");
|
|
@@ -483,47 +578,28 @@ function getAllToolDefinitions() {
|
|
|
483
578
|
},
|
|
484
579
|
{
|
|
485
580
|
name: "plur_recall",
|
|
486
|
-
description:
|
|
487
|
-
annotations: { title: "Recall
|
|
581
|
+
description: 'Search engrams by topic. Default mode is hybrid (BM25 + local embeddings via RRF) \u2014 set mode:"keyword" for BM25-only. No API calls, fully local. Note: a project-scope filter also returns personal-family engrams (local, global, user:*, agent:*); an explicit scope=global recall returns ALL personal-family engrams \u2014 wider than scope=global INJECT, which is targeted to the global namespace only.',
|
|
582
|
+
annotations: { title: "Recall", readOnlyHint: true, idempotentHint: true },
|
|
488
583
|
inputSchema: {
|
|
489
584
|
type: "object",
|
|
490
585
|
properties: {
|
|
491
586
|
query: { type: "string", description: "Search query to find relevant engrams" },
|
|
587
|
+
mode: { type: "string", enum: ["hybrid", "keyword"], description: "Search mode \u2014 hybrid (default): BM25 + embeddings via RRF; keyword: BM25-only (faster, embeddings-independent). budget, caller_session_id and include_episodes apply to hybrid mode only \u2014 in keyword mode use limit to bound results." },
|
|
492
588
|
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
493
589
|
domain: { type: "string", description: "Filter by domain prefix" },
|
|
494
590
|
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
495
|
-
budget: { type: "object", description:
|
|
496
|
-
caller_session_id: { type: "string", description:
|
|
591
|
+
budget: { type: "object", description: 'Budget constraints for sub-agents. Hybrid mode only \u2014 ignored when mode:"keyword".', properties: { max_tokens: { type: "number" }, max_results: { type: "number" }, ttl_seconds: { type: "number" } } },
|
|
592
|
+
caller_session_id: { type: "string", description: 'Session ID of calling agent for budget enforcement. Hybrid mode only \u2014 ignored when mode:"keyword".' },
|
|
593
|
+
include_episodes: { type: "boolean", description: 'If true, include linked episode summaries for each engram (SP2 episodic anchoring). Hybrid mode only \u2014 ignored when mode:"keyword".' }
|
|
497
594
|
},
|
|
498
595
|
required: ["query"]
|
|
499
596
|
},
|
|
500
|
-
handler:
|
|
501
|
-
const results = plur.recall(args.query, {
|
|
502
|
-
scope: args.scope,
|
|
503
|
-
domain: args.domain,
|
|
504
|
-
limit: args.limit
|
|
505
|
-
});
|
|
506
|
-
return {
|
|
507
|
-
results: results.map((e) => {
|
|
508
|
-
const supersededBy = e.relations?.superseded_by;
|
|
509
|
-
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
510
|
-
return {
|
|
511
|
-
id: e.id,
|
|
512
|
-
statement: e.statement + annotation,
|
|
513
|
-
type: e.type,
|
|
514
|
-
scope: e.scope,
|
|
515
|
-
domain: e.domain,
|
|
516
|
-
retrieval_strength: e.activation.retrieval_strength
|
|
517
|
-
};
|
|
518
|
-
}),
|
|
519
|
-
count: results.length
|
|
520
|
-
};
|
|
521
|
-
}
|
|
597
|
+
handler: recallHandler
|
|
522
598
|
},
|
|
523
599
|
{
|
|
524
600
|
name: "plur_recall_hybrid",
|
|
525
|
-
description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local.
|
|
526
|
-
annotations: { title: "Recall (hybrid)", readOnlyHint: true, idempotentHint: true },
|
|
601
|
+
description: "[Deprecated since 0.16 \u2014 use plur_recall (mode defaults to hybrid). Alias kept for backwards compatibility; removal earliest 0.18.] Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local.",
|
|
602
|
+
annotations: { title: "Recall (hybrid) [deprecated alias]", readOnlyHint: true, idempotentHint: true },
|
|
527
603
|
inputSchema: {
|
|
528
604
|
type: "object",
|
|
529
605
|
properties: {
|
|
@@ -537,72 +613,12 @@ function getAllToolDefinitions() {
|
|
|
537
613
|
},
|
|
538
614
|
required: ["query"]
|
|
539
615
|
},
|
|
616
|
+
// True forwarder — delegates to the canonical plur_recall handler and
|
|
617
|
+
// prepends the deprecation notice. No duplicated budget/episode/
|
|
618
|
+
// reranker logic, so a fix to plur_recall reaches this alias too.
|
|
540
619
|
handler: async (args, plur) => {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
const meta = await plur.recallHybridWithMeta(args.query, {
|
|
544
|
-
scope: args.scope,
|
|
545
|
-
domain: args.domain,
|
|
546
|
-
limit: effectiveLimit
|
|
547
|
-
});
|
|
548
|
-
recordTelemetry("recall");
|
|
549
|
-
const results = meta.engrams;
|
|
550
|
-
let truncated = false;
|
|
551
|
-
let boundedResults = results;
|
|
552
|
-
if (budget?.max_results && results.length > budget.max_results) {
|
|
553
|
-
boundedResults = results.slice(0, budget.max_results);
|
|
554
|
-
truncated = true;
|
|
555
|
-
}
|
|
556
|
-
if (budget?.max_tokens) {
|
|
557
|
-
let tokenCount = 0;
|
|
558
|
-
const withinBudget = [];
|
|
559
|
-
for (const e of boundedResults) {
|
|
560
|
-
const tokens = Math.ceil(e.statement.length / 4) + 20;
|
|
561
|
-
if (tokenCount + tokens > budget.max_tokens) {
|
|
562
|
-
truncated = true;
|
|
563
|
-
break;
|
|
564
|
-
}
|
|
565
|
-
withinBudget.push(e);
|
|
566
|
-
tokenCount += tokens;
|
|
567
|
-
}
|
|
568
|
-
boundedResults = withinBudget;
|
|
569
|
-
}
|
|
570
|
-
const includeEpisodes = args.include_episodes === true;
|
|
571
|
-
const response = {
|
|
572
|
-
results: boundedResults.map((e) => {
|
|
573
|
-
const raw = e;
|
|
574
|
-
const supersededBy = e.relations?.superseded_by;
|
|
575
|
-
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
576
|
-
const base = {
|
|
577
|
-
id: e.id,
|
|
578
|
-
statement: e.statement + annotation,
|
|
579
|
-
type: e.type,
|
|
580
|
-
scope: e.scope,
|
|
581
|
-
domain: e.domain,
|
|
582
|
-
retrieval_strength: e.activation.retrieval_strength
|
|
583
|
-
};
|
|
584
|
-
if (includeEpisodes && raw.episode_ids?.length > 0) {
|
|
585
|
-
const episodes = plur.timeline({ search: "" });
|
|
586
|
-
base.episodes = episodes.filter((ep) => raw.episode_ids.includes(ep.id)).map((ep) => ({ id: ep.id, summary: ep.summary, timestamp: ep.timestamp }));
|
|
587
|
-
}
|
|
588
|
-
return base;
|
|
589
|
-
}),
|
|
590
|
-
count: boundedResults.length,
|
|
591
|
-
truncated,
|
|
592
|
-
mode: meta.mode
|
|
593
|
-
};
|
|
594
|
-
if (meta.mode === "hybrid-degraded") {
|
|
595
|
-
response.warning = `Embedding layer unavailable \u2014 results are BM25-only. Run plur_doctor for diagnosis. Last error: ${meta.embedderError ?? "unknown"}`;
|
|
596
|
-
}
|
|
597
|
-
if (resolveRerankerName() !== "off") {
|
|
598
|
-
response.reranked = meta.reranked ?? 0;
|
|
599
|
-
const rr = plur.rerankerStatus();
|
|
600
|
-
if (boundedResults.length > 0 && (meta.reranked ?? 0) === 0 && rr.lastError) {
|
|
601
|
-
const corruptNote = rr.lastErrorKind === "corrupt-cache" ? " The model cache looks corrupt (truncated download) \u2014 purge and re-download, see plur_doctor." : "";
|
|
602
|
-
response.reranker_warning = `PLUR_RERANKER is set but the reranker did not engage \u2014 results are RRF-only (fusion order, no cross-encoder rerank).${corruptNote} Last error: ${rr.lastError}. Run plur_doctor for diagnosis.`;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
return response;
|
|
620
|
+
const result = await recallHandler({ ...args, mode: "hybrid" }, plur);
|
|
621
|
+
return { deprecated: RECALL_HYBRID_DEPRECATION, ...result };
|
|
606
622
|
}
|
|
607
623
|
},
|
|
608
624
|
{
|
|
@@ -614,18 +630,20 @@ function getAllToolDefinitions() {
|
|
|
614
630
|
properties: {
|
|
615
631
|
task: { type: "string", description: "The task description to inject context for" },
|
|
616
632
|
budget: { type: "number", description: "Token budget for injection (default 2000)" },
|
|
617
|
-
scope: { type: "string", description: "Scope filter for engram selection" }
|
|
633
|
+
scope: { type: "string", description: "Scope filter for engram selection" },
|
|
634
|
+
session_id: { type: "string", description: "Session this injection belongs to (from plur_session_start). Optional when one session is open; required for correct attribution when several are." }
|
|
618
635
|
},
|
|
619
636
|
required: ["task"]
|
|
620
637
|
},
|
|
621
638
|
handler: async (args, plur) => {
|
|
622
|
-
const
|
|
639
|
+
const session_id = _resolveInjectionSession(args);
|
|
640
|
+
const result = await plur.inject(args.task, {
|
|
623
641
|
budget: args.budget,
|
|
624
642
|
scope: args.scope,
|
|
625
643
|
source: "inject",
|
|
626
|
-
session_id
|
|
644
|
+
session_id
|
|
627
645
|
});
|
|
628
|
-
_recordInjectionTelemetry(
|
|
646
|
+
_recordInjectionTelemetry(session_id, result.injected_packs);
|
|
629
647
|
return {
|
|
630
648
|
directives: result.directives,
|
|
631
649
|
consider: result.consider,
|
|
@@ -646,18 +664,20 @@ function getAllToolDefinitions() {
|
|
|
646
664
|
properties: {
|
|
647
665
|
task: { type: "string", description: "The task description to inject context for" },
|
|
648
666
|
budget: { type: "number", description: "Token budget for injection (default 2000)" },
|
|
649
|
-
scope: { type: "string", description: "Scope filter for engram selection" }
|
|
667
|
+
scope: { type: "string", description: "Scope filter for engram selection" },
|
|
668
|
+
session_id: { type: "string", description: "Session this injection belongs to (from plur_session_start). Optional when one session is open; required for correct attribution when several are." }
|
|
650
669
|
},
|
|
651
670
|
required: ["task"]
|
|
652
671
|
},
|
|
653
672
|
handler: async (args, plur) => {
|
|
673
|
+
const session_id = _resolveInjectionSession(args);
|
|
654
674
|
const result = await plur.injectHybrid(args.task, {
|
|
655
675
|
budget: args.budget,
|
|
656
676
|
scope: args.scope,
|
|
657
677
|
source: "inject",
|
|
658
|
-
session_id
|
|
678
|
+
session_id
|
|
659
679
|
});
|
|
660
|
-
_recordInjectionTelemetry(
|
|
680
|
+
_recordInjectionTelemetry(session_id, result.injected_packs);
|
|
661
681
|
return {
|
|
662
682
|
directives: result.directives,
|
|
663
683
|
consider: result.consider,
|
|
@@ -737,7 +757,7 @@ function getAllToolDefinitions() {
|
|
|
737
757
|
},
|
|
738
758
|
handler: async (args, plur) => {
|
|
739
759
|
if (args.list === true) {
|
|
740
|
-
const pinned = plur.listPinned();
|
|
760
|
+
const pinned = await plur.listPinned();
|
|
741
761
|
return {
|
|
742
762
|
count: pinned.length,
|
|
743
763
|
pinned: pinned.map((e) => ({ id: e.id, statement: e.statement, scope: e.scope, domain: e.domain }))
|
|
@@ -767,7 +787,7 @@ function getAllToolDefinitions() {
|
|
|
767
787
|
},
|
|
768
788
|
handler: async (args, plur) => {
|
|
769
789
|
if (args.id) {
|
|
770
|
-
const engram = plur.getById(args.id);
|
|
790
|
+
const engram = await plur.getById(args.id);
|
|
771
791
|
if (engram) {
|
|
772
792
|
if (engram.status === "retired") return { success: false, error: `Already retired: ${args.id}` };
|
|
773
793
|
await plur.forget(args.id);
|
|
@@ -777,7 +797,7 @@ function getAllToolDefinitions() {
|
|
|
777
797
|
return { success: true, retired: { id: args.id } };
|
|
778
798
|
}
|
|
779
799
|
if (args.search) {
|
|
780
|
-
const matches = plur.recall(args.search, { limit: 100 });
|
|
800
|
+
const matches = await plur.recall(args.search, { limit: 100 });
|
|
781
801
|
if (matches.length === 0) return { success: false, error: `No active engrams matching "${args.search}"` };
|
|
782
802
|
if (matches.length === 1) {
|
|
783
803
|
await plur.forget(matches[0].id);
|
|
@@ -876,7 +896,7 @@ function getAllToolDefinitions() {
|
|
|
876
896
|
required: ["content"]
|
|
877
897
|
},
|
|
878
898
|
handler: async (args, plur) => {
|
|
879
|
-
const candidates = plur.ingest(args.content, {
|
|
899
|
+
const candidates = await plur.ingest(args.content, {
|
|
880
900
|
source: args.source,
|
|
881
901
|
extract_only: args.extract_only,
|
|
882
902
|
scope: args.scope,
|
|
@@ -895,32 +915,32 @@ function getAllToolDefinitions() {
|
|
|
895
915
|
},
|
|
896
916
|
{
|
|
897
917
|
name: "plur_packs_preview",
|
|
898
|
-
description: "Preview a pack before installing \u2014 shows manifest, engram list, security scan, and warnings. Always call this before plur_packs_install to let the user review what they are importing.",
|
|
918
|
+
description: "Preview a pack before installing \u2014 shows manifest, engram list, security scan, and warnings. Always call this before plur_packs_install to let the user review what they are importing. Accepts a local directory path or an https:// URL pointing to a .tar.gz archive.",
|
|
899
919
|
annotations: { title: "Preview pack", readOnlyHint: true, idempotentHint: true },
|
|
900
920
|
inputSchema: {
|
|
901
921
|
type: "object",
|
|
902
922
|
properties: {
|
|
903
|
-
source: { type: "string", description: "Path to the pack directory to
|
|
923
|
+
source: { type: "string", description: "Path to the pack directory, or an https:// URL to a .tar.gz pack archive" }
|
|
904
924
|
},
|
|
905
925
|
required: ["source"]
|
|
906
926
|
},
|
|
907
927
|
handler: async (args, plur) => {
|
|
908
|
-
return plur.previewPack(args.source);
|
|
928
|
+
return await plur.previewPack(args.source);
|
|
909
929
|
}
|
|
910
930
|
},
|
|
911
931
|
{
|
|
912
932
|
name: "plur_packs_install",
|
|
913
|
-
description: "Install an engram pack from a directory path. Runs a mandatory security scan (blocks if secrets found), detects conflicts with existing engrams, and records install metadata in the registry. Call plur_packs_preview first to show the user what the pack contains.",
|
|
933
|
+
description: "Install an engram pack from a local directory path or an https:// URL pointing to a .tar.gz archive. Runs a mandatory security scan (blocks if secrets found), detects conflicts with existing engrams, and records install metadata in the registry. Call plur_packs_preview first to show the user what the pack contains.",
|
|
914
934
|
annotations: { title: "Install pack", destructiveHint: false, idempotentHint: true },
|
|
915
935
|
inputSchema: {
|
|
916
936
|
type: "object",
|
|
917
937
|
properties: {
|
|
918
|
-
source: { type: "string", description: "Path to the pack directory to
|
|
938
|
+
source: { type: "string", description: "Path to the pack directory, or an https:// URL to a .tar.gz pack archive" }
|
|
919
939
|
},
|
|
920
940
|
required: ["source"]
|
|
921
941
|
},
|
|
922
942
|
handler: async (args, plur) => {
|
|
923
|
-
const result = plur.installPack(args.source);
|
|
943
|
+
const result = await plur.installPack(args.source);
|
|
924
944
|
return {
|
|
925
945
|
installed: result.installed,
|
|
926
946
|
name: result.name,
|
|
@@ -1015,16 +1035,18 @@ function getAllToolDefinitions() {
|
|
|
1015
1035
|
}
|
|
1016
1036
|
},
|
|
1017
1037
|
handler: async (args, plur) => {
|
|
1018
|
-
const result = plur.sync(args.remote, {
|
|
1038
|
+
const result = await plur.sync(args.remote, {
|
|
1019
1039
|
full: args.full === true,
|
|
1020
1040
|
...args.remote_type === "personal" || args.remote_type === "shared" ? { remoteType: args.remote_type } : {}
|
|
1021
1041
|
});
|
|
1022
1042
|
await plur.waitForIndex();
|
|
1023
1043
|
const indexError = plur.lastIndexError();
|
|
1024
1044
|
let outbox_result;
|
|
1045
|
+
let outbox_error;
|
|
1025
1046
|
try {
|
|
1026
1047
|
outbox_result = await plur.flushOutbox();
|
|
1027
|
-
} catch {
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
outbox_error = err.message;
|
|
1028
1050
|
}
|
|
1029
1051
|
return {
|
|
1030
1052
|
...result,
|
|
@@ -1038,6 +1060,10 @@ function getAllToolDefinitions() {
|
|
|
1038
1060
|
pending: outbox_result.failed,
|
|
1039
1061
|
warnings: outbox_result.expired_warnings
|
|
1040
1062
|
}
|
|
1063
|
+
} : {},
|
|
1064
|
+
...outbox_error ? {
|
|
1065
|
+
outbox_error,
|
|
1066
|
+
outbox_warning: `The outbox flush failed \u2014 ${outbox_error}. Engrams routed to a remote store are still queued locally and were NOT pushed. They retry on the next session_start or plur_sync.`
|
|
1041
1067
|
} : {}
|
|
1042
1068
|
};
|
|
1043
1069
|
}
|
|
@@ -1077,11 +1103,11 @@ function getAllToolDefinitions() {
|
|
|
1077
1103
|
args.llm_api_key,
|
|
1078
1104
|
args.llm_model
|
|
1079
1105
|
);
|
|
1080
|
-
const sourceEngrams = plur.list({
|
|
1106
|
+
const sourceEngrams = await plur.list({
|
|
1081
1107
|
domain: args.domain,
|
|
1082
1108
|
scope: args.scope
|
|
1083
1109
|
});
|
|
1084
|
-
const existingMetas = plur.list().filter((e) => e.id.startsWith("META-"));
|
|
1110
|
+
const existingMetas = (await plur.list()).filter((e) => e.id.startsWith("META-"));
|
|
1085
1111
|
const result = await extractMetaEngrams(sourceEngrams, llm, {
|
|
1086
1112
|
run_validation: args.run_validation,
|
|
1087
1113
|
existing_metas: existingMetas
|
|
@@ -1089,7 +1115,7 @@ function getAllToolDefinitions() {
|
|
|
1089
1115
|
const isDryRun = args.dry_run === true;
|
|
1090
1116
|
let saveStats = null;
|
|
1091
1117
|
if (!isDryRun && result.results.length > 0) {
|
|
1092
|
-
saveStats = plur.saveMetaEngrams(result.results);
|
|
1118
|
+
saveStats = await plur.saveMetaEngrams(result.results);
|
|
1093
1119
|
}
|
|
1094
1120
|
return {
|
|
1095
1121
|
engrams_analyzed: result.engrams_analyzed,
|
|
@@ -1125,7 +1151,7 @@ function getAllToolDefinitions() {
|
|
|
1125
1151
|
}
|
|
1126
1152
|
},
|
|
1127
1153
|
handler: async (args, plur) => {
|
|
1128
|
-
const allEngrams = plur.list();
|
|
1154
|
+
const allEngrams = await plur.list();
|
|
1129
1155
|
const metaEngrams = allEngrams.filter((e) => e.id.startsWith("META-"));
|
|
1130
1156
|
const minConfidence = args.min_confidence ?? 0;
|
|
1131
1157
|
const levelFilter = args.hierarchy_level;
|
|
@@ -1176,20 +1202,20 @@ function getAllToolDefinitions() {
|
|
|
1176
1202
|
required: ["meta_engram_id", "test_domain", "llm_base_url", "llm_api_key"]
|
|
1177
1203
|
},
|
|
1178
1204
|
handler: async (args, plur) => {
|
|
1179
|
-
const allEngrams = plur.list();
|
|
1205
|
+
const allEngrams = await plur.list();
|
|
1180
1206
|
const meta = allEngrams.find((e) => e.id === args.meta_engram_id);
|
|
1181
1207
|
if (!meta) {
|
|
1182
1208
|
throw new Error(`Meta-engram not found: ${args.meta_engram_id}`);
|
|
1183
1209
|
}
|
|
1184
1210
|
const testDomain = args.test_domain;
|
|
1185
|
-
const testEngrams = plur.list({ domain: testDomain });
|
|
1211
|
+
const testEngrams = await plur.list({ domain: testDomain });
|
|
1186
1212
|
const llm = makeHttpLlm(
|
|
1187
1213
|
args.llm_base_url,
|
|
1188
1214
|
args.llm_api_key,
|
|
1189
1215
|
args.llm_model
|
|
1190
1216
|
);
|
|
1191
1217
|
const result = await validateMetaEngram(meta, testEngrams, testDomain, llm);
|
|
1192
|
-
plur.updateEngram(meta);
|
|
1218
|
+
await plur.updateEngram(meta);
|
|
1193
1219
|
return {
|
|
1194
1220
|
meta_engram_id: result.meta_engram_id,
|
|
1195
1221
|
test_domain: result.test_domain,
|
|
@@ -1214,7 +1240,7 @@ function getAllToolDefinitions() {
|
|
|
1214
1240
|
}
|
|
1215
1241
|
},
|
|
1216
1242
|
handler: async (args, plur) => {
|
|
1217
|
-
const status = plur.status({
|
|
1243
|
+
const status = await plur.status({
|
|
1218
1244
|
domain: args.domain,
|
|
1219
1245
|
created_after: args.created_after
|
|
1220
1246
|
});
|
|
@@ -1246,7 +1272,7 @@ function getAllToolDefinitions() {
|
|
|
1246
1272
|
behind: minorVersionsBehind(versionCheck.current, versionCheck.latest)
|
|
1247
1273
|
}
|
|
1248
1274
|
} : {},
|
|
1249
|
-
capabilities: mcpCanary.status()
|
|
1275
|
+
capabilities: await mcpCanary.status()
|
|
1250
1276
|
};
|
|
1251
1277
|
}
|
|
1252
1278
|
},
|
|
@@ -1262,7 +1288,7 @@ function getAllToolDefinitions() {
|
|
|
1262
1288
|
},
|
|
1263
1289
|
handler: async (args, plur) => {
|
|
1264
1290
|
const days = typeof args.days === "number" && Number.isFinite(args.days) && args.days >= 1 ? Math.floor(args.days) : void 0;
|
|
1265
|
-
const receipt = plur.receipt(days ? { days } : void 0);
|
|
1291
|
+
const receipt = await plur.receipt(days ? { days } : void 0);
|
|
1266
1292
|
return { summary: receiptSummary(receipt), ...receipt };
|
|
1267
1293
|
}
|
|
1268
1294
|
},
|
|
@@ -1282,7 +1308,7 @@ function getAllToolDefinitions() {
|
|
|
1282
1308
|
plur.resetEmbedder();
|
|
1283
1309
|
plur.resetReranker();
|
|
1284
1310
|
}
|
|
1285
|
-
const status = plur.status();
|
|
1311
|
+
const status = await plur.status();
|
|
1286
1312
|
const before = plur.embedderStatus();
|
|
1287
1313
|
if (!before.disabled) {
|
|
1288
1314
|
try {
|
|
@@ -1385,7 +1411,7 @@ function getAllToolDefinitions() {
|
|
|
1385
1411
|
evalStatus = { result: run.result, stale: false };
|
|
1386
1412
|
freshlyRun = !run.cached;
|
|
1387
1413
|
} else {
|
|
1388
|
-
evalStatus = plur.rerankerEvalStatus(rerankerName);
|
|
1414
|
+
evalStatus = await plur.rerankerEvalStatus(rerankerName);
|
|
1389
1415
|
}
|
|
1390
1416
|
if (!evalStatus) {
|
|
1391
1417
|
checks.push({
|
|
@@ -1418,7 +1444,7 @@ function getAllToolDefinitions() {
|
|
|
1418
1444
|
});
|
|
1419
1445
|
}
|
|
1420
1446
|
}
|
|
1421
|
-
const canaryStatuses = mcpCanary.status();
|
|
1447
|
+
const canaryStatuses = await mcpCanary.status();
|
|
1422
1448
|
for (const cs of canaryStatuses) {
|
|
1423
1449
|
if (!cs.healthy) {
|
|
1424
1450
|
checks.push({ check: `capability: ${cs.capability}`, ok: false, detail: cs.warning });
|
|
@@ -1480,16 +1506,17 @@ function getAllToolDefinitions() {
|
|
|
1480
1506
|
const task = args.task;
|
|
1481
1507
|
const tags = args.tags;
|
|
1482
1508
|
_cleanExpiredSessions();
|
|
1483
|
-
_activeSessionId = session_id;
|
|
1484
1509
|
_sessionTelemetry.set(session_id, {
|
|
1485
1510
|
pack_counts: {},
|
|
1486
1511
|
injection_calls: 0,
|
|
1487
1512
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1488
1513
|
});
|
|
1489
1514
|
let outbox_result;
|
|
1515
|
+
let outbox_error;
|
|
1490
1516
|
try {
|
|
1491
1517
|
outbox_result = await plur.flushOutbox();
|
|
1492
|
-
} catch {
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
outbox_error = err.message;
|
|
1493
1520
|
}
|
|
1494
1521
|
const remote_scopes = plur.getWritableRemoteScopes().map((s) => {
|
|
1495
1522
|
const md = plur.getScopeMetadata(s.scope);
|
|
@@ -1504,7 +1531,7 @@ function getAllToolDefinitions() {
|
|
|
1504
1531
|
const default_scope = explicit_default_scope ?? projectConfig.scope ?? null;
|
|
1505
1532
|
const scope_source = explicit_default_scope ? "caller" : projectConfig.scope ? "project-config" : "none";
|
|
1506
1533
|
plur.setSessionScope(default_scope);
|
|
1507
|
-
const status = plur.status();
|
|
1534
|
+
const status = await plur.status();
|
|
1508
1535
|
const store_stats = {
|
|
1509
1536
|
engram_count: status.engram_count,
|
|
1510
1537
|
episode_count: status.episode_count,
|
|
@@ -1529,7 +1556,7 @@ function getAllToolDefinitions() {
|
|
|
1529
1556
|
engrams = { text: lines.join("\n"), count: result.count, injected_ids: result.injected_ids };
|
|
1530
1557
|
}
|
|
1531
1558
|
} catch {
|
|
1532
|
-
const result = plur.inject(task, {
|
|
1559
|
+
const result = await plur.inject(task, {
|
|
1533
1560
|
scope: tags?.length ? `tags:${tags.join(",")}` : void 0,
|
|
1534
1561
|
session_id,
|
|
1535
1562
|
source: "session_start"
|
|
@@ -1647,6 +1674,10 @@ Remote store scopes available: ${scopeList}. Set scope PER ENGRAM by content: wh
|
|
|
1647
1674
|
warnings: outbox_result.expired_warnings
|
|
1648
1675
|
}
|
|
1649
1676
|
} : {},
|
|
1677
|
+
...outbox_error ? {
|
|
1678
|
+
outbox_error,
|
|
1679
|
+
outbox_warning: `The outbox flush failed \u2014 ${outbox_error}. Engrams routed to a remote store are still queued locally and were NOT pushed. They retry on the next session_start or plur_sync.`
|
|
1680
|
+
} : {},
|
|
1650
1681
|
// Version staleness warning (issue #151)
|
|
1651
1682
|
...version_warning ? { version_warning, version: VERSION } : {}
|
|
1652
1683
|
};
|
|
@@ -1712,7 +1743,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1712
1743
|
`engram_suggestions[${i}] must be a string or {statement: string, type?: string}, got ${typeof s}`
|
|
1713
1744
|
);
|
|
1714
1745
|
}
|
|
1715
|
-
plur.learn(statement, { type });
|
|
1746
|
+
await plur.learn(statement, { type });
|
|
1716
1747
|
engrams_created++;
|
|
1717
1748
|
}
|
|
1718
1749
|
}
|
|
@@ -1728,7 +1759,6 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1728
1759
|
} : void 0;
|
|
1729
1760
|
if (session_id) {
|
|
1730
1761
|
_sessionTelemetry.delete(session_id);
|
|
1731
|
-
if (_activeSessionId === session_id) _activeSessionId = void 0;
|
|
1732
1762
|
}
|
|
1733
1763
|
try {
|
|
1734
1764
|
const plurDir = process.env.PLUR_PATH ?? join(homedir(), ".plur");
|
|
@@ -1743,7 +1773,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1743
1773
|
}
|
|
1744
1774
|
} catch {
|
|
1745
1775
|
}
|
|
1746
|
-
const status = plur.status();
|
|
1776
|
+
const status = await plur.status();
|
|
1747
1777
|
return {
|
|
1748
1778
|
engrams_created,
|
|
1749
1779
|
episode_id: episode.id,
|
|
@@ -1805,7 +1835,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1805
1835
|
inputSchema: { type: "object", properties: {} },
|
|
1806
1836
|
handler: async (_args, plur) => {
|
|
1807
1837
|
const stores = await plur.listStoresAsync();
|
|
1808
|
-
const outboxCount = plur.outboxCount();
|
|
1838
|
+
const outboxCount = await plur.outboxCount();
|
|
1809
1839
|
return {
|
|
1810
1840
|
stores,
|
|
1811
1841
|
count: stores.length,
|
|
@@ -1831,7 +1861,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1831
1861
|
const raw = args.min_confidence;
|
|
1832
1862
|
const explicit = typeof raw === "number" && Number.isFinite(raw) ? Math.min(1, Math.max(0, raw)) : void 0;
|
|
1833
1863
|
const minConfidence = explicit ?? plur.getScopeRoutingConfig().min_confidence ?? SUGGEST_DISPLAY_MIN_CONFIDENCE;
|
|
1834
|
-
const candidates = plur.suggestScope({
|
|
1864
|
+
const candidates = await plur.suggestScope({
|
|
1835
1865
|
statement: args.statement,
|
|
1836
1866
|
domain: args.domain,
|
|
1837
1867
|
tags: args.tags
|
|
@@ -1897,7 +1927,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1897
1927
|
const promoted = [];
|
|
1898
1928
|
const errors = [];
|
|
1899
1929
|
for (const id of targetIds) {
|
|
1900
|
-
const engram = plur.getById(id);
|
|
1930
|
+
const engram = await plur.getById(id);
|
|
1901
1931
|
if (!engram) {
|
|
1902
1932
|
errors.push({ id, error: "Not found" });
|
|
1903
1933
|
continue;
|
|
@@ -1914,7 +1944,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1914
1944
|
engram.activation.retrieval_strength = 0.7;
|
|
1915
1945
|
engram.activation.storage_strength = 1;
|
|
1916
1946
|
engram.activation.last_accessed = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1917
|
-
plur.updateEngram(engram);
|
|
1947
|
+
await plur.updateEngram(engram);
|
|
1918
1948
|
promoted.push({ id, statement: engram.statement });
|
|
1919
1949
|
}
|
|
1920
1950
|
return { promoted, errors, success: errors.length === 0 };
|
|
@@ -1959,12 +1989,12 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1959
1989
|
if (args.action === "resolve") {
|
|
1960
1990
|
const winner = args.winner;
|
|
1961
1991
|
if (!winner) throw new Error('action:"resolve" requires winner (the engram id to keep)');
|
|
1962
|
-
const { record, retired_id } = plur.resolveTension(id, winner);
|
|
1992
|
+
const { record, retired_id } = await plur.resolveTension(id, winner);
|
|
1963
1993
|
return { record, retired: retired_id, message: `Tension ${id} resolved: ${winner} wins, ${retired_id} retired.` };
|
|
1964
1994
|
}
|
|
1965
1995
|
throw new Error(`Unknown action: ${args.action}. Use confirm, dismiss, or resolve.`);
|
|
1966
1996
|
}
|
|
1967
|
-
const engrams = plur.list({
|
|
1997
|
+
const engrams = await plur.list({
|
|
1968
1998
|
scope: args.scope,
|
|
1969
1999
|
domain: args.domain
|
|
1970
2000
|
});
|
|
@@ -1988,7 +2018,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1988
2018
|
temporal_discount: args.temporal_discount ?? tensionsConfig.temporal_discount,
|
|
1989
2019
|
...persist ? { exclude_pairs: new Set(plur.suppressedTensionPairKeys()) } : {}
|
|
1990
2020
|
});
|
|
1991
|
-
const persisted = persist && result.tensions.length > 0 ? plur.recordTensions(result.tensions) : void 0;
|
|
2021
|
+
const persisted = persist && result.tensions.length > 0 ? await plur.recordTensions(result.tensions) : void 0;
|
|
1992
2022
|
return {
|
|
1993
2023
|
pairs_checked: result.pairs_checked,
|
|
1994
2024
|
count: result.new_tensions,
|
|
@@ -2041,7 +2071,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2041
2071
|
annotations: { title: "Purge Tensions", destructiveHint: true, idempotentHint: true },
|
|
2042
2072
|
inputSchema: { type: "object", properties: {} },
|
|
2043
2073
|
handler: async (_args, plur) => {
|
|
2044
|
-
const result = plur.purgeTensions();
|
|
2074
|
+
const result = await plur.purgeTensions();
|
|
2045
2075
|
return {
|
|
2046
2076
|
purged_conflict_refs: result.purged_count,
|
|
2047
2077
|
engrams_modified: result.engrams_modified,
|
|
@@ -2064,7 +2094,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2064
2094
|
required: ["episode_id"]
|
|
2065
2095
|
},
|
|
2066
2096
|
handler: async (args, plur) => {
|
|
2067
|
-
const engram = plur.episodeToEngram(args.episode_id, {
|
|
2097
|
+
const engram = await plur.episodeToEngram(args.episode_id, {
|
|
2068
2098
|
scope: args.scope,
|
|
2069
2099
|
domain: args.domain,
|
|
2070
2100
|
tags: args.tags
|
|
@@ -2101,7 +2131,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2101
2131
|
};
|
|
2102
2132
|
}
|
|
2103
2133
|
const { listHistoryMonths, readHistory } = await import("@plur-ai/core");
|
|
2104
|
-
const status = plur.status();
|
|
2134
|
+
const status = await plur.status();
|
|
2105
2135
|
const months = listHistoryMonths(status.storage_root);
|
|
2106
2136
|
const allEvents = [];
|
|
2107
2137
|
for (const month of months.reverse()) {
|
|
@@ -2174,7 +2204,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2174
2204
|
},
|
|
2175
2205
|
handler: async (args, plur) => {
|
|
2176
2206
|
const name = args.name;
|
|
2177
|
-
let engrams = plur.list({
|
|
2207
|
+
let engrams = await plur.list({
|
|
2178
2208
|
domain: args.filter_domain,
|
|
2179
2209
|
scope: args.filter_scope
|
|
2180
2210
|
});
|
|
@@ -2188,9 +2218,9 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2188
2218
|
if (filterType) {
|
|
2189
2219
|
engrams = engrams.filter((e) => e.type === filterType);
|
|
2190
2220
|
}
|
|
2191
|
-
const { homedir:
|
|
2192
|
-
const { join:
|
|
2193
|
-
const outputDir = args.output_dir ||
|
|
2221
|
+
const { homedir: homedir2 } = await import("os");
|
|
2222
|
+
const { join: join2 } = await import("path");
|
|
2223
|
+
const outputDir = args.output_dir || join2(homedir2(), "plur-packs", name);
|
|
2194
2224
|
const result = plur.exportPack(engrams, outputDir, {
|
|
2195
2225
|
name,
|
|
2196
2226
|
version: "1.0.0",
|
|
@@ -2255,7 +2285,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2255
2285
|
}
|
|
2256
2286
|
},
|
|
2257
2287
|
handler: async (args, plur) => {
|
|
2258
|
-
const status = plur.status();
|
|
2288
|
+
const status = await plur.status();
|
|
2259
2289
|
const storagePath = status.storage_root;
|
|
2260
2290
|
if (!args.force_regenerate) {
|
|
2261
2291
|
const cached = getProfileForInjection(storagePath);
|
|
@@ -2268,7 +2298,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2268
2298
|
}
|
|
2269
2299
|
const model = args.llm_model ?? selectModelForOperation("profile", status.config?.llm);
|
|
2270
2300
|
const llm = makeHttpLlm(args.llm_base_url, args.llm_api_key, model);
|
|
2271
|
-
const engrams = plur.list({ scope: args.scope });
|
|
2301
|
+
const engrams = await plur.list({ scope: args.scope });
|
|
2272
2302
|
const profile = await generateProfile(engrams, llm, storagePath, status.config?.profile?.cache_ttl_hours ?? 24);
|
|
2273
2303
|
return { profile, source: "generated", engram_count: engrams.length, model };
|
|
2274
2304
|
}
|
|
@@ -2276,367 +2306,11 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2276
2306
|
];
|
|
2277
2307
|
}
|
|
2278
2308
|
|
|
2279
|
-
// src/server.ts
|
|
2280
|
-
function serverPidPath(baseDir) {
|
|
2281
|
-
return join2(baseDir ?? join2(homedir2(), ".plur"), "server.pid");
|
|
2282
|
-
}
|
|
2283
|
-
function readEnterpriseToken(baseDir) {
|
|
2284
|
-
const configPath = join2(baseDir ?? join2(homedir2(), ".plur"), "config.json");
|
|
2285
|
-
if (!existsSync2(configPath)) return void 0;
|
|
2286
|
-
try {
|
|
2287
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf8"));
|
|
2288
|
-
const ent = cfg?.enterprise;
|
|
2289
|
-
if (!ent || typeof ent.url !== "string" || typeof ent.token !== "string") return void 0;
|
|
2290
|
-
return { url: ent.url, token: ent.token, username: ent.username };
|
|
2291
|
-
} catch {
|
|
2292
|
-
return void 0;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
var _pendingReload = false;
|
|
2296
|
-
function isPendingReload() {
|
|
2297
|
-
return _pendingReload;
|
|
2298
|
-
}
|
|
2299
|
-
function clearPendingReload() {
|
|
2300
|
-
_pendingReload = false;
|
|
2301
|
-
}
|
|
2302
|
-
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
2303
|
-
|
|
2304
|
-
PLUR is a GLOBAL tool \u2014 one MCP server, one engram store (~/.plur/), available in every project. Multi-project scoping uses domain/scope fields on engrams, not separate installations.
|
|
2305
|
-
|
|
2306
|
-
TOOL PROFILE: by default only the core session tools are exposed directly (lean profile). Every other plur_* operation is reachable via plur_admin: { action: "<tool name>", args: {...} } \u2014 same arguments and validation as a direct call. PLUR_TOOL_PROFILE=full exposes everything directly.
|
|
2307
|
-
|
|
2308
|
-
SESSION LIFECYCLE:
|
|
2309
|
-
- With hooks installed (plur init): engrams are injected automatically on first message. You do NOT need to call plur_session_start \u2014 it happens via hooks. Just call plur_session_end before the conversation ends.
|
|
2310
|
-
- Without hooks: call plur_session_start at the start, plur_session_end at the end.
|
|
2311
|
-
|
|
2312
|
-
DURING the session:
|
|
2313
|
-
- When user corrects you ("no, use X not Y") \u2192 call plur_learn immediately
|
|
2314
|
-
- When user states a preference ("always X", "never Y") \u2192 call plur_learn immediately
|
|
2315
|
-
- When you discover a codebase convention or pattern \u2192 call plur_learn
|
|
2316
|
-
|
|
2317
|
-
SCOPE SELECTION (set scope PER engram, by content \u2014 not once per session):
|
|
2318
|
-
A single session produces engrams that belong in different stores. Choose the
|
|
2319
|
-
"scope" on EACH plur_learn call from what the engram is about:
|
|
2320
|
-
- Team / engineering / architecture / shared-convention knowledge \u2192 the matching
|
|
2321
|
-
team store scope (e.g. "group:<org>/<team>"). plur_session_start lists the
|
|
2322
|
-
scopes this install can write to \u2014 use them.
|
|
2323
|
-
- Personal preferences, your workflow, or details specific to one project \u2192 the
|
|
2324
|
-
default/local scope (or "project:<name>").
|
|
2325
|
-
- "global" is for genuinely cross-project facts only (language gotchas, tool
|
|
2326
|
-
quirks). Do NOT let team-relevant knowledge fall back to "global" by omitting
|
|
2327
|
-
scope \u2014 with a team store configured it never reaches the team store. If no
|
|
2328
|
-
scope fits, prefer the project/local default over global.
|
|
2329
|
-
|
|
2330
|
-
OPTIONAL but improves quality:
|
|
2331
|
-
- Call plur_feedback to rate which injected engrams helped (positive/negative)
|
|
2332
|
-
- Call plur_recall_hybrid before answering factual questions \u2014 the answer may be in memory
|
|
2333
|
-
|
|
2334
|
-
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
2335
|
-
|
|
2336
|
-
Setup: If this is a fresh install, suggest the user run: npx @plur-ai/mcp init
|
|
2337
|
-
This installs hooks for automatic injection + session management. One-time global setup.`;
|
|
2338
|
-
var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
|
|
2339
|
-
|
|
2340
|
-
## What is PLUR?
|
|
2341
|
-
|
|
2342
|
-
Persistent memory for AI agents. Corrections, preferences, and conventions are stored as **engrams** \u2014 small assertions that strengthen with use and decay when irrelevant (ACT-R model). Storage is plain YAML on disk. Search is fully local (BM25 + embeddings). Zero API calls.
|
|
2343
|
-
|
|
2344
|
-
## Quick Start
|
|
2345
|
-
|
|
2346
|
-
1. \`plur_session_start\` \u2014 start a session, inject relevant context
|
|
2347
|
-
2. \`plur_learn\` \u2014 store a new learning
|
|
2348
|
-
3. \`plur_feedback\` \u2014 rate injected engrams
|
|
2349
|
-
4. \`plur_session_end\` \u2014 capture summary and new learnings
|
|
2350
|
-
|
|
2351
|
-
## When to Call Each Tool
|
|
2352
|
-
|
|
2353
|
-
| Trigger | Tool |
|
|
2354
|
-
|---------|------|
|
|
2355
|
-
| Session starts | \`plur_session_start\` with task description |
|
|
2356
|
-
| User corrects you | \`plur_learn\` with the correction |
|
|
2357
|
-
| User states preference ("always X", "never Y") | \`plur_learn\` with scope and type |
|
|
2358
|
-
| You used a recalled engram successfully | \`plur_feedback\` with "positive" |
|
|
2359
|
-
| A recalled engram was wrong or irrelevant | \`plur_feedback\` with "negative" |
|
|
2360
|
-
| User says "forget X" or a memory is outdated | \`plur_forget\` |
|
|
2361
|
-
| You need to check what's stored | \`plur_status\` or \`plur_packs_list\` |
|
|
2362
|
-
| User asks what memory did for them / is memory working | \`plur_receipt\` (relay its \`summary\`; activation_rate is coverage, not quality) |
|
|
2363
|
-
| End of session | \`plur_session_end\` with summary and suggestions |
|
|
2364
|
-
|
|
2365
|
-
## Tool Categories
|
|
2366
|
-
|
|
2367
|
-
### Session Management
|
|
2368
|
-
- **plur_session_start** \u2014 start a session, inject relevant context
|
|
2369
|
-
- **plur_session_end** \u2014 end a session, capture summary and new learnings
|
|
2370
|
-
|
|
2371
|
-
### Core Memory
|
|
2372
|
-
- **plur_learn** \u2014 store a correction, preference, or convention
|
|
2373
|
-
- **plur_recall** \u2014 BM25 keyword search
|
|
2374
|
-
- **plur_recall_hybrid** \u2014 BM25 + embeddings (recommended default)
|
|
2375
|
-
- **plur_feedback** \u2014 rate an engram (trains relevance)
|
|
2376
|
-
- **plur_forget** \u2014 retire an outdated engram
|
|
2377
|
-
- **plur_promote** \u2014 activate a candidate engram
|
|
2378
|
-
|
|
2379
|
-
### Context Injection
|
|
2380
|
-
- **plur_inject** \u2014 select engrams for a task (BM25)
|
|
2381
|
-
- **plur_inject_hybrid** \u2014 select engrams for a task (BM25 + embeddings, recommended)
|
|
2382
|
-
|
|
2383
|
-
### Episodic Timeline
|
|
2384
|
-
- **plur_capture** \u2014 record what happened in a session
|
|
2385
|
-
- **plur_timeline** \u2014 query past episodes
|
|
2386
|
-
|
|
2387
|
-
### Knowledge Management
|
|
2388
|
-
- **plur_ingest** \u2014 extract engrams from text content
|
|
2389
|
-
- **plur_packs_install** \u2014 install curated engram packs
|
|
2390
|
-
- **plur_packs_list** \u2014 list installed packs
|
|
2391
|
-
- **plur_packs_export** \u2014 export engrams as a shareable pack
|
|
2392
|
-
|
|
2393
|
-
### Multi-Store
|
|
2394
|
-
- **plur_stores_add** \u2014 register an additional engram store
|
|
2395
|
-
- **plur_stores_list** \u2014 list all configured stores
|
|
2396
|
-
|
|
2397
|
-
**Note:** Multi-store is currently config-only. Recall and inject search the primary store. Cross-store search coming in a future release.
|
|
2398
|
-
|
|
2399
|
-
### Sync & Status
|
|
2400
|
-
- **plur_sync** \u2014 sync engrams across devices via git
|
|
2401
|
-
- **plur_sync_status** \u2014 check sync state
|
|
2402
|
-
- **plur_status** \u2014 system health
|
|
2403
|
-
- **plur_receipt** \u2014 counted report of what memory retrieved for the user (local, read-only)
|
|
2404
|
-
|
|
2405
|
-
## Scoping
|
|
2406
|
-
|
|
2407
|
-
Use \`scope\` to namespace engrams per project:
|
|
2408
|
-
- \`scope: "global"\` \u2014 applies everywhere (default)
|
|
2409
|
-
- \`scope: "project:my-app"\` \u2014 applies only to my-app
|
|
2410
|
-
- Scoped recall automatically includes global engrams
|
|
2411
|
-
|
|
2412
|
-
## Storage
|
|
2413
|
-
|
|
2414
|
-
\`\`\`
|
|
2415
|
-
~/.plur/
|
|
2416
|
-
\u251C\u2500\u2500 engrams.yaml # learned knowledge
|
|
2417
|
-
\u251C\u2500\u2500 episodes.yaml # session timeline
|
|
2418
|
-
\u2514\u2500\u2500 config.yaml # settings
|
|
2419
|
-
\`\`\`
|
|
2420
|
-
|
|
2421
|
-
Override with \`PLUR_PATH\` environment variable.
|
|
2422
|
-
`;
|
|
2423
|
-
async function createServer(plur, options) {
|
|
2424
|
-
const instance = plur ?? new Plur2();
|
|
2425
|
-
const tools = getToolDefinitions(options?.profile ?? "lean");
|
|
2426
|
-
checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
|
|
2427
|
-
if (r.updateAvailable) {
|
|
2428
|
-
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
|
|
2429
|
-
}
|
|
2430
|
-
});
|
|
2431
|
-
const server = new Server(
|
|
2432
|
-
{ name: "plur-mcp", version: VERSION },
|
|
2433
|
-
{
|
|
2434
|
-
capabilities: {
|
|
2435
|
-
tools: {},
|
|
2436
|
-
resources: {},
|
|
2437
|
-
prompts: {},
|
|
2438
|
-
logging: {}
|
|
2439
|
-
},
|
|
2440
|
-
instructions: INSTRUCTIONS
|
|
2441
|
-
}
|
|
2442
|
-
);
|
|
2443
|
-
server.setRequestHandler("tools/list", async () => ({
|
|
2444
|
-
tools: tools.map((t) => ({
|
|
2445
|
-
name: t.name,
|
|
2446
|
-
description: t.description,
|
|
2447
|
-
inputSchema: t.inputSchema,
|
|
2448
|
-
...t.annotations && { annotations: t.annotations }
|
|
2449
|
-
}))
|
|
2450
|
-
}));
|
|
2451
|
-
server.setRequestHandler("tools/call", async (request) => {
|
|
2452
|
-
const tool = tools.find((t) => t.name === request.params.name);
|
|
2453
|
-
if (!tool) {
|
|
2454
|
-
const hidden = getToolDefinitions("full").find((t) => t.name === request.params.name);
|
|
2455
|
-
if (hidden) {
|
|
2456
|
-
return {
|
|
2457
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
2458
|
-
error: `Tool "${request.params.name}" exists but is not directly callable under the current tool profile.`,
|
|
2459
|
-
success: false,
|
|
2460
|
-
hint: `Call it via plur_admin: { action: "${request.params.name}", args: { ... } } \u2014 same arguments, same validation, same result. To expose all tools directly, set PLUR_TOOL_PROFILE=full.`
|
|
2461
|
-
}) }],
|
|
2462
|
-
isError: true
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
return {
|
|
2466
|
-
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${request.params.name}`, success: false }) }],
|
|
2467
|
-
isError: true
|
|
2468
|
-
};
|
|
2469
|
-
}
|
|
2470
|
-
mcpCanary.tick();
|
|
2471
|
-
try {
|
|
2472
|
-
let args = request.params.arguments ?? {};
|
|
2473
|
-
const validated = validateToolArgs(tool, args);
|
|
2474
|
-
if (!validated.ok) {
|
|
2475
|
-
return {
|
|
2476
|
-
content: [{ type: "text", text: JSON.stringify(validated.errorPayload) }],
|
|
2477
|
-
isError: true
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
|
-
args = validated.data;
|
|
2481
|
-
const result = await tool.handler(args, instance);
|
|
2482
|
-
let payload = result;
|
|
2483
|
-
let resultIsError = false;
|
|
2484
|
-
if (result && typeof result === "object" && result._isError === true) {
|
|
2485
|
-
resultIsError = true;
|
|
2486
|
-
const { _isError, ...rest } = result;
|
|
2487
|
-
payload = rest;
|
|
2488
|
-
}
|
|
2489
|
-
return {
|
|
2490
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
2491
|
-
...resultIsError ? { isError: true } : {}
|
|
2492
|
-
};
|
|
2493
|
-
} catch (err) {
|
|
2494
|
-
const message = err?.message ?? String(err);
|
|
2495
|
-
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
|
|
2496
|
-
return {
|
|
2497
|
-
content: [{ type: "text", text: JSON.stringify({ error: message, success: false }) }],
|
|
2498
|
-
isError: true
|
|
2499
|
-
};
|
|
2500
|
-
}
|
|
2501
|
-
});
|
|
2502
|
-
server.setRequestHandler("resources/list", async () => ({
|
|
2503
|
-
resources: [
|
|
2504
|
-
{
|
|
2505
|
-
uri: "plur://guide",
|
|
2506
|
-
name: "PLUR Agent Guide",
|
|
2507
|
-
description: "Complete reference for all PLUR tools, when to use them, scoping, and storage",
|
|
2508
|
-
mimeType: "text/markdown"
|
|
2509
|
-
},
|
|
2510
|
-
{
|
|
2511
|
-
uri: "plur://status",
|
|
2512
|
-
name: "PLUR Status",
|
|
2513
|
-
description: "Live system health \u2014 engram count, episode count, pack count, storage path",
|
|
2514
|
-
mimeType: "application/json"
|
|
2515
|
-
}
|
|
2516
|
-
]
|
|
2517
|
-
}));
|
|
2518
|
-
server.setRequestHandler("resources/read", async (request) => {
|
|
2519
|
-
const uri = request.params.uri;
|
|
2520
|
-
if (uri === "plur://guide") {
|
|
2521
|
-
const cursorNote = options?.profile === "cursor" || options?.profile === "lean" || options?.profile == null ? `
|
|
2522
|
-
|
|
2523
|
-
## Lean tool profile (default)
|
|
2524
|
-
|
|
2525
|
-
Most tools above are NOT directly callable in this session \u2014 only ${[...CURSOR_CORE_TOOL_NAMES].join(", ")} are top-level tools here. Everything else in this guide is reachable through **plur_admin**: call it with \`{ action: "<tool name above>", args: {...} }\`. Set \`PLUR_TOOL_PROFILE=full\` to expose all ${getToolDefinitions("full").length} tools directly.` : "";
|
|
2526
|
-
return {
|
|
2527
|
-
contents: [{
|
|
2528
|
-
uri: "plur://guide",
|
|
2529
|
-
mimeType: "text/markdown",
|
|
2530
|
-
text: GUIDE_RESOURCE + cursorNote
|
|
2531
|
-
}]
|
|
2532
|
-
};
|
|
2533
|
-
}
|
|
2534
|
-
if (uri === "plur://status") {
|
|
2535
|
-
const status = instance.status();
|
|
2536
|
-
return {
|
|
2537
|
-
contents: [{
|
|
2538
|
-
uri: "plur://status",
|
|
2539
|
-
mimeType: "application/json",
|
|
2540
|
-
text: JSON.stringify({
|
|
2541
|
-
engram_count: status.engram_count,
|
|
2542
|
-
episode_count: status.episode_count,
|
|
2543
|
-
pack_count: status.pack_count,
|
|
2544
|
-
storage_root: status.storage_root,
|
|
2545
|
-
version: VERSION
|
|
2546
|
-
}, null, 2)
|
|
2547
|
-
}]
|
|
2548
|
-
};
|
|
2549
|
-
}
|
|
2550
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown resource: ${uri}`);
|
|
2551
|
-
});
|
|
2552
|
-
server.setRequestHandler("prompts/list", async () => ({
|
|
2553
|
-
prompts: [
|
|
2554
|
-
{
|
|
2555
|
-
name: "plur-getting-started",
|
|
2556
|
-
description: "Step-by-step guide to set up and start using PLUR memory"
|
|
2557
|
-
},
|
|
2558
|
-
{
|
|
2559
|
-
name: "plur-session-start",
|
|
2560
|
-
description: "Load relevant context for a task \u2014 call at the start of each session",
|
|
2561
|
-
arguments: [
|
|
2562
|
-
{ name: "task", description: "Brief description of the task or goal", required: true },
|
|
2563
|
-
{ name: "scope", description: "Project scope (e.g. project:my-app)", required: false }
|
|
2564
|
-
]
|
|
2565
|
-
}
|
|
2566
|
-
]
|
|
2567
|
-
}));
|
|
2568
|
-
server.setRequestHandler("prompts/get", async (request) => {
|
|
2569
|
-
const name = request.params.name;
|
|
2570
|
-
if (name === "plur-getting-started") {
|
|
2571
|
-
const status = instance.status();
|
|
2572
|
-
return {
|
|
2573
|
-
description: "Get started with PLUR memory",
|
|
2574
|
-
messages: [{
|
|
2575
|
-
role: "user",
|
|
2576
|
-
content: {
|
|
2577
|
-
type: "text",
|
|
2578
|
-
text: `I just set up PLUR. Here's my current status:
|
|
2579
|
-
|
|
2580
|
-
- Engrams stored: ${status.engram_count}
|
|
2581
|
-
- Episodes recorded: ${status.episode_count}
|
|
2582
|
-
- Packs installed: ${status.pack_count}
|
|
2583
|
-
- Storage: ${status.storage_root}
|
|
2584
|
-
|
|
2585
|
-
${status.engram_count === 0 ? `I have no memories yet. Help me get started by:
|
|
2586
|
-
1. Teaching me a coding preference or convention (I'll use plur_learn)
|
|
2587
|
-
2. Then recalling it to verify it works (I'll use plur_recall_hybrid)
|
|
2588
|
-
3. Rating the recall quality (I'll use plur_feedback)` : `I have ${status.engram_count} engrams stored. Try asking me something related to your project \u2014 I'll check my memory first.`}`
|
|
2589
|
-
}
|
|
2590
|
-
}]
|
|
2591
|
-
};
|
|
2592
|
-
}
|
|
2593
|
-
if (name === "plur-session-start") {
|
|
2594
|
-
const task = request.params.arguments?.task ?? "general work";
|
|
2595
|
-
const scope = request.params.arguments?.scope;
|
|
2596
|
-
return {
|
|
2597
|
-
description: "Load relevant context for this session",
|
|
2598
|
-
messages: [{
|
|
2599
|
-
role: "user",
|
|
2600
|
-
content: {
|
|
2601
|
-
type: "text",
|
|
2602
|
-
text: `Starting a new session. Task: ${task}${scope ? ` (scope: ${scope})` : ""}
|
|
2603
|
-
|
|
2604
|
-
Please:
|
|
2605
|
-
1. Call plur_recall_hybrid with query "${task}"${scope ? ` and scope "${scope}"` : ""} to load relevant memories
|
|
2606
|
-
2. Review the recalled engrams and apply any relevant conventions or preferences
|
|
2607
|
-
3. If any recalled engrams are helpful, call plur_feedback with "positive"
|
|
2608
|
-
4. If any are irrelevant, call plur_feedback with "negative"`
|
|
2609
|
-
}
|
|
2610
|
-
}]
|
|
2611
|
-
};
|
|
2612
|
-
}
|
|
2613
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown prompt: ${name}`);
|
|
2614
|
-
});
|
|
2615
|
-
return server;
|
|
2616
|
-
}
|
|
2617
|
-
async function runStdio() {
|
|
2618
|
-
const envProfile = process.env.PLUR_TOOL_PROFILE;
|
|
2619
|
-
const profile = envProfile === "full" ? "full" : envProfile === "cursor" ? "cursor" : "lean";
|
|
2620
|
-
const server = await createServer(void 0, { profile });
|
|
2621
|
-
registerFlushOnExit({});
|
|
2622
|
-
try {
|
|
2623
|
-
writeFileSync(serverPidPath(), String(process.pid));
|
|
2624
|
-
} catch {
|
|
2625
|
-
}
|
|
2626
|
-
if (process.platform !== "win32") {
|
|
2627
|
-
process.on("SIGUSR1", () => {
|
|
2628
|
-
_pendingReload = true;
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
const transport = new StdioServerTransport();
|
|
2632
|
-
await server.connect(transport);
|
|
2633
|
-
}
|
|
2634
2309
|
export {
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
serverPidPath
|
|
2310
|
+
registerFlushOnExit,
|
|
2311
|
+
VERSION,
|
|
2312
|
+
validateToolArgs,
|
|
2313
|
+
mcpCanary,
|
|
2314
|
+
CURSOR_CORE_TOOL_NAMES,
|
|
2315
|
+
getToolDefinitions
|
|
2642
2316
|
};
|