claude-memory-layer 1.0.35 → 1.0.37

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.
@@ -2327,6 +2327,14 @@ function normalizeQueryRewriteKind(value) {
2327
2327
  return "none";
2328
2328
  }
2329
2329
  var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
2330
+ var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
2331
+ var DEFAULT_OUTBOX_MAX_RETRIES = 3;
2332
+ function emptyOutboxRecoveryResult() {
2333
+ return {
2334
+ embedding: { recoveredProcessing: 0, retriedFailed: 0 },
2335
+ vector: { recoveredProcessing: 0, retriedFailed: 0 }
2336
+ };
2337
+ }
2330
2338
  var SQLiteEventStore = class {
2331
2339
  db;
2332
2340
  initialized = false;
@@ -3077,7 +3085,9 @@ var SQLiteEventStore = class {
3077
3085
  const placeholders = ids.map(() => "?").join(",");
3078
3086
  sqliteRun(
3079
3087
  this.db,
3080
- `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
3088
+ `UPDATE embedding_outbox
3089
+ SET status = 'processing', processed_at = datetime('now'), error_message = NULL
3090
+ WHERE id IN (${placeholders})`,
3081
3091
  ids
3082
3092
  );
3083
3093
  return pending.map((row) => ({
@@ -3147,6 +3157,58 @@ var SQLiteEventStore = class {
3147
3157
  [error, ...ids]
3148
3158
  );
3149
3159
  }
3160
+ /**
3161
+ * Recover abandoned outbox work after a worker/process crash.
3162
+ *
3163
+ * Rows in `processing` are claimed work. If the process exits before marking
3164
+ * them done/failed, they otherwise remain invisible to future processing.
3165
+ * Recovery is deliberately age-gated so an active worker is not disturbed.
3166
+ */
3167
+ async recoverStuckOutboxItems(options = {}) {
3168
+ await this.initialize();
3169
+ const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
3170
+ const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
3171
+ const now = options.now ?? /* @__PURE__ */ new Date();
3172
+ const threshold = new Date(now.getTime() - thresholdMs).toISOString();
3173
+ const result = emptyOutboxRecoveryResult();
3174
+ const embeddingRecovered = sqliteRun(
3175
+ this.db,
3176
+ `UPDATE embedding_outbox
3177
+ SET status = 'pending', processed_at = NULL, error_message = NULL
3178
+ WHERE status = 'processing'
3179
+ AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
3180
+ [threshold]
3181
+ );
3182
+ result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
3183
+ const embeddingRetried = sqliteRun(
3184
+ this.db,
3185
+ `UPDATE embedding_outbox
3186
+ SET status = 'pending', error_message = NULL
3187
+ WHERE status = 'failed'
3188
+ AND retry_count < ?`,
3189
+ [maxRetries]
3190
+ );
3191
+ result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
3192
+ const vectorRecovered = sqliteRun(
3193
+ this.db,
3194
+ `UPDATE vector_outbox
3195
+ SET status = 'pending', updated_at = ?, error = NULL
3196
+ WHERE status = 'processing'
3197
+ AND datetime(updated_at) < datetime(?)`,
3198
+ [now.toISOString(), threshold]
3199
+ );
3200
+ result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
3201
+ const vectorRetried = sqliteRun(
3202
+ this.db,
3203
+ `UPDATE vector_outbox
3204
+ SET status = 'pending', updated_at = ?, error = NULL
3205
+ WHERE status = 'failed'
3206
+ AND retry_count < ?`,
3207
+ [now.toISOString(), maxRetries]
3208
+ );
3209
+ result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
3210
+ return result;
3211
+ }
3150
3212
  /**
3151
3213
  * Get embedding/vector outbox health statistics
3152
3214
  */
@@ -4018,6 +4080,7 @@ var VectorStore = class {
4018
4080
  * Get total count of vectors
4019
4081
  */
4020
4082
  async count() {
4083
+ await this.initialize();
4021
4084
  if (!this.table)
4022
4085
  return 0;
4023
4086
  const result = await this.table.countRows();
@@ -4456,6 +4519,10 @@ var MemoryQueryService = class {
4456
4519
  await this.initialize();
4457
4520
  return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
4458
4521
  }
4522
+ async recoverStuckOutboxItems(options) {
4523
+ await this.initialize();
4524
+ return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
4525
+ }
4459
4526
  async getStats() {
4460
4527
  await this.initialize();
4461
4528
  const deps = this.getStatsDeps();
@@ -7728,6 +7795,9 @@ var MemoryService = class {
7728
7795
  async getOutboxStats() {
7729
7796
  return this.queryService.getOutboxStats();
7730
7797
  }
7798
+ async recoverStuckOutboxItems(options) {
7799
+ return this.queryService.recoverStuckOutboxItems(options);
7800
+ }
7731
7801
  async getRetrievalTraceStats() {
7732
7802
  return this.retrievalAnalyticsService.getRetrievalTraceStats();
7733
7803
  }
@@ -8041,6 +8111,26 @@ function getServiceFromQuery(c) {
8041
8111
  }
8042
8112
  return getReadOnlyMemoryService();
8043
8113
  }
8114
+ function getWritableServiceFromQuery(c) {
8115
+ const project = c.req.query("project") || c.req.query("projectId");
8116
+ if (project) {
8117
+ const storagePath = resolveProjectStoragePath(project);
8118
+ return new MemoryService({
8119
+ storagePath,
8120
+ readOnly: false,
8121
+ lightweightMode: true,
8122
+ analyticsEnabled: false,
8123
+ sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
8124
+ });
8125
+ }
8126
+ return new MemoryService({
8127
+ storagePath: "~/.claude-code/memory",
8128
+ readOnly: false,
8129
+ lightweightMode: true,
8130
+ analyticsEnabled: false,
8131
+ sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
8132
+ });
8133
+ }
8044
8134
  function getLightweightServiceFromQuery(c) {
8045
8135
  const project = c.req.query("project") || c.req.query("projectId");
8046
8136
  if (project) {
@@ -8067,7 +8157,7 @@ var sessionsRouter = new Hono();
8067
8157
  sessionsRouter.get("/", async (c) => {
8068
8158
  const page = parseInt(c.req.query("page") || "1", 10);
8069
8159
  const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
8070
- const memoryService = getServiceFromQuery(c);
8160
+ const memoryService = getLightweightServiceFromQuery(c);
8071
8161
  try {
8072
8162
  await memoryService.initialize();
8073
8163
  const recentEvents = await memoryService.getRecentEvents(1e3);
@@ -8111,7 +8201,7 @@ sessionsRouter.get("/", async (c) => {
8111
8201
  });
8112
8202
  sessionsRouter.get("/:id", async (c) => {
8113
8203
  const { id } = c.req.param();
8114
- const memoryService = getServiceFromQuery(c);
8204
+ const memoryService = getLightweightServiceFromQuery(c);
8115
8205
  try {
8116
8206
  await memoryService.initialize();
8117
8207
  const events = await memoryService.getSessionHistory(id);
@@ -8157,7 +8247,7 @@ eventsRouter.get("/", async (c) => {
8157
8247
  const q = (c.req.query("q") || "").trim().toLowerCase();
8158
8248
  const limit = parseInt(c.req.query("limit") || "100", 10);
8159
8249
  const offset = parseInt(c.req.query("offset") || "0", 10);
8160
- const memoryService = getServiceFromQuery(c);
8250
+ const memoryService = getLightweightServiceFromQuery(c);
8161
8251
  try {
8162
8252
  await memoryService.initialize();
8163
8253
  let events;
@@ -8213,7 +8303,7 @@ eventsRouter.get("/", async (c) => {
8213
8303
  });
8214
8304
  eventsRouter.get("/:id", async (c) => {
8215
8305
  const { id } = c.req.param();
8216
- const memoryService = getServiceFromQuery(c);
8306
+ const memoryService = getLightweightServiceFromQuery(c);
8217
8307
  try {
8218
8308
  await memoryService.initialize();
8219
8309
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -8252,6 +8342,10 @@ eventsRouter.get("/:id", async (c) => {
8252
8342
  // src/apps/server/api/search.ts
8253
8343
  import { Hono as Hono3 } from "hono";
8254
8344
  var searchRouter = new Hono3();
8345
+ function isEmbeddingBackendUnavailable(error) {
8346
+ const message = error instanceof Error ? error.message : String(error);
8347
+ return /model file path or buffer|onnxruntime|transformers|embedding backend/i.test(message);
8348
+ }
8255
8349
  searchRouter.post("/", async (c) => {
8256
8350
  const memoryService = getServiceFromQuery(c);
8257
8351
  try {
@@ -8293,15 +8387,41 @@ searchRouter.post("/", async (c) => {
8293
8387
  });
8294
8388
  searchRouter.post("/disclosure", async (c) => {
8295
8389
  let memoryService;
8390
+ let body;
8296
8391
  try {
8297
- const body = await c.req.json();
8392
+ body = await c.req.json();
8298
8393
  if (!body.query) {
8299
8394
  return c.json({ error: "Query is required" }, 400);
8300
8395
  }
8301
- memoryService = body.options?.strategy === "fast" ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
8302
- await memoryService.initialize();
8303
- const result = await memoryService.searchDisclosure(body.query, body.options);
8304
- return c.json(result);
8396
+ const useFastStrategy = body.options?.strategy === "fast";
8397
+ memoryService = useFastStrategy ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
8398
+ try {
8399
+ await memoryService.initialize();
8400
+ const result = await memoryService.searchDisclosure(body.query, body.options);
8401
+ return c.json(result);
8402
+ } catch (error) {
8403
+ if (!useFastStrategy && isEmbeddingBackendUnavailable(error)) {
8404
+ await memoryService.shutdown();
8405
+ memoryService = getLightweightServiceFromQuery(c);
8406
+ await memoryService.initialize();
8407
+ const result = await memoryService.searchDisclosure(body.query, {
8408
+ ...body.options,
8409
+ strategy: "fast"
8410
+ });
8411
+ return c.json({
8412
+ ...result,
8413
+ meta: {
8414
+ ...result.meta,
8415
+ fallbackApplied: true,
8416
+ fallbackTrace: [
8417
+ ...result.meta.fallbackTrace || [],
8418
+ "fallback:embedding-backend-unavailable:fast"
8419
+ ]
8420
+ }
8421
+ });
8422
+ }
8423
+ throw error;
8424
+ }
8305
8425
  } catch (error) {
8306
8426
  return c.json({ error: error.message }, 500);
8307
8427
  } finally {
@@ -8309,7 +8429,7 @@ searchRouter.post("/disclosure", async (c) => {
8309
8429
  }
8310
8430
  });
8311
8431
  searchRouter.get("/disclosure/:resultId/expand", async (c) => {
8312
- const memoryService = getServiceFromQuery(c);
8432
+ const memoryService = getLightweightServiceFromQuery(c);
8313
8433
  try {
8314
8434
  const resultId = c.req.param("resultId");
8315
8435
  const rawWindowSize = c.req.query("windowSize");
@@ -8329,7 +8449,7 @@ searchRouter.get("/disclosure/:resultId/expand", async (c) => {
8329
8449
  }
8330
8450
  });
8331
8451
  searchRouter.get("/disclosure/:resultId/source", async (c) => {
8332
- const memoryService = getServiceFromQuery(c);
8452
+ const memoryService = getLightweightServiceFromQuery(c);
8333
8453
  try {
8334
8454
  const resultId = c.req.param("resultId");
8335
8455
  const result = await memoryService.sourceDisclosure(resultId);
@@ -9666,6 +9786,13 @@ import { Hono as Hono8 } from "hono";
9666
9786
  import { streamSSE } from "hono/streaming";
9667
9787
  import { spawn } from "child_process";
9668
9788
  var chatRouter = new Hono8();
9789
+ var ProviderFailure = class extends Error {
9790
+ constructor(code, message) {
9791
+ super(message);
9792
+ this.code = code;
9793
+ this.name = "ProviderFailure";
9794
+ }
9795
+ };
9669
9796
  var CLAUDE_TIMEOUT_MS = 12e4;
9670
9797
  chatRouter.post("/", async (c) => {
9671
9798
  let body;
@@ -9677,43 +9804,12 @@ chatRouter.post("/", async (c) => {
9677
9804
  if (!body.message?.trim()) {
9678
9805
  return c.json({ error: "Message is required" }, 400);
9679
9806
  }
9680
- const memoryService = getServiceFromQuery(c);
9807
+ const memoryOnly = body.mode === "memory-only" || body.memoryOnly === true;
9808
+ const memoryService = memoryOnly ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
9681
9809
  try {
9682
9810
  await memoryService.initialize();
9683
- let memoryContext = "";
9684
- let statsContext = "";
9685
- try {
9686
- const result = await memoryService.retrieveMemories(body.message, {
9687
- topK: 8,
9688
- minScore: 0.5
9689
- });
9690
- if (result.memories.length > 0) {
9691
- const parts = ["## Relevant Memories\n"];
9692
- for (const m of result.memories) {
9693
- const date = new Date(m.event.timestamp).toISOString().split("T")[0];
9694
- const content = m.event.content.slice(0, 500);
9695
- parts.push(`### [${m.event.eventType}] ${date} (score: ${m.score.toFixed(2)})`);
9696
- parts.push(content);
9697
- if (m.sessionContext) {
9698
- parts.push(`_Context: ${m.sessionContext}_`);
9699
- }
9700
- parts.push("");
9701
- }
9702
- memoryContext = parts.join("\n");
9703
- }
9704
- } catch {
9705
- }
9706
- try {
9707
- const stats = await memoryService.getStats();
9708
- const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
9709
- statsContext = [
9710
- "## Memory Stats",
9711
- `- Total events: ${stats.totalEvents}`,
9712
- `- Vector nodes: ${stats.vectorCount}`,
9713
- `- By level: ${levels}`
9714
- ].join("\n");
9715
- } catch {
9716
- }
9811
+ const { memoryContext, memoryHits } = await collectMemoryContext(memoryService, body.message);
9812
+ const statsContext = await collectStatsContext(memoryService);
9717
9813
  const fullPrompt = buildPrompt(
9718
9814
  statsContext,
9719
9815
  memoryContext,
@@ -9721,13 +9817,23 @@ chatRouter.post("/", async (c) => {
9721
9817
  body.message
9722
9818
  );
9723
9819
  return streamSSE(c, async (stream) => {
9820
+ if (memoryOnly) {
9821
+ await streamMemoryOnlyResponse(stream, {
9822
+ memoryContext,
9823
+ memoryHits,
9824
+ reason: "memory-only-mode"
9825
+ });
9826
+ return;
9827
+ }
9724
9828
  try {
9725
9829
  await streamClaudeResponse(fullPrompt, stream);
9726
9830
  } catch (err) {
9831
+ const diagnostic = providerDiagnostic(err);
9727
9832
  await stream.writeSSE({
9728
- event: "error",
9729
- data: JSON.stringify({ error: err.message })
9833
+ event: "provider_error",
9834
+ data: JSON.stringify(diagnostic)
9730
9835
  });
9836
+ await streamMemoryOnlyFallback(stream, memoryContext, memoryHits);
9731
9837
  }
9732
9838
  });
9733
9839
  } catch (error) {
@@ -9736,6 +9842,115 @@ chatRouter.post("/", async (c) => {
9736
9842
  await memoryService.shutdown();
9737
9843
  }
9738
9844
  });
9845
+ async function collectMemoryContext(memoryService, query) {
9846
+ let memoryHits = [];
9847
+ try {
9848
+ const result = await memoryService.retrieveMemories?.(query, {
9849
+ topK: 8,
9850
+ minScore: 0.5
9851
+ });
9852
+ memoryHits = result?.memories ?? [];
9853
+ } catch {
9854
+ memoryHits = [];
9855
+ }
9856
+ if (memoryHits.length === 0) {
9857
+ try {
9858
+ memoryHits = await memoryService.keywordSearch?.(query, { topK: 8, minScore: 0.05 }) ?? [];
9859
+ } catch {
9860
+ memoryHits = [];
9861
+ }
9862
+ }
9863
+ return {
9864
+ memoryContext: formatMemoryContext(memoryHits),
9865
+ memoryHits
9866
+ };
9867
+ }
9868
+ async function collectStatsContext(memoryService) {
9869
+ try {
9870
+ const stats = await memoryService.getStats?.();
9871
+ if (!stats)
9872
+ return "";
9873
+ const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
9874
+ return [
9875
+ "## Memory Stats",
9876
+ `- Total events: ${stats.totalEvents}`,
9877
+ `- Vector nodes: ${stats.vectorCount}`,
9878
+ `- By level: ${levels}`
9879
+ ].join("\n");
9880
+ } catch {
9881
+ return "";
9882
+ }
9883
+ }
9884
+ function formatMemoryContext(memoryHits) {
9885
+ if (memoryHits.length === 0)
9886
+ return "";
9887
+ const parts = ["## Relevant Memories\n"];
9888
+ for (const m of memoryHits) {
9889
+ const date = m.event.timestamp ? new Date(m.event.timestamp).toISOString().split("T")[0] : "unknown-date";
9890
+ const content = (m.event.content ?? "").slice(0, 500);
9891
+ parts.push(`### [${m.event.eventType ?? "memory"}] ${date} (score: ${m.score.toFixed(2)})`);
9892
+ parts.push(content);
9893
+ if (m.sessionContext) {
9894
+ parts.push(`_Context: ${m.sessionContext}_`);
9895
+ }
9896
+ parts.push("");
9897
+ }
9898
+ return parts.join("\n");
9899
+ }
9900
+ async function streamMemoryOnlyResponse(stream, options) {
9901
+ await stream.writeSSE({
9902
+ event: "diagnostic",
9903
+ data: JSON.stringify({
9904
+ provider: "claude-cli",
9905
+ status: "skipped",
9906
+ mode: "memory-only",
9907
+ reason: options.reason,
9908
+ retrievedMemories: options.memoryHits.length
9909
+ })
9910
+ });
9911
+ await streamMemoryOnlyFallback(stream, options.memoryContext, options.memoryHits);
9912
+ }
9913
+ async function streamMemoryOnlyFallback(stream, memoryContext, memoryHits) {
9914
+ const content = memoryHits.length > 0 ? [
9915
+ "Provider unavailable or skipped; showing retrieved memory context directly.",
9916
+ "",
9917
+ memoryContext
9918
+ ].join("\n") : "Provider unavailable or skipped, and no directly relevant memories were found for this query.";
9919
+ await stream.writeSSE({
9920
+ event: "message",
9921
+ data: JSON.stringify({ content, mode: "memory-only" })
9922
+ });
9923
+ await stream.writeSSE({ event: "done", data: "{}" });
9924
+ }
9925
+ function providerDiagnostic(err) {
9926
+ if (err instanceof ProviderFailure) {
9927
+ return {
9928
+ provider: "claude-cli",
9929
+ code: err.code,
9930
+ message: err.message,
9931
+ fallback: "memory-only"
9932
+ };
9933
+ }
9934
+ return {
9935
+ provider: "claude-cli",
9936
+ code: "claude-cli-error",
9937
+ message: err instanceof Error ? err.message : "Unknown Claude CLI failure",
9938
+ fallback: "memory-only"
9939
+ };
9940
+ }
9941
+ function classifyProviderFailure(message) {
9942
+ const normalized = message.toLowerCase();
9943
+ if (normalized.includes("401") || normalized.includes("unauthorized") || normalized.includes("auth")) {
9944
+ return new ProviderFailure("claude-cli-auth", "Claude CLI authentication failed; showing memory-only context.");
9945
+ }
9946
+ if (normalized.includes("not found") || normalized.includes("enoent")) {
9947
+ return new ProviderFailure("claude-cli-not-found", "Claude CLI was not found; showing memory-only context.");
9948
+ }
9949
+ if (normalized.includes("timed out")) {
9950
+ return new ProviderFailure("claude-cli-timeout", "Claude CLI timed out; showing memory-only context.");
9951
+ }
9952
+ return new ProviderFailure("claude-cli-error", "Claude CLI failed; showing memory-only context.");
9953
+ }
9739
9954
  function buildPrompt(statsContext, memoryContext, history, currentMessage) {
9740
9955
  const parts = [];
9741
9956
  parts.push("You are a helpful assistant that answers questions about the user's code memory data.");
@@ -9778,12 +9993,13 @@ function streamClaudeResponse(prompt, stream) {
9778
9993
  });
9779
9994
  const timeout = setTimeout(() => {
9780
9995
  proc.kill("SIGTERM");
9781
- reject(new Error("Chat response timed out after 2 minutes"));
9996
+ reject(classifyProviderFailure("timed out"));
9782
9997
  }, CLAUDE_TIMEOUT_MS);
9783
9998
  proc.stdin.write(prompt);
9784
9999
  proc.stdin.end();
9785
10000
  let buffer = "";
9786
10001
  let lastSentText = "";
10002
+ let stderrText = "";
9787
10003
  proc.stdout.on("data", async (chunk) => {
9788
10004
  buffer += chunk.toString();
9789
10005
  const lines = buffer.split("\n");
@@ -9812,6 +10028,7 @@ function streamClaudeResponse(prompt, stream) {
9812
10028
  }
9813
10029
  });
9814
10030
  proc.stderr.on("data", (chunk) => {
10031
+ stderrText += chunk.toString();
9815
10032
  if (process.env.CLAUDE_MEMORY_DEBUG) {
9816
10033
  console.error("[chat] claude stderr:", chunk.toString());
9817
10034
  }
@@ -9819,7 +10036,7 @@ function streamClaudeResponse(prompt, stream) {
9819
10036
  proc.on("error", (err) => {
9820
10037
  clearTimeout(timeout);
9821
10038
  if (err.code === "ENOENT") {
9822
- reject(new Error("Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"));
10039
+ reject(classifyProviderFailure("ENOENT not found"));
9823
10040
  } else {
9824
10041
  reject(err);
9825
10042
  }
@@ -9836,7 +10053,7 @@ function streamClaudeResponse(prompt, stream) {
9836
10053
  }
9837
10054
  }
9838
10055
  if (code !== 0 && code !== null) {
9839
- reject(new Error(`Claude CLI exited with code ${code}`));
10056
+ reject(classifyProviderFailure(stderrText || `Claude CLI exited with code ${code}`));
9840
10057
  } else {
9841
10058
  resolve3();
9842
10059
  }
@@ -9885,6 +10102,49 @@ healthRouter.get("/", async (c) => {
9885
10102
  await memoryService.shutdown();
9886
10103
  }
9887
10104
  });
10105
+ healthRouter.post("/recover", async (c) => {
10106
+ const memoryService = getWritableServiceFromQuery(c);
10107
+ try {
10108
+ await memoryService.initialize();
10109
+ const body = await c.req.json().catch(() => ({}));
10110
+ const options = {};
10111
+ if (typeof body.stuckThresholdMs === "number" && Number.isFinite(body.stuckThresholdMs)) {
10112
+ options.stuckThresholdMs = body.stuckThresholdMs;
10113
+ }
10114
+ if (typeof body.maxRetries === "number" && Number.isFinite(body.maxRetries)) {
10115
+ options.maxRetries = body.maxRetries;
10116
+ }
10117
+ const before = await memoryService.getOutboxStats();
10118
+ const recovered = await memoryService.recoverStuckOutboxItems(options);
10119
+ const [stats, outbox] = await Promise.all([
10120
+ memoryService.getStats(),
10121
+ memoryService.getOutboxStats()
10122
+ ]);
10123
+ return c.json({
10124
+ status: "ok",
10125
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10126
+ recovered,
10127
+ before: {
10128
+ outbox: before
10129
+ },
10130
+ after: {
10131
+ storage: {
10132
+ totalEvents: stats.totalEvents,
10133
+ vectorCount: stats.vectorCount
10134
+ },
10135
+ outbox
10136
+ }
10137
+ });
10138
+ } catch (error) {
10139
+ return c.json({
10140
+ status: "error",
10141
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10142
+ error: error.message
10143
+ }, 500);
10144
+ } finally {
10145
+ await memoryService.shutdown();
10146
+ }
10147
+ });
9888
10148
 
9889
10149
  // src/apps/server/api/index.ts
9890
10150
  var apiRouter = new Hono10().route("/sessions", sessionsRouter).route("/events", eventsRouter).route("/search", searchRouter).route("/stats", statsRouter).route("/citations", citationsRouter).route("/turns", turnsRouter).route("/projects", projectsRouter).route("/chat", chatRouter).route("/health", healthRouter);