@plur-ai/mcp 0.8.0 → 0.8.3

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
5
  import { join } from "path";
6
6
  import { homedir } from "os";
7
- var VERSION = "0.8.0";
7
+ var VERSION = "0.8.3";
8
8
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
9
9
 
10
10
  Usage:
@@ -56,9 +56,15 @@ var CLAUDE_MD_SECTION = `## PLUR Memory
56
56
 
57
57
  You have persistent memory via PLUR. Corrections, preferences, and conventions persist across sessions as engrams.
58
58
 
59
+ ### Architecture
60
+
61
+ PLUR is installed **globally** \u2014 one MCP server, one engram store (\`~/.plur/\`), available in every project. You do NOT need per-project installation. Multi-project scoping uses \`domain\` and \`scope\` fields on engrams, not separate stores.
62
+
63
+ Hooks inject engrams automatically on every first message \u2014 you do not need to call \`plur_session_start\` manually (though you can for explicit session tracking).
64
+
59
65
  ### Session Workflow
60
66
 
61
- 1. **Start**: Call \`plur_session_start\` with task description \u2014 injects relevant engrams
67
+ 1. **Automatic**: Hooks inject relevant engrams on first message \u2014 no action needed
62
68
  2. **Learn**: When corrected or discovering something new, call \`plur_learn\` immediately
63
69
  3. **Recall**: Before answering factual questions, call \`plur_recall_hybrid\` \u2014 check memory first
64
70
  4. **Feedback**: Rate injected engrams with \`plur_feedback\` (positive/negative) \u2014 trains relevance
@@ -66,36 +72,12 @@ You have persistent memory via PLUR. Corrections, preferences, and conventions p
66
72
 
67
73
  Do not ask permission to use these tools \u2014 they are your memory system.
68
74
 
69
- ### When to check memory
70
-
71
- Before reaching for web search, file reads, or guessing \u2014 apply this priority:
72
- 1. Is the answer already in engrams? \u2192 \`plur_recall_hybrid\`
73
- 2. Is the answer in the local filesystem? \u2192 Read/Grep/Glob
74
- 3. Is the answer derivable from context already loaded? \u2192 Just answer
75
- 4. Only if 1-3 fail \u2192 Use external tools
76
-
77
- | Domain | When to recall |
78
- |--------|----------------|
79
- | Decisions | Past design choices, architecture rationale |
80
- | Corrections | API quirks, bugs, wrong assumptions |
81
- | Preferences | Formatting, tone, workflow, tool choices |
82
- | Conventions | Tag formats, file routing, naming rules |
83
- | Infrastructure | Server IPs, SSH configs, deployment targets |
84
-
85
75
  ### When corrected
86
76
 
87
77
  When the user corrects you ("no, use X not Y", "that's wrong"):
88
78
  1. Call \`plur_learn\` immediately \u2014 before continuing the task
89
79
  2. Call \`plur_feedback\` with negative signal on the wrong engram if one was injected
90
80
  3. Then continue with the corrected approach
91
-
92
- ### Verification
93
-
94
- When recalling facts that will drive actions:
95
- 1. State the recalled fact explicitly before acting on it
96
- 2. Include the engram ID or search that produced it
97
- 3. If no engram matches, say so and verify from the filesystem
98
- 4. Never interpolate between two engrams to produce a "probably correct" composite
99
81
  `;
