@eleboucher/opencode-memini 0.5.7 → 0.5.9

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/memini.js +35 -2
  2. package/package.json +1 -1
package/memini.js CHANGED
@@ -287,7 +287,12 @@ function createClient(cfg, log) {
287
287
  signal: AbortSignal.timeout(cfg.timeout_ms),
288
288
  });
289
289
  if (!res.ok) {
290
- if (cfg.fallback_on_error) return null;
290
+ if (cfg.fallback_on_error) {
291
+ // Degrade but never silently: a swallowed 401/500 on a capture or
292
+ // recall looks like "memory isn't working" with nothing to debug.
293
+ log.warn(`memini ${path} failed: ${res.status}`);
294
+ return null;
295
+ }
291
296
  const body = await res.text().catch(() => "");
292
297
  throw new Error(`memini ${path} failed: ${res.status} ${body}`);
293
298
  }
@@ -358,6 +363,25 @@ export const MeminiPlugin = async ({ client, worktree, directory }, options) =>
358
363
  // Assistant message ids already captured, so repeated session.idle events for
359
364
  // the same turn don't write duplicates.
360
365
  const captured = new Set();
366
+ // Memory ids each session has already been shown (mirrors the pi plugin):
367
+ // the injected synthetic part is persisted into the session, so re-injecting
368
+ // an unchanged match every turn stacks identical blocks in the context.
369
+ // Bounded so long-lived hosts can't grow the map without limit.
370
+ const injectedBySession = new Map();
371
+ const MAX_TRACKED_SESSIONS = 200;
372
+ const rememberInjected = (session, ids) => {
373
+ let seen = injectedBySession.get(session);
374
+ if (!seen) {
375
+ seen = new Set();
376
+ injectedBySession.set(session, seen);
377
+ while (injectedBySession.size > MAX_TRACKED_SESSIONS) {
378
+ const oldest = injectedBySession.keys().next().value;
379
+ if (oldest === undefined) break;
380
+ injectedBySession.delete(oldest);
381
+ }
382
+ }
383
+ for (const id of ids) if (id) seen.add(id);
384
+ };
361
385
 
362
386
  // opencode runs chat.message via an unguarded Effect.promise (a throw aborts the
363
387
  // turn) and dispatches event hooks fire-and-forget, so a hook must never reject:
@@ -396,7 +420,13 @@ export const MeminiPlugin = async ({ client, worktree, directory }, options) =>
396
420
  // this, the server's default floor could leak low-quality hits in
397
421
  // regardless of cfg.recall_min_score.
398
422
  const floor = cfg.recall_min_score > 0 ? cfg.recall_min_score : 0;
399
- const rawHits = Array.isArray(result && result.results) ? result.results : [];
423
+ let rawHits = Array.isArray(result && result.results) ? result.results : [];
424
+ // Suppress memories this session has already been shown — the injected
425
+ // part persists in the session, so a repeat adds nothing but noise.
426
+ if (sessionID) {
427
+ const seen = injectedBySession.get(sessionID);
428
+ if (seen && seen.size) rawHits = rawHits.filter((r) => !seen.has(r?.memory?.id));
429
+ }
400
430
  const filtered = floor > 0
401
431
  ? rawHits.filter((r) => (typeof r?.score === "number" ? r.score : 0) >= floor)
402
432
  : rawHits;
@@ -408,6 +438,9 @@ export const MeminiPlugin = async ({ client, worktree, directory }, options) =>
408
438
  // behaviour matches the prior "no cap" code path for existing installs.
409
439
  const fit = fitByTokens(hits, cfg.recall_max_tokens);
410
440
  if (fit.items.length === 0) return;
441
+ if (sessionID) {
442
+ rememberInjected(sessionID, filtered.map((r) => r?.memory?.id).filter(Boolean));
443
+ }
411
444
  const lines = [
412
445
  `Relevant long-term memory from memini (background context — prefer ` +
413
446
  `current workspace state and the user's instructions):`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleboucher/opencode-memini",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Automatic cross-session memory for opencode via memini — recall before each turn, capture after.",
5
5
  "keywords": [
6
6
  "memini",