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.
- package/dist/cli/index.js +321 -54
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +68 -1
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +71 -1
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +71 -1
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +71 -1
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +71 -1
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +71 -1
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +71 -1
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +71 -1
- package/dist/index.js.map +2 -2
- package/dist/mcp/index.js +71 -1
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +312 -52
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +312 -52
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +71 -1
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/assets/js/chat.js +21 -3
- package/package.json +1 -1
package/dist/server/api/index.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
8392
|
+
body = await c.req.json();
|
|
8298
8393
|
if (!body.query) {
|
|
8299
8394
|
return c.json({ error: "Query is required" }, 400);
|
|
8300
8395
|
}
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
9684
|
-
|
|
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: "
|
|
9729
|
-
data: JSON.stringify(
|
|
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(
|
|
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(
|
|
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(
|
|
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);
|