100
82
  function installClaudeMd() {
101
83
  const marker = "## PLUR Memory";
@@ -198,6 +180,10 @@ async function runInit() {
198
180
  results.push(`CLAUDE.md: ${claudeMdStatus}`);
199
181
  process.stdout.write(`PLUR initialized.
200
182
 
183
+ Architecture: PLUR is a global tool \u2014 one MCP server, one engram
184
+ store (~/.plur/), available in every project. Multi-project scoping
185
+ uses domain/scope fields on engrams, not separate installations.
186
+
201
187
  ${results.join("\n ")}
202
188
 
203
189
  `);
@@ -226,7 +212,7 @@ if (arg === "init") {
226
212
  process.exit(0);
227
213
  }
228
214
  if (arg === "serve" || arg === void 0) {
229
- const { runStdio } = await import("./server-JG4LLIW5.js");
215
+ const { runStdio } = await import("./server-YEWTBXTD.js");
230
216
  runStdio().catch((err) => {
231
217
  console.error("Failed to start PLUR MCP server:", err);
232
218
  process.exit(1);
@@ -14,7 +14,7 @@ import {
14
14
  import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
15
15
 
16
16
  // src/tools.ts
17
- import { extractMetaEngrams, validateMetaEngram, confidenceBand } from "@plur-ai/core";
17
+ import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation } from "@plur-ai/core";
18
18
  function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
19
19
  return async (prompt) => {
20
20
  const response = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
@@ -65,6 +65,13 @@ var PLUR_GUIDE = `## PLUR Quick Start
65
65
  - **plur_learn** \u2014 record corrections, preferences, patterns (CALL THIS OFTEN)
66
66
  - **plur_recall_hybrid** \u2014 search engrams by topic
67
67
  - **plur_forget** \u2014 retire an outdated engram`;
68
+ function getLlmFunction() {
69
+ const openaiKey = process.env.OPENAI_API_KEY;
70
+ const openrouterKey = process.env.OPENROUTER_API_KEY;
71
+ if (openrouterKey) return makeHttpLlm("https://openrouter.ai/api/v1", openrouterKey, "openai/gpt-4o-mini");
72
+ if (openaiKey) return makeHttpLlm("https://api.openai.com/v1", openaiKey, "gpt-4o-mini");
73
+ return void 0;
74
+ }
68
75
  function getToolDefinitions() {
69
76
  return [
70
77
  {
@@ -108,12 +115,17 @@ function getToolDefinitions() {
108
115
  description: "Dual coding for richer encoding"
109
116
  },
110
117
  abstract: { type: "string", description: "Abstract engram ID this was derived from" },
111
- derived_from: { type: "string", description: "Source engram ID this was derived from" }
118
+ derived_from: { type: "string", description: "Source engram ID this was derived from" },
119
+ commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "Commitment level (default: leaning)" },
120
+ locked_reason: { type: "string", description: "Reason for locking (when commitment=locked)" },
121
+ memory_class: { type: "string", enum: ["semantic", "episodic", "procedural", "metacognitive"], description: "Memory classification (auto-set from type if omitted)" },
122
+ session_episode_id: { type: "string", description: "Link to current session episode for episodic anchoring" }
112
123
  },
113
124
  required: ["statement"]
114
125
  },
115
126
  handler: async (args, plur) => {
116
- const engram = plur.learn(args.statement, {
127
+ const llm = getLlmFunction();
128
+ const context = {
117
129
  type: args.type,
118
130
  scope: args.scope,
119
131
  domain: args.domain,
@@ -124,9 +136,28 @@ function getToolDefinitions() {
124
136
  knowledge_anchors: args.knowledge_anchors,
125
137
  dual_coding: args.dual_coding,
126
138
  abstract: args.abstract,
127
- derived_from: args.derived_from
128
- });
129
- return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
139
+ derived_from: args.derived_from,
140
+ commitment: args.commitment,
141
+ locked_reason: args.locked_reason,
142
+ memory_class: args.memory_class,
143
+ session_episode_id: args.session_episode_id,
144
+ llm
145
+ };
146
+ try {
147
+ const result = await plur.learnAsync(args.statement, context);
148
+ return {
149
+ id: result.engram.id,
150
+ statement: result.engram.statement,
151
+ scope: result.engram.scope,
152
+ type: result.engram.type,
153
+ decision: result.decision,
154
+ existing_id: result.existing_id,
155
+ tensions: result.tensions
156
+ };
157
+ } catch {
158
+ const engram = plur.learn(args.statement, context);
159
+ return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type, decision: "ADD" };
160
+ }
130
161
  }
131
162
  },
132
163
  {
@@ -140,7 +171,9 @@ function getToolDefinitions() {
140
171
  scope: { type: "string", description: "Filter by scope (also includes global)" },
141
172
  domain: { type: "string", description: "Filter by domain prefix" },
142
173
  limit: { type: "number", description: "Max results to return (default 20)" },
143
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
174
+ min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
175
+ budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" } } },
176
+ caller_session_id: { type: "string", description: "Caller session ID for budget enforcement" }
144
177
  },
145
178
  required: ["query"]
146
179
  },
@@ -175,27 +208,62 @@ function getToolDefinitions() {
175
208
  scope: { type: "string", description: "Filter by scope (also includes global)" },
176
209
  domain: { type: "string", description: "Filter by domain prefix" },
177
210
  limit: { type: "number", description: "Max results to return (default 20)" },
178
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
211
+ min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
212
+ budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" }, ttl_seconds: { type: "number" } } },
213
+ caller_session_id: { type: "string", description: "Session ID of calling agent for budget enforcement" },
214
+ include_episodes: { type: "boolean", description: "If true, include linked episode summaries for each engram (SP2 episodic anchoring)" }
179
215
  },
180
216
  required: ["query"]
181
217
  },
182
218
  handler: async (args, plur) => {
219
+ const budget = args.budget;
220
+ const effectiveLimit = budget?.max_results ?? args.limit ?? 20;
183
221
  const results = await plur.recallHybrid(args.query, {
184
222
  scope: args.scope,
185
223
  domain: args.domain,
186
- limit: args.limit,
224
+ limit: effectiveLimit,
187
225
  min_strength: args.min_strength
188
226
  });
227
+ let truncated = false;
228
+ let boundedResults = results;
229
+ if (budget?.max_results && results.length > budget.max_results) {
230
+ boundedResults = results.slice(0, budget.max_results);
231
+ truncated = true;
232
+ }
233
+ if (budget?.max_tokens) {
234
+ let tokenCount = 0;
235
+ const withinBudget = [];
236
+ for (const e of boundedResults) {
237
+ const tokens = Math.ceil(e.statement.length / 4) + 20;
238
+ if (tokenCount + tokens > budget.max_tokens) {
239
+ truncated = true;
240
+ break;
241
+ }
242
+ withinBudget.push(e);
243
+ tokenCount += tokens;
244
+ }
245
+ boundedResults = withinBudget;
246
+ }
247
+ const includeEpisodes = args.include_episodes === true;
189
248
  return {
190
- results: results.map((e) => ({
191
- id: e.id,
192
- statement: e.statement,
193
- type: e.type,
194
- scope: e.scope,
195
- domain: e.domain,
196
- retrieval_strength: e.activation.retrieval_strength
197
- })),
198
- count: results.length,
249
+ results: boundedResults.map((e) => {
250
+ const raw = e;
251
+ const base = {
252
+ id: e.id,
253
+ statement: e.statement,
254
+ type: e.type,
255
+ scope: e.scope,
256
+ domain: e.domain,
257
+ retrieval_strength: e.activation.retrieval_strength
258
+ };
259
+ if (includeEpisodes && raw.episode_ids?.length > 0) {
260
+ const episodes = plur.timeline({ search: "" });
261
+ base.episodes = episodes.filter((ep) => raw.episode_ids.includes(ep.id)).map((ep) => ({ id: ep.id, summary: ep.summary, timestamp: ep.timestamp }));
262
+ }
263
+ return base;
264
+ }),
265
+ count: boundedResults.length,
266
+ truncated,
199
267
  mode: "hybrid"
200
268
  };
201
269
  }
@@ -735,7 +803,10 @@ function getToolDefinitions() {
735
803
  engram_count: status.engram_count,
736
804
  episode_count: status.episode_count,
737
805
  pack_count: status.pack_count,
738
- storage_root: status.storage_root
806
+ storage_root: status.storage_root,
807
+ locked_count: status.locked_count,
808
+ tension_count: status.tension_count,
809
+ versioned_engram_count: status.versioned_engram_count ?? 0
739
810
  };
740
811
  }
741
812
  },
@@ -937,6 +1008,147 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
937
1008
  return { promoted, errors, success: errors.length === 0 };
938
1009
  }
939
1010
  },
