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.
@@ -2339,6 +2339,14 @@ function normalizeQueryRewriteKind(value) {
2339
2339
  return "none";
2340
2340
  }
2341
2341
  var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
2342
+ var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
2343
+ var DEFAULT_OUTBOX_MAX_RETRIES = 3;
2344
+ function emptyOutboxRecoveryResult() {
2345
+ return {
2346
+ embedding: { recoveredProcessing: 0, retriedFailed: 0 },
2347
+ vector: { recoveredProcessing: 0, retriedFailed: 0 }
2348
+ };
2349
+ }
2342
2350
  var SQLiteEventStore = class {
2343
2351
  db;
2344
2352
  initialized = false;
@@ -3089,7 +3097,9 @@ var SQLiteEventStore = class {
3089
3097
  const placeholders = ids.map(() => "?").join(",");
3090
3098
  sqliteRun(
3091
3099
  this.db,
3092
- `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
3100
+ `UPDATE embedding_outbox
3101
+ SET status = 'processing', processed_at = datetime('now'), error_message = NULL
3102
+ WHERE id IN (${placeholders})`,
3093
3103
  ids
3094
3104
  );
3095
3105
  return pending.map((row) => ({
@@ -3159,6 +3169,58 @@ var SQLiteEventStore = class {
3159
3169
  [error, ...ids]
3160
3170
  );
3161
3171
  }
3172
+ /**
3173
+ * Recover abandoned outbox work after a worker/process crash.
3174
+ *
3175
+ * Rows in `processing` are claimed work. If the process exits before marking
3176
+ * them done/failed, they otherwise remain invisible to future processing.
3177
+ * Recovery is deliberately age-gated so an active worker is not disturbed.
3178
+ */
3179
+ async recoverStuckOutboxItems(options = {}) {
3180
+ await this.initialize();
3181
+ const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
3182
+ const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
3183
+ const now = options.now ?? /* @__PURE__ */ new Date();
3184
+ const threshold = new Date(now.getTime() - thresholdMs).toISOString();
3185
+ const result = emptyOutboxRecoveryResult();
3186
+ const embeddingRecovered = sqliteRun(
3187
+ this.db,
3188
+ `UPDATE embedding_outbox
3189
+ SET status = 'pending', processed_at = NULL, error_message = NULL
3190
+ WHERE status = 'processing'
3191
+ AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
3192
+ [threshold]
3193
+ );
3194
+ result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
3195
+ const embeddingRetried = sqliteRun(
3196
+ this.db,
3197
+ `UPDATE embedding_outbox
3198
+ SET status = 'pending', error_message = NULL
3199
+ WHERE status = 'failed'
3200
+ AND retry_count < ?`,
3201
+ [maxRetries]
3202
+ );
3203
+ result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
3204
+ const vectorRecovered = sqliteRun(
3205
+ this.db,
3206
+ `UPDATE vector_outbox
3207
+ SET status = 'pending', updated_at = ?, error = NULL
3208
+ WHERE status = 'processing'
3209
+ AND datetime(updated_at) < datetime(?)`,
3210
+ [now.toISOString(), threshold]
3211
+ );
3212
+ result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
3213
+ const vectorRetried = sqliteRun(
3214
+ this.db,
3215
+ `UPDATE vector_outbox
3216
+ SET status = 'pending', updated_at = ?, error = NULL
3217
+ WHERE status = 'failed'
3218
+ AND retry_count < ?`,
3219
+ [now.toISOString(), maxRetries]
3220
+ );
3221
+ result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
3222
+ return result;
3223
+ }
3162
3224
  /**
3163
3225
  * Get embedding/vector outbox health statistics
3164
3226
  */
@@ -4030,6 +4092,7 @@ var VectorStore = class {
4030
4092
  * Get total count of vectors
4031
4093
  */
4032
4094
  async count() {
4095
+ await this.initialize();
4033
4096
  if (!this.table)
4034
4097
  return 0;
4035
4098
  const result = await this.table.countRows();
@@ -4468,6 +4531,10 @@ var MemoryQueryService = class {
4468
4531
  await this.initialize();
4469
4532
  return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
4470
4533
  }
4534
+ async recoverStuckOutboxItems(options) {
4535
+ await this.initialize();
4536
+ return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
4537
+ }
4471
4538
  async getStats() {
4472
4539
  await this.initialize();
4473
4540
  const deps = this.getStatsDeps();
@@ -7740,6 +7807,9 @@ var MemoryService = class {
7740
7807
  async getOutboxStats() {
7741
7808
  return this.queryService.getOutboxStats();
7742
7809
  }
7810
+ async recoverStuckOutboxItems(options) {
7811
+ return this.queryService.recoverStuckOutboxItems(options);
7812
+ }
7743
7813
  async getRetrievalTraceStats() {
7744
7814
  return this.retrievalAnalyticsService.getRetrievalTraceStats();
7745
7815
  }
@@ -8053,6 +8123,26 @@ function getServiceFromQuery(c) {
8053
8123
  }
8054
8124
  return getReadOnlyMemoryService();
8055
8125
  }
8126
+ function getWritableServiceFromQuery(c) {
8127
+ const project = c.req.query("project") || c.req.query("projectId");
8128
+ if (project) {
8129
+ const storagePath = resolveProjectStoragePath(project);
8130
+ return new MemoryService({
8131
+ storagePath,
8132
+ readOnly: false,
8133
+ lightweightMode: true,
8134
+ analyticsEnabled: false,
8135
+ sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
8136
+ });
8137
+ }
8138
+ return new MemoryService({
8139
+ storagePath: "~/.claude-code/memory",
8140
+ readOnly: false,
8141
+ lightweightMode: true,
8142
+ analyticsEnabled: false,
8143
+ sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
8144
+ });
8145
+ }
8056
8146
  function getLightweightServiceFromQuery(c) {
8057
8147
  const project = c.req.query("project") || c.req.query("projectId");
8058
8148
  if (project) {
@@ -8079,7 +8169,7 @@ var sessionsRouter = new Hono();
8079
8169
  sessionsRouter.get("/", async (c) => {
8080
8170
  const page = parseInt(c.req.query("page") || "1", 10);
8081
8171
  const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
8082
- const memoryService = getServiceFromQuery(c);
8172
+ const memoryService = getLightweightServiceFromQuery(c);
8083
8173
  try {
8084
8174
  await memoryService.initialize();
8085
8175
  const recentEvents = await memoryService.getRecentEvents(1e3);
@@ -8123,7 +8213,7 @@ sessionsRouter.get("/", async (c) => {
8123
8213
  });
8124
8214
  sessionsRouter.get("/:id", async (c) => {
8125
8215
  const { id } = c.req.param();
8126
- const memoryService = getServiceFromQuery(c);
8216
+ const memoryService = getLightweightServiceFromQuery(c);
8127
8217
  try {
8128
8218
  await memoryService.initialize();
8129
8219
  const events = await memoryService.getSessionHistory(id);
@@ -8169,7 +8259,7 @@ eventsRouter.get("/", async (c) => {
8169
8259
  const q = (c.req.query("q") || "").trim().toLowerCase();
8170
8260
  const limit = parseInt(c.req.query("limit") || "100", 10);
8171
8261
  const offset = parseInt(c.req.query("offset") || "0", 10);
8172
- const memoryService = getServiceFromQuery(c);
8262
+ const memoryService = getLightweightServiceFromQuery(c);
8173
8263
  try {
8174
8264
  await memoryService.initialize();
8175
8265
  let events;
@@ -8225,7 +8315,7 @@ eventsRouter.get("/", async (c) => {
8225
8315
  });
8226
8316
  eventsRouter.get("/:id", async (c) => {
8227
8317
  const { id } = c.req.param();
8228
- const memoryService = getServiceFromQuery(c);
8318
+ const memoryService = getLightweightServiceFromQuery(c);
8229
8319
  try {
8230
8320
  await memoryService.initialize();
8231
8321
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -8264,6 +8354,10 @@ eventsRouter.get("/:id", async (c) => {
8264
8354
  // src/apps/server/api/search.ts
8265
8355
  import { Hono as Hono3 } from "hono";
8266
8356
  var searchRouter = new Hono3();
8357
+ function isEmbeddingBackendUnavailable(error) {
8358
+ const message = error instanceof Error ? error.message : String(error);
8359
+ return /model file path or buffer|onnxruntime|transformers|embedding backend/i.test(message);
8360
+ }
8267
8361
  searchRouter.post("/", async (c) => {
8268
8362
  const memoryService = getServiceFromQuery(c);
8269
8363
  try {
@@ -8305,15 +8399,41 @@ searchRouter.post("/", async (c) => {
8305
8399
  });
8306
8400
  searchRouter.post("/disclosure", async (c) => {
8307
8401
  let memoryService;
8402
+ let body;
8308
8403
  try {
8309
- const body = await c.req.json();
8404
+ body = await c.req.json();
8310
8405
  if (!body.query) {
8311
8406
  return c.json({ error: "Query is required" }, 400);
8312
8407
  }
8313
- memoryService = body.options?.strategy === "fast" ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
8314
- await memoryService.initialize();
8315
- const result = await memoryService.searchDisclosure(body.query, body.options);
8316
- return c.json(result);
8408
+ const useFastStrategy = body.options?.strategy === "fast";
8409
+ memoryService = useFastStrategy ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
8410
+ try {
8411
+ await memoryService.initialize();
8412
+ const result = await memoryService.searchDisclosure(body.query, body.options);
8413
+ return c.json(result);
8414
+ } catch (error) {
8415
+ if (!useFastStrategy && isEmbeddingBackendUnavailable(error)) {
8416
+ await memoryService.shutdown();
8417
+ memoryService = getLightweightServiceFromQuery(c);
8418
+ await memoryService.initialize();
8419
+ const result = await memoryService.searchDisclosure(body.query, {
8420
+ ...body.options,
8421
+ strategy: "fast"
8422
+ });
8423
+ return c.json({
8424
+ ...result,
8425
+ meta: {
8426
+ ...result.meta,
8427
+ fallbackApplied: true,
8428
+ fallbackTrace: [
8429
+ ...result.meta.fallbackTrace || [],
8430
+ "fallback:embedding-backend-unavailable:fast"
8431
+ ]
8432
+ }
8433
+ });
8434
+ }
8435
+ throw error;
8436
+ }
8317
8437
  } catch (error) {
8318
8438
  return c.json({ error: error.message }, 500);
8319
8439
  } finally {
@@ -8321,7 +8441,7 @@ searchRouter.post("/disclosure", async (c) => {
8321
8441
  }
8322
8442
  });
8323
8443
  searchRouter.get("/disclosure/:resultId/expand", async (c) => {
8324
- const memoryService = getServiceFromQuery(c);
8444
+ const memoryService = getLightweightServiceFromQuery(c);
8325
8445
  try {
8326
8446
  const resultId = c.req.param("resultId");
8327
8447
  const rawWindowSize = c.req.query("windowSize");
@@ -8341,7 +8461,7 @@ searchRouter.get("/disclosure/:resultId/expand", async (c) => {
8341
8461
  }
8342
8462
  });
8343
8463
  searchRouter.get("/disclosure/:resultId/source", async (c) => {
8344
- const memoryService = getServiceFromQuery(c);
8464
+ const memoryService = getLightweightServiceFromQuery(c);
8345
8465
  try {
8346
8466
  const resultId = c.req.param("resultId");
8347
8467
  const result = await memoryService.sourceDisclosure(resultId);
@@ -9678,6 +9798,13 @@ import { Hono as Hono8 } from "hono";
9678
9798
  import { streamSSE } from "hono/streaming";
9679
9799
  import { spawn } from "child_process";
9680
9800
  var chatRouter = new Hono8();
9801
+ var ProviderFailure = class extends Error {
9802
+ constructor(code, message) {
9803
+ super(message);
9804
+ this.code = code;
9805
+ this.name = "ProviderFailure";
9806
+ }
9807
+ };
9681
9808
  var CLAUDE_TIMEOUT_MS = 12e4;
9682
9809
  chatRouter.post("/", async (c) => {
9683
9810
  let body;
@@ -9689,43 +9816,12 @@ chatRouter.post("/", async (c) => {
9689
9816
  if (!body.message?.trim()) {
9690
9817
  return c.json({ error: "Message is required" }, 400);
9691
9818
  }
9692
- const memoryService = getServiceFromQuery(c);
9819
+ const memoryOnly = body.mode === "memory-only" || body.memoryOnly === true;
9820
+ const memoryService = memoryOnly ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
9693
9821
  try {
9694
9822
  await memoryService.initialize();
9695
- let memoryContext = "";
9696
- let statsContext = "";
9697
- try {
9698
- const result = await memoryService.retrieveMemories(body.message, {
9699
- topK: 8,
9700
- minScore: 0.5
9701
- });
9702
- if (result.memories.length > 0) {
9703
- const parts = ["## Relevant Memories\n"];
9704
- for (const m of result.memories) {
9705
- const date = new Date(m.event.timestamp).toISOString().split("T")[0];
9706
- const content = m.event.content.slice(0, 500);
9707
- parts.push(`### [${m.event.eventType}] ${date} (score: ${m.score.toFixed(2)})`);
9708
- parts.push(content);
9709
- if (m.sessionContext) {
9710
- parts.push(`_Context: ${m.sessionContext}_`);
9711
- }
9712
- parts.push("");
9713
- }
9714
- memoryContext = parts.join("\n");
9715
- }
9716
- } catch {
9717
- }
9718
- try {
9719
- const stats = await memoryService.getStats();
9720
- const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
9721
- statsContext = [
9722
- "## Memory Stats",
9723
- `- Total events: ${stats.totalEvents}`,
9724
- `- Vector nodes: ${stats.vectorCount}`,
9725
- `- By level: ${levels}`
9726
- ].join("\n");
9727
- } catch {
9728
- }
9823
+ const { memoryContext, memoryHits } = await collectMemoryContext(memoryService, body.message);
9824
+ const statsContext = await collectStatsContext(memoryService);
9729
9825
  const fullPrompt = buildPrompt(
9730
9826
  statsContext,
9731
9827
  memoryContext,
@@ -9733,13 +9829,23 @@ chatRouter.post("/", async (c) => {
9733
9829
  body.message
9734
9830
  );
9735
9831
  return streamSSE(c, async (stream) => {
9832
+ if (memoryOnly) {
9833
+ await streamMemoryOnlyResponse(stream, {
9834
+ memoryContext,
9835
+ memoryHits,
9836
+ reason: "memory-only-mode"
9837
+ });
9838
+ return;
9839
+ }
9736
9840
  try {
9737
9841
  await streamClaudeResponse(fullPrompt, stream);
9738
9842
  } catch (err) {
9843
+ const diagnostic = providerDiagnostic(err);
9739
9844
  await stream.writeSSE({
9740
- event: "error",
9741
- data: JSON.stringify({ error: err.message })
9845
+ event: "provider_error",
9846
+ data: JSON.stringify(diagnostic)
9742
9847
  });
9848
+ await streamMemoryOnlyFallback(stream, memoryContext, memoryHits);
9743
9849
  }
9744
9850
  });
9745
9851
  } catch (error) {
@@ -9748,6 +9854,115 @@ chatRouter.post("/", async (c) => {
9748
9854
  await memoryService.shutdown();
9749
9855
  }
9750
9856
  });
9857
+ async function collectMemoryContext(memoryService, query) {
9858
+ let memoryHits = [];
9859
+ try {
9860
+ const result = await memoryService.retrieveMemories?.(query, {
9861
+ topK: 8,
9862
+ minScore: 0.5
9863
+ });
9864
+ memoryHits = result?.memories ?? [];
9865
+ } catch {
9866
+ memoryHits = [];
9867
+ }
9868
+ if (memoryHits.length === 0) {
9869
+ try {
9870
+ memoryHits = await memoryService.keywordSearch?.(query, { topK: 8, minScore: 0.05 }) ?? [];
9871
+ } catch {
9872
+ memoryHits = [];
9873
+ }
9874
+ }
9875
+ return {
9876
+ memoryContext: formatMemoryContext(memoryHits),
9877
+ memoryHits
9878
+ };
9879
+ }
9880
+ async function collectStatsContext(memoryService) {
9881
+ try {
9882
+ const stats = await memoryService.getStats?.();
9883
+ if (!stats)
9884
+ return "";
9885
+ const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
9886
+ return [
9887
+ "## Memory Stats",
9888
+ `- Total events: ${stats.totalEvents}`,
9889
+ `- Vector nodes: ${stats.vectorCount}`,
9890
+ `- By level: ${levels}`
9891
+ ].join("\n");
9892
+ } catch {
9893
+ return "";
9894
+ }
9895
+ }
9896
+ function formatMemoryContext(memoryHits) {
9897
+ if (memoryHits.length === 0)
9898
+ return "";
9899
+ const parts = ["## Relevant Memories\n"];
9900
+ for (const m of memoryHits) {
9901
+ const date = m.event.timestamp ? new Date(m.event.timestamp).toISOString().split("T")[0] : "unknown-date";
9902
+ const content = (m.event.content ?? "").slice(0, 500);
9903
+ parts.push(`### [${m.event.eventType ?? "memory"}] ${date} (score: ${m.score.toFixed(2)})`);
9904
+ parts.push(content);
9905
+ if (m.sessionContext) {
9906
+ parts.push(`_Context: ${m.sessionContext}_`);
9907
+ }
9908
+ parts.push("");
9909
+ }
9910
+ return parts.join("\n");
9911
+ }
9912
+ async function streamMemoryOnlyResponse(stream, options) {
9913
+ await stream.writeSSE({
9914
+ event: "diagnostic",
9915
+ data: JSON.stringify({
9916
+ provider: "claude-cli",
9917
+ status: "skipped",
9918
+ mode: "memory-only",
9919
+ reason: options.reason,
9920
+ retrievedMemories: options.memoryHits.length
9921
+ })
9922
+ });
9923
+ await streamMemoryOnlyFallback(stream, options.memoryContext, options.memoryHits);
9924
+ }
9925
+ async function streamMemoryOnlyFallback(stream, memoryContext, memoryHits) {
9926
+ const content = memoryHits.length > 0 ? [
9927
+ "Provider unavailable or skipped; showing retrieved memory context directly.",
9928
+ "",
9929
+ memoryContext
9930
+ ].join("\n") : "Provider unavailable or skipped, and no directly relevant memories were found for this query.";
9931
+ await stream.writeSSE({
9932
+ event: "message",
9933
+ data: JSON.stringify({ content, mode: "memory-only" })
9934
+ });
9935
+ await stream.writeSSE({ event: "done", data: "{}" });
9936
+ }
9937
+ function providerDiagnostic(err) {
9938
+ if (err instanceof ProviderFailure) {
9939
+ return {
9940
+ provider: "claude-cli",
9941
+ code: err.code,
9942
+ message: err.message,
9943
+ fallback: "memory-only"
9944
+ };
9945
+ }
9946
+ return {
9947
+ provider: "claude-cli",
9948
+ code: "claude-cli-error",
9949
+ message: err instanceof Error ? err.message : "Unknown Claude CLI failure",
9950
+ fallback: "memory-only"
9951
+ };
9952
+ }
9953
+ function classifyProviderFailure(message) {
9954
+ const normalized = message.toLowerCase();
9955
+ if (normalized.includes("401") || normalized.includes("unauthorized") || normalized.includes("auth")) {
9956
+ return new ProviderFailure("claude-cli-auth", "Claude CLI authentication failed; showing memory-only context.");
9957
+ }
9958
+ if (normalized.includes("not found") || normalized.includes("enoent")) {
9959
+ return new ProviderFailure("claude-cli-not-found", "Claude CLI was not found; showing memory-only context.");
9960
+ }
9961
+ if (normalized.includes("timed out")) {
9962
+ return new ProviderFailure("claude-cli-timeout", "Claude CLI timed out; showing memory-only context.");
9963
+ }
9964
+ return new ProviderFailure("claude-cli-error", "Claude CLI failed; showing memory-only context.");
9965
+ }
9751
9966
  function buildPrompt(statsContext, memoryContext, history, currentMessage) {
9752
9967
  const parts = [];
9753
9968
  parts.push("You are a helpful assistant that answers questions about the user's code memory data.");
@@ -9790,12 +10005,13 @@ function streamClaudeResponse(prompt, stream) {
9790
10005
  });
9791
10006
  const timeout = setTimeout(() => {
9792
10007
  proc.kill("SIGTERM");
9793
- reject(new Error("Chat response timed out after 2 minutes"));
10008
+ reject(classifyProviderFailure("timed out"));
9794
10009
  }, CLAUDE_TIMEOUT_MS);
9795
10010
  proc.stdin.write(prompt);
9796
10011
  proc.stdin.end();
9797
10012
  let buffer = "";
9798
10013
  let lastSentText = "";
10014
+ let stderrText = "";
9799
10015
  proc.stdout.on("data", async (chunk) => {
9800
10016
  buffer += chunk.toString();
9801
10017
  const lines = buffer.split("\n");
@@ -9824,6 +10040,7 @@ function streamClaudeResponse(prompt, stream) {
9824
10040
  }
9825
10041
  });
9826
10042
  proc.stderr.on("data", (chunk) => {
10043
+ stderrText += chunk.toString();
9827
10044
  if (process.env.CLAUDE_MEMORY_DEBUG) {
9828
10045
  console.error("[chat] claude stderr:", chunk.toString());
9829
10046
  }
@@ -9831,7 +10048,7 @@ function streamClaudeResponse(prompt, stream) {
9831
10048
  proc.on("error", (err) => {
9832
10049
  clearTimeout(timeout);
9833
10050
  if (err.code === "ENOENT") {
9834
- reject(new Error("Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"));
10051
+ reject(classifyProviderFailure("ENOENT not found"));
9835
10052
  } else {
9836
10053
  reject(err);
9837
10054
  }
@@ -9848,7 +10065,7 @@ function streamClaudeResponse(prompt, stream) {
9848
10065
  }
9849
10066
  }
9850
10067
  if (code !== 0 && code !== null) {
9851
- reject(new Error(`Claude CLI exited with code ${code}`));
10068
+ reject(classifyProviderFailure(stderrText || `Claude CLI exited with code ${code}`));
9852
10069
  } else {
9853
10070
  resolve4();
9854
10071
  }
@@ -9897,6 +10114,49 @@ healthRouter.get("/", async (c) => {
9897
10114
  await memoryService.shutdown();
9898
10115
  }
9899
10116
  });
10117
+ healthRouter.post("/recover", async (c) => {
10118
+ const memoryService = getWritableServiceFromQuery(c);
10119
+ try {
10120
+ await memoryService.initialize();
10121
+ const body = await c.req.json().catch(() => ({}));
10122
+ const options = {};
10123
+ if (typeof body.stuckThresholdMs === "number" && Number.isFinite(body.stuckThresholdMs)) {
10124
+ options.stuckThresholdMs = body.stuckThresholdMs;
10125
+ }
10126
+ if (typeof body.maxRetries === "number" && Number.isFinite(body.maxRetries)) {
10127
+ options.maxRetries = body.maxRetries;
10128
+ }
10129
+ const before = await memoryService.getOutboxStats();
10130
+ const recovered = await memoryService.recoverStuckOutboxItems(options);
10131
+ const [stats, outbox] = await Promise.all([
10132
+ memoryService.getStats(),
10133
+ memoryService.getOutboxStats()
10134
+ ]);
10135
+ return c.json({
10136
+ status: "ok",
10137
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10138
+ recovered,
10139
+ before: {
10140
+ outbox: before
10141
+ },
10142
+ after: {
10143
+ storage: {
10144
+ totalEvents: stats.totalEvents,
10145
+ vectorCount: stats.vectorCount
10146
+ },
10147
+ outbox
10148
+ }
10149
+ });
10150
+ } catch (error) {
10151
+ return c.json({
10152
+ status: "error",
10153
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10154
+ error: error.message
10155
+ }, 500);
10156
+ } finally {
10157
+ await memoryService.shutdown();
10158
+ }
10159
+ });
9900
10160
 
9901
10161
  // src/apps/server/api/index.ts
9902
10162
  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);