mevoric 2.2.0 → 2.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +69 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mevoric",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Unified memory + agent bridge for Claude Code. Semantic recall, cross-tab messaging, session checkpoints — one MCP server.",
5
5
  "type": "module",
6
6
  "main": "server.mjs",
package/server.mjs CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  } from '@modelcontextprotocol/sdk/types.js';
25
25
  import {
26
26
  existsSync, mkdirSync, writeFileSync, readFileSync,
27
- readdirSync, unlinkSync, renameSync
27
+ readdirSync, unlinkSync, renameSync, appendFileSync
28
28
  } from 'fs';
29
29
  import { resolve, dirname } from 'path';
30
30
  import { randomBytes, randomUUID } from 'crypto';
@@ -1237,8 +1237,34 @@ async function runCapturePrompt() {
1237
1237
  const clean = stripSystemTags(prompt);
1238
1238
  if (clean.length < 5) process.exit(0);
1239
1239
 
1240
+ // Append to JSONL file (one line per prompt, accumulates across the session)
1240
1241
  const tmp = tmpdir();
1241
- writeFileSync(resolve(tmp, `mevoric-prompt-${sessionId}`), clean, 'utf8');
1242
+ const entry = JSON.stringify({ ts: Date.now(), prompt: clean });
1243
+ appendFileSync(resolve(tmp, `mevoric-prompt-${sessionId}`), entry + '\n', 'utf8');
1244
+
1245
+ // Fire-and-forget POST to /ingest so this prompt is saved even if session crashes
1246
+ try {
1247
+ let convId = '';
1248
+ try { convId = readFileSync(resolve(tmp, 'mevoric-convid'), 'utf8').trim(); } catch {}
1249
+ if (!convId) convId = sessionId;
1250
+
1251
+ const project = process.cwd().split(/[\\/]/).pop();
1252
+ const controller = new AbortController();
1253
+ const timer = setTimeout(() => controller.abort(), 3000);
1254
+ await fetch(`${MEMORY_SERVER_URL}/ingest`, {
1255
+ method: 'POST',
1256
+ headers: { 'Content-Type': 'application/json' },
1257
+ body: JSON.stringify({
1258
+ messages: [{ role: 'user', content: clean.slice(0, 10000) }],
1259
+ user_id: 'lloyd',
1260
+ conversation_id: convId,
1261
+ project
1262
+ }),
1263
+ signal: controller.signal
1264
+ });
1265
+ clearTimeout(timer);
1266
+ } catch {} // Best-effort — prompt is still in JSONL file for Stop hook fallback
1267
+
1242
1268
  process.exit(0);
1243
1269
  }
1244
1270
 
@@ -1261,13 +1287,26 @@ async function runIngest() {
1261
1287
  const assistantMsg = data.last_assistant_message || '';
1262
1288
  if (!sessionId || !assistantMsg) process.exit(0);
1263
1289
 
1264
- // Read user prompt saved by --capture-prompt
1290
+ // Read ALL user prompts saved by --capture-prompt (JSONL format, one per line)
1265
1291
  const tmp = tmpdir();
1266
1292
  const promptPath = resolve(tmp, `mevoric-prompt-${sessionId}`);
1267
- let userMsg = '';
1293
+ let allPrompts = [];
1268
1294
  try {
1269
- userMsg = readFileSync(promptPath, 'utf8');
1295
+ const raw = readFileSync(promptPath, 'utf8');
1296
+ allPrompts = raw.split('\n').filter(Boolean).map(line => {
1297
+ try { return JSON.parse(line); } catch { return null; }
1298
+ }).filter(Boolean);
1270
1299
  } catch {}
1300
+ // Fallback for old plain-text format (pre-JSONL)
1301
+ if (allPrompts.length === 0) {
1302
+ try {
1303
+ const plain = readFileSync(promptPath, 'utf8');
1304
+ if (plain && plain.length >= 5) allPrompts = [{ ts: Date.now(), prompt: plain }];
1305
+ } catch {}
1306
+ }
1307
+ const userMsg = allPrompts.length > 0 ? allPrompts[allPrompts.length - 1].prompt : '';
1308
+ // Clean up temp file
1309
+ try { unlinkSync(promptPath); } catch {}
1271
1310
 
1272
1311
  const cleanAssistant = stripSystemTags(assistantMsg);
1273
1312
  if (!cleanAssistant || cleanAssistant.length < 50) process.exit(0);
@@ -1286,6 +1325,17 @@ async function runIngest() {
1286
1325
  else if (prev.content) existing = { exchanges: [{ role: 'context', content: prev.content }] };
1287
1326
  } catch {}
1288
1327
 
1328
+ // Store all user prompts from this session, not just the last one
1329
+ if (allPrompts.length > 1) {
1330
+ for (let i = 0; i < allPrompts.length - 1; i++) {
1331
+ existing.exchanges.push({
1332
+ timestamp: new Date(allPrompts[i].ts).toISOString(),
1333
+ user: allPrompts[i].prompt.slice(0, 2000),
1334
+ assistant: ''
1335
+ });
1336
+ }
1337
+ }
1338
+ // Final exchange has the actual assistant response
1289
1339
  existing.exchanges.push({
1290
1340
  timestamp: new Date().toISOString(),
1291
1341
  user: userMsg.slice(0, 2000),
@@ -1336,8 +1386,8 @@ async function runIngest() {
1336
1386
  renameSync(cpTmp, cpPath);
1337
1387
  } catch {}
1338
1388
 
1339
- // --- 3. POST to memory server /ingest (ported from Python auto-ingest.py) ---
1340
- if (userMsg && cleanAssistant) {
1389
+ // --- 3. POST to memory server /ingest — full conversation (all prompts + final response) ---
1390
+ if ((allPrompts.length > 0 || userMsg) && cleanAssistant) {
1341
1391
  // Read conversation ID from temp file (written by MCP server process)
1342
1392
  let convId = '';
1343
1393
  try {
@@ -1346,6 +1396,17 @@ async function runIngest() {
1346
1396
  if (!convId) convId = sessionId; // fallback
1347
1397
 
1348
1398
  try {
1399
+ // Build messages array: all user prompts + final assistant response
1400
+ const messages = [];
1401
+ if (allPrompts.length > 0) {
1402
+ for (const entry of allPrompts) {
1403
+ messages.push({ role: 'user', content: entry.prompt.slice(0, 10000) });
1404
+ }
1405
+ } else if (userMsg) {
1406
+ messages.push({ role: 'user', content: userMsg.slice(0, 10000) });
1407
+ }
1408
+ messages.push({ role: 'assistant', content: cleanAssistant.slice(0, 10000) });
1409
+
1349
1410
  const project = process.cwd().split(/[\\/]/).pop();
1350
1411
  const controller = new AbortController();
1351
1412
  const timer = setTimeout(() => controller.abort(), 15000);
@@ -1353,10 +1414,7 @@ async function runIngest() {
1353
1414
  method: 'POST',
1354
1415
  headers: { 'Content-Type': 'application/json' },
1355
1416
  body: JSON.stringify({
1356
- messages: [
1357
- { role: 'user', content: userMsg.slice(0, 10000) },
1358
- { role: 'assistant', content: cleanAssistant.slice(0, 10000) }
1359
- ],
1417
+ messages,
1360
1418
  user_id: 'lloyd',
1361
1419
  conversation_id: convId,
1362
1420
  project