prism-mcp-server 20.21.19 → 20.21.20

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/README.md CHANGED
@@ -158,6 +158,18 @@ or by re-enabling after each run.
158
158
  <details>
159
159
  <summary>Release history (optional)</summary>
160
160
 
161
+ ## What's New in v20.21.20
162
+
163
+ - Fixed: a `session_save_ledger` call refused with `context_not_loaded`, or the
164
+ greeting-only reply that writes nothing, restarted the hourly drift reminder.
165
+ A ledger save restarts it now only when it writes an entry.
166
+ - A written ledger entry's reply also asks for the handoff when the checkpoint
167
+ ends a unit of work.
168
+ - A ledger save skipped as a duplicate of one saved in the last 5 minutes now
169
+ says nothing new was written, instead of "saved".
170
+ - Fixed: with Supabase storage, a handoff whose save failed was reported as
171
+ updated. It is now an error the agent can see and retry.
172
+
161
173
  ## What's New in v20.21.19
162
174
 
163
175
  - Prism Browser emulates phones and tablets. `--device iphone-17`, or one of
package/dist/server.js CHANGED
@@ -764,14 +764,8 @@ export function createServer() {
764
764
  if (!SESSION_MEMORY_ENABLED)
765
765
  throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
766
766
  result = await sessionSaveLedgerHandler(args);
767
- // GATE 5: Reset drift timer — save_ledger counts as a drift checkpoint
768
- {
769
- const cid = args?.conversation_id;
770
- if (cid) {
771
- const { noteDriftCheck } = await import("./session/sessionContext.js");
772
- noteDriftCheck(cid);
773
- }
774
- }
767
+ // GATE 5: the handler resets the drift timer itself, and only when
768
+ // it wrote an entry (not on a refusal or the greeting-only skip).
775
769
  // Savings sync (paid, opt-in, fail-soft): session end is the natural
776
770
  // push point. pushSavings gates itself — disabled/free/no-jwt all
777
771
  // return without a request, and nothing here awaits it.
@@ -154,11 +154,11 @@ export class SupabaseStorage {
154
154
  };
155
155
  }
156
156
  catch (e) {
157
- debugLog("[SupabaseStorage] saveHandoff RPC failed: " + (e instanceof Error ? e.message : String(e)));
158
- return {
159
- status: "updated",
160
- version: handoff.version ?? 1,
161
- };
157
+ // The write failed. Reporting "updated" here told the agent its handoff
158
+ // was saved when nothing was; the caller turns this into an error reply.
159
+ const message = e instanceof Error ? e.message : String(e);
160
+ debugLog("[SupabaseStorage] saveHandoff RPC failed: " + message);
161
+ throw new Error(`Handoff for project "${handoff.project}" was not saved: ${message}`);
162
162
  }
163
163
  }
164
164
  async patchHandoff(project, userId, data) {
@@ -810,6 +810,22 @@ export async function sessionSaveLedgerHandler(args) {
810
810
  keywords,
811
811
  role: effectiveRole, // v3.0: Hivemind role scoping (dashboard fallback)
812
812
  });
813
+ // SQLite skips an identical (project, conversation_id, summary) saved in the
814
+ // last 5 minutes and returns { id, deduplicated: true } without writing.
815
+ // Nothing new was saved, so this is not a drift checkpoint and must not read
816
+ // as one: no timer reset, no "saved" line, no handoff nudge, and no second
817
+ // embedding or auto-link pass over the existing row.
818
+ if (result?.deduplicated === true) {
819
+ return {
820
+ content: [{
821
+ type: "text",
822
+ text: (_saveLedgerGateWarning ? `⚠️ ${_saveLedgerGateWarning}\n\n` : "") +
823
+ `ℹ️ An identical ledger entry for project "${project}" was already saved in the last 5 minutes; nothing new was written.` +
824
+ resolverNote,
825
+ }],
826
+ isError: false,
827
+ };
828
+ }
813
829
  // ─── Fire-and-forget embedding generation ───
814
830
  let embeddingQueued = false;
815
831
  if (result) {
@@ -908,6 +924,14 @@ export async function sessionSaveLedgerHandler(args) {
908
924
  debugLog(`[session_save_ledger] Background decay failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
909
925
  });
910
926
  const metricsBlock = formatInferenceMetrics();
927
+ // GATE 5: a ledger entry that was WRITTEN is a drift checkpoint. Reset the
928
+ // timer here, on the write path only. It used to be reset by the server
929
+ // after every returned result, so a context_not_loaded refusal or the
930
+ // greeting-only skip silenced the hourly reminder without saving anything.
931
+ {
932
+ const { noteDriftCheck } = await import("../session/sessionContext.js");
933
+ noteDriftCheck(conversation_id);
934
+ }
911
935
  return {
912
936
  content: [{
913
937
  type: "text",
@@ -920,6 +944,9 @@ export async function sessionSaveLedgerHandler(args) {
920
944
  (embeddingQueued
921
945
  ? `📊 Embedding generation queued for semantic search.`
922
946
  : `📊 Primary history saved; optional semantic indexing was not queued.`) +
947
+ // The ledger logs what happened; the next session starts from the
948
+ // handoff. Only a written entry asks, never the skip or a refusal.
949
+ `\n💾 If this checkpoint ends a unit of work, also save the handoff with session_save_handoff.` +
923
950
  metricsBlock +
924
951
  resolverNote,
925
952
  }],
@@ -99,11 +99,13 @@ size alone. Emulate the device instead:
99
99
  `viewport-fit=cover`; without it an iPhone 17 reports 0. Chromium applies
100
100
  them either way, so check that your page sets it.
101
101
 
102
- Flags given explicitly override the preset, so a phone turned sideways is
103
- `--device iphone-17 --viewport 874x402 --safe-area 0,62,21,62`. Landscape
104
- insets are not part of the presets. The engine stays Chromium, and the user
105
- agent stays the desktop one, so pages that branch on the user-agent string
106
- still take their desktop path.
102
+ Flags given explicitly override the preset, so
103
+ `--device iphone-17 --viewport 874x402 --safe-area T,R,B,L` is the same phone
104
+ turned sideways. Landscape insets were not measured and are not part of the
105
+ presets: put the values your device reports in place of `T,R,B,L`, because
106
+ without `--safe-area` the portrait insets still apply. The engine stays
107
+ Chromium, and the user agent stays the desktop one, so pages that branch on the
108
+ user-agent string still take their desktop path.
107
109
 
108
110
  ```bash
109
111
  printf '%s\n' \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.21.19",
3
+ "version": "20.21.20",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
5
  "description": "Persistent session memory for AI coding agents that never leaves your machine — including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
6
6
  "module": "index.ts",
@@ -83,7 +83,7 @@
83
83
  "hono": "^4.13.5",
84
84
  "ip-address": "^10.2.0",
85
85
  "protobufjs": "^7.6.5",
86
- "sharp": "^0.35.4",
86
+ "sharp": "^0.35.5",
87
87
  "tar": "^7.5.19",
88
88
  "nanoid": "^3.3.17"
89
89
  },