@rynfar/meridian 1.28.1 → 1.29.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.
@@ -13851,26 +13851,59 @@ function computeMessageHashes(messages) {
13851
13851
  return [];
13852
13852
  return messages.map(hashMessage);
13853
13853
  }
13854
- function measurePrefixOverlap(storedHashes, incomingSet) {
13854
+ function measurePrefixOverlap(storedHashes, incomingHashes) {
13855
13855
  let overlap = 0;
13856
- for (const h of storedHashes) {
13857
- if (incomingSet.has(h))
13856
+ const minLen = Math.min(storedHashes.length, incomingHashes.length);
13857
+ for (let i = 0;i < minLen; i++) {
13858
+ if (storedHashes[i] === incomingHashes[i])
13858
13859
  overlap++;
13859
13860
  else
13860
13861
  break;
13861
13862
  }
13862
13863
  return overlap;
13863
13864
  }
13864
- function measureSuffixOverlap(storedHashes, incomingSet) {
13865
+ function measureSuffixOverlap(storedHashes, incomingHashes) {
13866
+ if (storedHashes.length === 0 || incomingHashes.length === 0)
13867
+ return 0;
13868
+ const lastStoredHash = storedHashes[storedHashes.length - 1];
13869
+ let anchorInIncoming = -1;
13870
+ for (let i = incomingHashes.length - 1;i >= 0; i--) {
13871
+ if (incomingHashes[i] === lastStoredHash) {
13872
+ anchorInIncoming = i;
13873
+ break;
13874
+ }
13875
+ }
13876
+ if (anchorInIncoming < 0)
13877
+ return 0;
13865
13878
  let overlap = 0;
13866
- for (let i = storedHashes.length - 1;i >= 0; i--) {
13867
- if (incomingSet.has(storedHashes[i]))
13879
+ let si = storedHashes.length - 1;
13880
+ let ii = anchorInIncoming;
13881
+ while (si >= 0 && ii >= 0) {
13882
+ if (storedHashes[si] === incomingHashes[ii]) {
13868
13883
  overlap++;
13869
- else
13884
+ si--;
13885
+ ii--;
13886
+ } else {
13870
13887
  break;
13888
+ }
13871
13889
  }
13872
13890
  return overlap;
13873
13891
  }
13892
+ function findSuffixAnchorStart(storedHashes, incomingHashes, suffixOverlap) {
13893
+ if (suffixOverlap <= 0)
13894
+ return -1;
13895
+ const lastStoredHash = storedHashes[storedHashes.length - 1];
13896
+ let anchor = -1;
13897
+ for (let i = incomingHashes.length - 1;i >= 0; i--) {
13898
+ if (incomingHashes[i] === lastStoredHash) {
13899
+ anchor = i;
13900
+ break;
13901
+ }
13902
+ }
13903
+ if (anchor < 0)
13904
+ return -1;
13905
+ return anchor - suffixOverlap + 1;
13906
+ }
13874
13907
  function verifyLineage(cached, messages, cacheKey2, cache) {
13875
13908
  if (!cached.lineageHash || cached.messageCount === 0) {
13876
13909
  return { type: "continuation", session: cached };
@@ -13889,11 +13922,11 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
13889
13922
  return { type: "diverged" };
13890
13923
  }
13891
13924
  const incomingHashes = computeMessageHashes(messages);
13892
- const incomingSet = new Set(incomingHashes);
13893
- const prefixOverlap = measurePrefixOverlap(cached.messageHashes, incomingSet);
13894
- const suffixOverlap = measureSuffixOverlap(cached.messageHashes, incomingSet);
13925
+ const prefixOverlap = measurePrefixOverlap(cached.messageHashes, incomingHashes);
13926
+ const suffixOverlap = measureSuffixOverlap(cached.messageHashes, incomingHashes);
13895
13927
  const MIN_STORED_FOR_COMPACTION = 6;
13896
- if (suffixOverlap >= MIN_SUFFIX_FOR_COMPACTION && cached.messageHashes.length >= MIN_STORED_FOR_COMPACTION) {
13928
+ const suffixStartInIncoming = incomingHashes.length - suffixOverlap >= 0 ? findSuffixAnchorStart(cached.messageHashes, incomingHashes, suffixOverlap) : -1;
13929
+ if (suffixOverlap >= MIN_SUFFIX_FOR_COMPACTION && cached.messageHashes.length >= MIN_STORED_FOR_COMPACTION && suffixStartInIncoming > 0) {
13897
13930
  const compactionMsg = `Compaction detected (key=${cacheKey2.slice(0, 8)}…): suffix overlap ${suffixOverlap}/${cached.messageHashes.length}. Allowing resume.`;
13898
13931
  console.error(`[PROXY] ${compactionMsg}`);
13899
13932
  diagnosticLog.lineage(compactionMsg);
@@ -14130,6 +14163,7 @@ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, mes
14130
14163
  try {
14131
14164
  const store = readStore();
14132
14165
  const existing = store[key];
14166
+ const previousClaudeSessionId = existing && existing.claudeSessionId !== claudeSessionId ? existing.claudeSessionId : existing?.previousClaudeSessionId;
14133
14167
  store[key] = {
14134
14168
  claudeSessionId,
14135
14169
  createdAt: existing?.createdAt || Date.now(),
@@ -14138,7 +14172,8 @@ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, mes
14138
14172
  lineageHash: lineageHash ?? existing?.lineageHash,
14139
14173
  messageHashes: messageHashes ?? existing?.messageHashes,
14140
14174
  sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids,
14141
- contextUsage: contextUsage ?? existing?.contextUsage
14175
+ contextUsage: contextUsage ?? existing?.contextUsage,
14176
+ ...previousClaudeSessionId ? { previousClaudeSessionId } : {}
14142
14177
  };
14143
14178
  const maxEntries = getMaxStoredSessions();
14144
14179
  const keys = Object.keys(store);
@@ -14175,6 +14210,30 @@ function evictSharedSession(key) {
14175
14210
  }
14176
14211
  }
14177
14212
  }
14213
+ function lookupSessionRecovery(key) {
14214
+ const store = readStore();
14215
+ const session = store[key];
14216
+ if (!session)
14217
+ return;
14218
+ return {
14219
+ claudeSessionId: session.claudeSessionId,
14220
+ previousClaudeSessionId: session.previousClaudeSessionId,
14221
+ createdAt: session.createdAt,
14222
+ lastUsedAt: session.lastUsedAt,
14223
+ messageCount: session.messageCount
14224
+ };
14225
+ }
14226
+ function listStoredSessions() {
14227
+ const store = readStore();
14228
+ return Object.entries(store).map(([key, session]) => ({
14229
+ key,
14230
+ claudeSessionId: session.claudeSessionId,
14231
+ previousClaudeSessionId: session.previousClaudeSessionId,
14232
+ createdAt: session.createdAt,
14233
+ lastUsedAt: session.lastUsedAt,
14234
+ messageCount: session.messageCount
14235
+ }));
14236
+ }
14178
14237
  function clearSharedSessions() {
14179
14238
  const path3 = getStorePath();
14180
14239
  try {
@@ -14588,6 +14647,15 @@ function createProxyServer(config = {}) {
14588
14647
  const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name} model=${model} stream=${stream2} tools=${body.tools?.length ?? 0} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
14589
14648
  console.error(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
14590
14649
  diagnosticLog.session(`${requestLogLine}`, requestMeta.requestId);
14650
+ if (lineageResult.type === "diverged" && profileSessionId) {
14651
+ const recovery = lookupSessionRecovery(profileSessionId);
14652
+ if (recovery) {
14653
+ const prevId = recovery.previousClaudeSessionId || recovery.claudeSessionId;
14654
+ const recoveryMsg = `${requestMeta.requestId} SESSION RECOVERY: previous conversation available. Run: claude --resume ${prevId}`;
14655
+ console.error(`[PROXY] ${recoveryMsg}`);
14656
+ diagnosticLog.session(recoveryMsg, requestMeta.requestId);
14657
+ }
14658
+ }
14591
14659
  claudeLog("request.received", {
14592
14660
  model,
14593
14661
  stream: stream2,
@@ -15797,6 +15865,46 @@ data: ${JSON.stringify({
15797
15865
  }
15798
15866
  return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
15799
15867
  });
15868
+ app.get("/v1/sessions/recover", (c) => {
15869
+ const sessions = listStoredSessions();
15870
+ if (sessions.length === 0) {
15871
+ return c.json({ error: "No sessions found in store" }, 404);
15872
+ }
15873
+ return c.json({
15874
+ sessions: sessions.map((s) => ({
15875
+ key: s.key,
15876
+ claudeSessionId: s.claudeSessionId,
15877
+ previousClaudeSessionId: s.previousClaudeSessionId,
15878
+ createdAt: new Date(s.createdAt).toISOString(),
15879
+ lastUsedAt: new Date(s.lastUsedAt).toISOString(),
15880
+ messageCount: s.messageCount,
15881
+ recoverCommand: `claude --resume ${s.claudeSessionId}`,
15882
+ ...s.previousClaudeSessionId ? {
15883
+ recoverPreviousCommand: `claude --resume ${s.previousClaudeSessionId}`
15884
+ } : {}
15885
+ }))
15886
+ });
15887
+ });
15888
+ app.get("/v1/sessions/:key/recover", (c) => {
15889
+ const key = c.req.param("key");
15890
+ const recovery = lookupSessionRecovery(key);
15891
+ if (!recovery) {
15892
+ return c.json({ error: "Session not found", key }, 404);
15893
+ }
15894
+ return c.json({
15895
+ key,
15896
+ claudeSessionId: recovery.claudeSessionId,
15897
+ previousClaudeSessionId: recovery.previousClaudeSessionId,
15898
+ createdAt: new Date(recovery.createdAt).toISOString(),
15899
+ lastUsedAt: new Date(recovery.lastUsedAt).toISOString(),
15900
+ messageCount: recovery.messageCount,
15901
+ recoverCommand: `claude --resume ${recovery.claudeSessionId}`,
15902
+ ...recovery.previousClaudeSessionId ? {
15903
+ recoverPreviousCommand: `claude --resume ${recovery.previousClaudeSessionId}`,
15904
+ note: "Previous session was replaced — if your current session has lost context, try the previous session ID."
15905
+ } : {}
15906
+ });
15907
+ });
15800
15908
  app.all("*", (c) => {
15801
15909
  console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
15802
15910
  return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-zcxn6xmn.js";
4
+ } from "./cli-msyx6dnk.js";
5
5
  import"./cli-g9ypdz51.js";
6
6
  import"./cli-rtab0qa6.js";
7
7
  import"./cli-m9pfb7h9.js";
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAqBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAEzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAimDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAqBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA0pDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
@@ -77,20 +77,37 @@ export declare function computeMessageHashes(messages: Array<{
77
77
  }>): string[];
78
78
  /**
79
79
  * Measure how many stored hashes match from the START of the stored array
80
- * against the incoming hashes (order-preserving).
80
+ * against the incoming hashes (positional comparison).
81
81
  *
82
82
  * Prefix overlap means the beginning of the conversation is intact (undo
83
83
  * changes the end but preserves the beginning).
84
+ *
85
+ * NOTE: Compares stored[i] === incoming[i] positionally. An earlier
86
+ * implementation used a Set for O(1) lookups, but that allowed a stored
87
+ * hash at position i to match an incoming hash at a completely different
88
+ * position, inflating the overlap count when duplicate messages exist
89
+ * in the conversation history.
84
90
  */
85
- export declare function measurePrefixOverlap(storedHashes: string[], incomingSet: Set<string>): number;
91
+ export declare function measurePrefixOverlap(storedHashes: string[], incomingHashes: string[]): number;
86
92
  /**
87
- * Measure how many stored hashes match from the END of the stored array
88
- * against the incoming hashes (order-preserving).
93
+ * Measure how many consecutive messages at the END of the stored array
94
+ * appear as a contiguous run in the incoming array.
89
95
  *
90
96
  * Suffix overlap means the recent conversation is intact (compaction
91
97
  * changes the beginning but preserves the end).
98
+ *
99
+ * Algorithm: find the last stored hash in the incoming array, then walk
100
+ * backward through both arrays verifying contiguous matches. This handles
101
+ * the real-world compaction pattern where new messages are appended AFTER
102
+ * the preserved suffix.
103
+ *
104
+ * NOTE: An earlier implementation used a Set for O(1) lookups, but that
105
+ * allowed a stored suffix hash to match an incoming hash at a completely
106
+ * different position — producing false compaction when duplicate messages
107
+ * exist in the conversation. The current approach verifies positional
108
+ * contiguity.
92
109
  */
93
- export declare function measureSuffixOverlap(storedHashes: string[], incomingSet: Set<string>): number;
110
+ export declare function measureSuffixOverlap(storedHashes: string[], incomingHashes: string[]): number;
94
111
  /** Cache-like interface for verifyLineage — only needs get/set/delete */
95
112
  export interface SessionCacheLike {
96
113
  delete(key: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC;AAED;0EAC0E;AAC1E,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;kCAE8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;oDAEgD;IAChD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,iGAAiG;IACjG,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAG,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAS,OAAO,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAIxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAG9F;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAID,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,gBAAgB,GACtB,aAAa,CAqFf"}
1
+ {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC;AAED;0EAC0E;AAC1E,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;kCAE8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;oDAEgD;IAChD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,iGAAiG;IACjG,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAG,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAS,OAAO,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAIxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAG9F;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CA6B7F;AAyBD,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,gBAAgB,GACtB,aAAa,CA6Ff"}
@@ -23,6 +23,10 @@ export interface StoredSession {
23
23
  sdkMessageUuids?: Array<string | null>;
24
24
  /** Last observed token usage for this Claude session */
25
25
  contextUsage?: TokenUsage;
26
+ /** Previous Claude session ID preserved when the session mapping is replaced.
27
+ * Enables recovery when a lineage bug (e.g. false compaction) causes the
28
+ * original session to be abandoned and a new one started. */
29
+ previousClaudeSessionId?: string;
26
30
  }
27
31
  /** Set an explicit session store directory. Takes priority over env var.
28
32
  * Pass null to clear. For testing only.
@@ -36,5 +40,25 @@ export declare function storeSharedSession(key: string, claudeSessionId: string,
36
40
  /** Remove a single session from the shared file store.
37
41
  * Used when a session is detected as stale (e.g. expired upstream). */
38
42
  export declare function evictSharedSession(key: string): void;
43
+ /** Look up recovery information for a session key.
44
+ * Returns the current and previous Claude session IDs, plus derived
45
+ * file paths and CLI commands for conversation recovery. */
46
+ export declare function lookupSessionRecovery(key: string): {
47
+ claudeSessionId: string;
48
+ previousClaudeSessionId?: string;
49
+ createdAt: number;
50
+ lastUsedAt: number;
51
+ messageCount: number;
52
+ } | undefined;
53
+ /** List all stored session keys and their Claude session IDs.
54
+ * Used by the recovery endpoint to find sessions by partial match. */
55
+ export declare function listStoredSessions(): Array<{
56
+ key: string;
57
+ claudeSessionId: string;
58
+ previousClaudeSessionId?: string;
59
+ createdAt: number;
60
+ lastUsedAt: number;
61
+ messageCount: number;
62
+ }>;
39
63
  export declare function clearSharedSessions(): void;
40
64
  //# sourceMappingURL=sessionStore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/proxy/sessionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,wDAAwD;IACxD,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AA0DD;;oFAEoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAG7F;AAsED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG1E;AAED,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAYhG;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EACtC,YAAY,CAAC,EAAE,UAAU,GACxB,IAAI,CAsCN;AAED;wEACwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
1
+ {"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/proxy/sessionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,wDAAwD;IACxD,YAAY,CAAC,EAAE,UAAU,CAAA;IACzB;;kEAE8D;IAC9D,uBAAuB,CAAC,EAAE,MAAM,CAAA;CACjC;AA0DD;;oFAEoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAG7F;AAsED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG1E;AAED,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAYhG;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EACtC,YAAY,CAAC,EAAE,UAAU,GACxB,IAAI,CA8CN;AAED;wEACwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAED;;6DAE6D;AAC7D,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAClD,eAAe,EAAE,MAAM,CAAA;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,SAAS,CAWZ;AAED;uEACuE;AACvE,wBAAgB,kBAAkB,IAAI,KAAK,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,eAAe,EAAE,MAAM,CAAA;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAC,CAUD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getMaxSessionsLimit,
7
7
  hashMessage,
8
8
  startProxyServer
9
- } from "./cli-zcxn6xmn.js";
9
+ } from "./cli-msyx6dnk.js";
10
10
  import"./cli-g9ypdz51.js";
11
11
  import"./cli-rtab0qa6.js";
12
12
  import"./cli-m9pfb7h9.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.28.1",
3
+ "version": "1.29.0",
4
4
  "description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
@@ -24,7 +24,7 @@
24
24
  "build": "rm -rf dist && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' && tsc -p tsconfig.build.json",
25
25
  "postbuild": "node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
26
26
  "prepublishOnly": "bun run build",
27
- "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*profile-switch-integration*' && bun test src/__tests__/profile-switch-integration.test.ts && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts",
27
+ "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*profile-switch-integration*' --path-ignore-patterns '**/*session-recovery*' && bun test src/__tests__/profile-switch-integration.test.ts && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts && bun test src/__tests__/proxy-session-recovery.test.ts",
28
28
  "typecheck": "tsc --noEmit",
29
29
  "proxy:direct": "bun run ./bin/cli.ts"
30
30
  },