1011
+ {
1012
+ name: "plur_tensions",
1013
+ description: "List engram pairs that have conflicting knowledge \u2014 shows tensions in your memory that may need resolution",
1014
+ annotations: { title: "Tensions", readOnlyHint: true, idempotentHint: true },
1015
+ inputSchema: {
1016
+ type: "object",
1017
+ properties: {
1018
+ scope: { type: "string", description: "Filter by scope" },
1019
+ domain: { type: "string", description: "Filter by domain prefix" }
1020
+ }
1021
+ },
1022
+ handler: async (args, plur) => {
1023
+ const engrams = plur.list({
1024
+ scope: args.scope,
1025
+ domain: args.domain
1026
+ });
1027
+ const tensions = [];
1028
+ const seen = /* @__PURE__ */ new Set();
1029
+ for (const engram of engrams) {
1030
+ if (!engram.relations?.conflicts?.length) continue;
1031
+ for (const conflictId of engram.relations.conflicts) {
1032
+ const pairKey = [engram.id, conflictId].sort().join(":");
1033
+ if (seen.has(pairKey)) continue;
1034
+ seen.add(pairKey);
1035
+ const other = engrams.find((e) => e.id === conflictId);
1036
+ if (!other) continue;
1037
+ tensions.push({
1038
+ engram_a: { id: engram.id, statement: engram.statement, type: engram.type },
1039
+ engram_b: { id: other.id, statement: other.statement, type: other.type },
1040
+ detected_at: engram.activation.last_accessed
1041
+ });
1042
+ }
1043
+ }
1044
+ return { tensions, count: tensions.length };
1045
+ }
1046
+ },
1047
+ {
1048
+ name: "plur_episode_to_engram",
1049
+ description: "Promote an episode to a persistent episodic engram \u2014 useful when a session event deserves long-term memory",
1050
+ annotations: { title: "Episode to Engram", destructiveHint: false, idempotentHint: false },
1051
+ inputSchema: {
1052
+ type: "object",
1053
+ properties: {
1054
+ episode_id: { type: "string", description: "Episode ID to promote (from plur_timeline)" },
1055
+ scope: { type: "string", description: "Scope for the new engram" },
1056
+ domain: { type: "string", description: "Domain tag for the new engram" },
1057
+ tags: { type: "array", items: { type: "string" }, description: "Tags for the new engram" }
1058
+ },
1059
+ required: ["episode_id"]
1060
+ },
1061
+ handler: async (args, plur) => {
1062
+ const engram = plur.episodeToEngram(args.episode_id, {
1063
+ scope: args.scope,
1064
+ domain: args.domain,
1065
+ tags: args.tags
1066
+ });
1067
+ return {
1068
+ id: engram.id,
1069
+ statement: engram.statement,
1070
+ memory_class: engram.knowledge_type?.memory_class,
1071
+ episode_ids: engram.episode_ids,
1072
+ source: engram.source
1073
+ };
1074
+ }
1075
+ },
1076
+ {
1077
+ name: "plur_history",
1078
+ description: "View the event-sourced history of an engram or all recent history \u2014 shows creation, updates, feedback, and evolution events",
1079
+ annotations: { title: "History", readOnlyHint: true, idempotentHint: true },
1080
+ inputSchema: {
1081
+ type: "object",
1082
+ properties: {
1083
+ engram_id: { type: "string", description: "Filter history for a specific engram ID. If omitted, returns recent history across all engrams." },
1084
+ limit: { type: "number", description: "Max events to return (default 50)" }
1085
+ }
1086
+ },
1087
+ handler: async (args, plur) => {
1088
+ const engramId = args.engram_id;
1089
+ const limit = args.limit ?? 50;
1090
+ if (engramId) {
1091
+ const events = plur.getEngramHistory(engramId);
1092
+ return {
1093
+ engram_id: engramId,
1094
+ events: events.slice(-limit),
1095
+ total: events.length
1096
+ };
1097
+ }
1098
+ const { listHistoryMonths, readHistory } = await import("@plur-ai/core");
1099
+ const status = plur.status();
1100
+ const months = listHistoryMonths(status.storage_root);
1101
+ const allEvents = [];
1102
+ for (const month of months.reverse()) {
1103
+ const events = readHistory(status.storage_root, month);
1104
+ allEvents.push(...events);
1105
+ if (allEvents.length >= limit) break;
1106
+ }
1107
+ return {
1108
+ events: allEvents.slice(-limit),
1109
+ total: allEvents.length
1110
+ };
1111
+ }
1112
+ },
1113
+ {
1114
+ name: "plur_report_failure",
1115
+ description: "Report a failure for a procedural engram \u2014 triggers procedure evolution via LLM if configured. Only works on procedural engrams. Max 3 revisions per procedure per 24h.",
1116
+ annotations: { title: "Report Failure", destructiveHint: false, idempotentHint: false },
1117
+ inputSchema: {
1118
+ type: "object",
1119
+ properties: {
1120
+ engram_id: { type: "string", description: "ID of the procedural engram that failed" },
1121
+ failure_context: { type: "string", description: "Description of what went wrong when following this procedure" },
1122
+ llm_base_url: { type: "string", description: "OpenAI-compatible API base URL for procedure evolution" },
1123
+ llm_api_key: { type: "string", description: "API key for the LLM" },
1124
+ llm_model: { type: "string", description: "Model name (default: gpt-4o-mini)" }
1125
+ },
1126
+ required: ["engram_id", "failure_context"]
1127
+ },
1128
+ handler: async (args, plur) => {
1129
+ let llm;
1130
+ if (args.llm_base_url && args.llm_api_key) {
1131
+ llm = makeHttpLlm(
1132
+ args.llm_base_url,
1133
+ args.llm_api_key,
1134
+ args.llm_model
1135
+ );
1136
+ }
1137
+ const result = await plur.reportFailure(
1138
+ args.engram_id,
1139
+ args.failure_context,
1140
+ llm
1141
+ );
1142
+ return {
1143
+ engram_id: result.engram.id,
1144
+ statement: result.engram.statement,
1145
+ evolved: result.evolved,
1146
+ engram_version: result.engram.engram_version ?? 1,
1147
+ failure_episode_id: result.episode.id,
1148
+ note: result.evolved ? "Procedure was improved based on the failure report" : "Failure logged but procedure was not rewritten (no LLM configured or LLM unavailable)"
1149
+ };
1150
+ }
1151
+ },
940
1152
  {
941
1153
  name: "plur_packs_export",
942
1154
  description: "Export engrams as a shareable thematic pack with privacy scanning and integrity hash. Filters out private and secret-containing engrams automatically. Output goes to ~/plur-packs/<name> by default.",
@@ -990,20 +1202,55 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
990
1202
  name
991
1203
  };
