persyst-mcp 2.2.4 → 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 {
@@ -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 });
@@ -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
  }