persyst-mcp 2.2.3 → 2.2.5

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/src/tools.js CHANGED
@@ -48,6 +48,7 @@ import { searchHybrid, getOptimizedContext, consolidateMemories } from './search
48
48
  import { getRecentCommits } from './git.js';
49
49
  import { verifyChainIntegrity } from './attestation.js';
50
50
  import { searchCache } from './cache.js';
51
+ import { memoryEventBus } from './events.js';
51
52
 
52
53
  // ============================================================
53
54
  // CONSTANTS
@@ -152,6 +153,9 @@ export async function addMemoryInternal({ content, importance = 1.0, agent_id, s
152
153
  // Feature 1: Invalidate search cache on write
153
154
  searchCache.invalidate();
154
155
 
156
+ // Broadcast to SSE subscribers (HTTP gateway + SSE clients)
157
+ memoryEventBus.emit('memory_added', { id, content, namespace, source: normalizedAgentId || 'manual' });
158
+
155
159
  // Feature 2: Contradiction Detection
156
160
  let contradictions = [];
157
161
  try {
@@ -290,7 +294,7 @@ export function registerTools(server) {
290
294
  'Search memories using hybrid keyword + semantic search with cryptographic attestation. CRITICAL: Call this tool at the start of a session or task to retrieve relevant user preferences, coding guidelines, and past decisions.',
291
295
  {
292
296
  query: z.string().describe('What to search for'),
293
- limit: z.number().default(5).describe('Max results (default: 5)'),
297
+ limit: z.number().int().min(1).default(5).describe('Max results (default: 5)'),
294
298
  agent_id: z.string().optional().describe('Agent ID — filters results to this agent\'s namespace + shared'),
295
299
  session_id: z.string().optional().describe('Session ID')
296
300
  },
@@ -403,6 +407,9 @@ export function registerTools(server) {
403
407
  // Feature 1: Invalidate search cache on write
404
408
  searchCache.invalidate();
405
409
 
410
+ // Broadcast deletion to SSE subscribers
411
+ memoryEventBus.emit('memory_deleted', { id });
412
+
406
413
  return text({ success: true, id, message: `Memory #${id} deleted` });
407
414
  } catch (err) {
408
415
  return text({ error: err.message });
@@ -415,7 +422,7 @@ export function registerTools(server) {
415
422
  'get_recent_memories',
416
423
  'Get the most recently created memories, newest first. Filtered by agent namespace if agent_id is provided.',
417
424
  {
418
- limit: z.number().default(10).describe('How many to return (default: 10)'),
425
+ limit: z.number().int().min(1).default(10).describe('How many to return (default: 10)'),
419
426
  agent_id: z.string().optional().describe('Agent ID — filters to this agent\'s namespace + shared')
420
427
  },
421
428
  async ({ limit, agent_id }) => {
@@ -434,7 +441,7 @@ export function registerTools(server) {
434
441
  'get_important_memories',
435
442
  'Get memories ranked by importance score, highest first. Filtered by agent namespace if agent_id is provided.',
436
443
  {
437
- limit: z.number().default(10).describe('How many to return (default: 10)'),
444
+ limit: z.number().int().min(1).default(10).describe('How many to return (default: 10)'),
438
445
  agent_id: z.string().optional().describe('Agent ID — filters to this agent\'s namespace + shared')
439
446
  },
440
447
  async ({ limit, agent_id }) => {
@@ -778,12 +785,13 @@ export function registerTools(server) {
778
785
  query: z.string().describe('The search query context'),
779
786
  max_tokens: z.number().default(4000).describe('Token budget for LLM context compression (default: 4000)'),
780
787
  agent_id: z.string().optional().describe('Agent ID requesting context — filters to this agent\'s namespace + shared'),
781
- session_id: z.string().optional().describe('Session ID')
788
+ session_id: z.string().optional().describe('Session ID'),
789
+ intent: z.string().optional().describe('The active task intent / category (e.g. debugging, ui_styling, database_management)')
782
790
  },
783
- async ({ query, max_tokens, agent_id, session_id }) => {
791
+ async ({ query, max_tokens, agent_id, session_id, intent }) => {
784
792
  try {
785
793
  const namespace = agent_id || null;
786
- const contextData = await getOptimizedContext(query, max_tokens, agent_id, session_id, namespace);
794
+ const contextData = await getOptimizedContext(query, max_tokens, agent_id, session_id, namespace, intent);
787
795
  return text(contextData);
788
796
  } catch (err) {
789
797
  return text({ error: err.message });
package/src/watcher.js CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { join, resolve } from 'path';
10
10
  import { homedir } from 'os';
11
- import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
11
+ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, openSync, readSync, closeSync } from 'fs';
12
12
  import {
13
13
  getWatchPosition,
14
14
  upsertWatchPosition,
@@ -20,6 +20,7 @@ import { generateEmbedding } from './embeddings.js';
20
20
  import { extractHeuristic } from './extractor-heuristic.js';
21
21
  import { searchHybrid } from './search.js';
22
22
  import { searchCache } from './cache.js';
23
+ import { memoryEventBus } from './events.js';
23
24
 
24
25
  // Config path: ~/.persyst/config.json
25
26
  const CONFIG_FILE = join(homedir(), '.persyst', 'config.json');
@@ -86,10 +87,19 @@ async function processJsonlFile(filePath) {
86
87
 
87
88
  if (stat.size <= lastPos) return;
88
89
 
89
- // Read only new content appended to the file
90
- const fileBuffer = readFileSync(filePath);
91
- const newContentBuffer = fileBuffer.subarray(lastPos, stat.size);
92
- const text = newContentBuffer.toString('utf8');
90
+ // Read only new content appended to the file (Bug C fix)
91
+ const length = stat.size - lastPos;
92
+ let text = '';
93
+ if (length > 0) {
94
+ const newContentBuffer = Buffer.alloc(length);
95
+ const fd = openSync(filePath, 'r');
96
+ try {
97
+ readSync(fd, newContentBuffer, 0, length, lastPos);
98
+ } finally {
99
+ closeSync(fd);
100
+ }
101
+ text = newContentBuffer.toString('utf8');
102
+ }
93
103
 
94
104
  const lines = text.split('\n');
95
105
  let addedCount = 0;
@@ -116,16 +126,16 @@ async function processJsonlFile(filePath) {
116
126
 
117
127
  const facts = extractHeuristic(cleanText);
118
128
  for (const fact of facts) {
119
- // Verify against exact duplicate
120
- if (memoryExists(fact.content)) continue;
129
+ // Verify against exact duplicate (Bug A fix: check namespace 'shared')
130
+ if (memoryExists(fact.content, 'shared')) continue;
121
131
 
122
- // Verify against semantic similarity
123
- const similar = await searchHybrid(fact.content, 1);
132
+ // Verify against semantic similarity (Bug B fix: check namespace 'shared')
133
+ const similar = await searchHybrid(fact.content, 1, null, null, 'shared');
124
134
  if (similar.length > 0 && parseFloat(similar[0].similarity) >= DEDUP_THRESHOLD) {
125
135
  continue;
126
136
  }
127
137
 
128
- // Insert memory with provenance
138
+ // Insert memory with provenance (written to 'shared' by default)
129
139
  const id = insertMemory(fact.content, fact.confidence, {
130
140
  source_type: 'agent',
131
141
  source_id: record.source === 'MODEL' ? 'antigravity-worker' : 'user-dialogue',
@@ -136,6 +146,7 @@ async function processJsonlFile(filePath) {
136
146
  insertVector(id, embedding);
137
147
  addedCount++;
138
148
  console.error(`[persyst-watcher] Auto-extracted fact: "${fact.content}" (Memory #${id})`);
149
+ memoryEventBus.emit('memory_added', { id, content: fact.content, namespace: 'shared', source: 'watcher-antigravity' });
139
150
  }
140
151
  }
141
152
  }
@@ -181,13 +192,16 @@ async function processJsonFile(filePath) {
181
192
  if (msg.role === 'user' || msg.role === 'assistant') {
182
193
  const facts = extractHeuristic(msg.content);
183
194
  for (const fact of facts) {
184
- if (memoryExists(fact.content)) continue;
195
+ // Verify against exact duplicate (Bug A fix: check namespace 'shared')
196
+ if (memoryExists(fact.content, 'shared')) continue;
185
197
 
186
- const similar = await searchHybrid(fact.content, 1);
198
+ // Verify against semantic similarity (Bug B fix: check namespace 'shared')
199
+ const similar = await searchHybrid(fact.content, 1, null, null, 'shared');
187
200
  if (similar.length > 0 && parseFloat(similar[0].similarity) >= DEDUP_THRESHOLD) {
188
201
  continue;
189
202
  }
190
203
 
204
+ // Insert memory with provenance (written to 'shared' by default)
191
205
  const id = insertMemory(fact.content, fact.confidence, {
192
206
  source_type: 'agent',
193
207
  source_id: msg.role === 'assistant' ? 'roo-worker' : 'user-dialogue',
@@ -198,6 +212,7 @@ async function processJsonFile(filePath) {
198
212
  insertVector(id, embedding);
199
213
  addedCount++;
200
214
  console.error(`[persyst-watcher] Auto-extracted fact: "${fact.content}" (Memory #${id})`);
215
+ memoryEventBus.emit('memory_added', { id, content: fact.content, namespace: 'shared', source: 'watcher-roo' });
201
216
  }
202
217
  }
203
218
  }