992
1204
  }
1205
+ },
1206
+ {
1207
+ name: "plur_profile",
1208
+ description: "Generate or retrieve a cognitive profile \u2014 a narrative summary synthesized from stored engrams. Cached for 24h.",
1209
+ annotations: { title: "Cognitive profile", readOnlyHint: true, idempotentHint: true },
1210
+ inputSchema: {
1211
+ type: "object",
1212
+ properties: {
1213
+ scope: { type: "string", description: "Filter engrams by scope" },
1214
+ llm_base_url: { type: "string", description: "OpenAI-compatible API base URL" },
1215
+ llm_api_key: { type: "string", description: "API key for the LLM" },
1216
+ llm_model: { type: "string", description: "Model name" },
1217
+ force_regenerate: { type: "boolean", description: "Force regeneration (default false)" }
1218
+ }
1219
+ },
1220
+ handler: async (args, plur) => {
1221
+ const status = plur.status();
1222
+ const storagePath = status.storage_root;
1223
+ if (!args.force_regenerate) {
1224
+ const cached = getProfileForInjection(storagePath);
1225
+ if (cached) return { profile: cached, source: "cache" };
1226
+ }
1227
+ if (!args.llm_base_url || !args.llm_api_key) {
1228
+ const cached = getProfileForInjection(storagePath);
1229
+ if (cached) return { profile: cached, source: "stale_cache" };
1230
+ return { profile: null, error: "No cached profile. Provide llm_base_url and llm_api_key." };
1231
+ }
1232
+ const model = args.llm_model ?? selectModelForOperation("profile", status.config?.llm);
1233
+ const llm = makeHttpLlm(args.llm_base_url, args.llm_api_key, model);
1234
+ const engrams = plur.list({ scope: args.scope });
1235
+ const profile = await generateProfile(engrams, llm, storagePath, status.config?.profile?.cache_ttl_hours ?? 24);
1236
+ return { profile, source: "generated", engram_count: engrams.length, model };
1237
+ }
993
1238
  }
994
1239
  ];
995
1240
  }
996
1241
 
997
1242
  // src/server.ts
998
1243
  import { z } from "zod";
999
- var VERSION = "0.8.0";
1244
+ var VERSION = "0.8.3";
1000
1245
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
1001
1246
 
1002
- REQUIRED at session boundaries:
1003
- - FIRST ACTION of every session: call plur_session_start with a task description
1004
- - LAST ACTION before conversation ends: call plur_session_end with summary + engram_suggestions
1247
+ 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.
1248
+
1249
+ SESSION LIFECYCLE:
1250
+ - 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.
1251
+ - Without hooks: call plur_session_start at the start, plur_session_end at the end.
1005
1252
 
1006
- REQUIRED during the session:
1253
+ DURING the session:
1007
1254
  - When user corrects you ("no, use X not Y") \u2192 call plur_learn immediately
1008
1255
  - When user states a preference ("always X", "never Y") \u2192 call plur_learn immediately
1009
1256
  - When you discover a codebase convention or pattern \u2192 call plur_learn
@@ -1012,12 +1259,10 @@ OPTIONAL but improves quality:
1012
1259
  - Call plur_feedback to rate which injected engrams helped (positive/negative)
1013
1260
  - Call plur_recall_hybrid before answering factual questions \u2014 the answer may be in memory
1014
1261
 
1015
- For combined search (engrams + files): call plur_recall_hybrid for memories, then datacore.search for journal/knowledge files.
1016
-
1017
1262
  Do not ask permission to use these tools \u2014 they are your memory system.
1018
1263
 
1019
- Setup: If this is a fresh install, suggest the user run: npx @plur-ai/cli init
1020
- This installs Claude Code hooks for automatic engram injection. Without hooks, injection only happens when you call plur_session_start.`;
1264
+ Setup: If this is a fresh install, suggest the user run: npx @plur-ai/mcp init
1265
+ This installs hooks for automatic injection + session management. One-time global setup.`;
1021
1266
  var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
1022
1267
 
1023
1268
  ## What is PLUR?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.8.0",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "plur-mcp": "dist/index.js"
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@modelcontextprotocol/sdk": "^1.12.0",
14
14
  "zod": "^3.23.0",
15
- "@plur-ai/core": "0.8.0"
15
+ "@plur-ai/core": "0.8.3"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.5.0"