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/cli/index.js
CHANGED
|
@@ -2329,6 +2329,14 @@ function normalizeQueryRewriteKind(value) {
|
|
|
2329
2329
|
return "none";
|
|
2330
2330
|
}
|
|
2331
2331
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
2332
|
+
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2333
|
+
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
2334
|
+
function emptyOutboxRecoveryResult() {
|
|
2335
|
+
return {
|
|
2336
|
+
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
2337
|
+
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2332
2340
|
var SQLiteEventStore = class {
|
|
2333
2341
|
db;
|
|
2334
2342
|
initialized = false;
|
|
@@ -3079,7 +3087,9 @@ var SQLiteEventStore = class {
|
|
|
3079
3087
|
const placeholders = ids.map(() => "?").join(",");
|
|
3080
3088
|
sqliteRun(
|
|
3081
3089
|
this.db,
|
|
3082
|
-
`UPDATE embedding_outbox
|
|
3090
|
+
`UPDATE embedding_outbox
|
|
3091
|
+
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
3092
|
+
WHERE id IN (${placeholders})`,
|
|
3083
3093
|
ids
|
|
3084
3094
|
);
|
|
3085
3095
|
return pending.map((row) => ({
|
|
@@ -3149,6 +3159,58 @@ var SQLiteEventStore = class {
|
|
|
3149
3159
|
[error, ...ids]
|
|
3150
3160
|
);
|
|
3151
3161
|
}
|
|
3162
|
+
/**
|
|
3163
|
+
* Recover abandoned outbox work after a worker/process crash.
|
|
3164
|
+
*
|
|
3165
|
+
* Rows in `processing` are claimed work. If the process exits before marking
|
|
3166
|
+
* them done/failed, they otherwise remain invisible to future processing.
|
|
3167
|
+
* Recovery is deliberately age-gated so an active worker is not disturbed.
|
|
3168
|
+
*/
|
|
3169
|
+
async recoverStuckOutboxItems(options = {}) {
|
|
3170
|
+
await this.initialize();
|
|
3171
|
+
const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
|
|
3172
|
+
const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
|
|
3173
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
3174
|
+
const threshold = new Date(now.getTime() - thresholdMs).toISOString();
|
|
3175
|
+
const result = emptyOutboxRecoveryResult();
|
|
3176
|
+
const embeddingRecovered = sqliteRun(
|
|
3177
|
+
this.db,
|
|
3178
|
+
`UPDATE embedding_outbox
|
|
3179
|
+
SET status = 'pending', processed_at = NULL, error_message = NULL
|
|
3180
|
+
WHERE status = 'processing'
|
|
3181
|
+
AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
|
|
3182
|
+
[threshold]
|
|
3183
|
+
);
|
|
3184
|
+
result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
|
|
3185
|
+
const embeddingRetried = sqliteRun(
|
|
3186
|
+
this.db,
|
|
3187
|
+
`UPDATE embedding_outbox
|
|
3188
|
+
SET status = 'pending', error_message = NULL
|
|
3189
|
+
WHERE status = 'failed'
|
|
3190
|
+
AND retry_count < ?`,
|
|
3191
|
+
[maxRetries]
|
|
3192
|
+
);
|
|
3193
|
+
result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
|
|
3194
|
+
const vectorRecovered = sqliteRun(
|
|
3195
|
+
this.db,
|
|
3196
|
+
`UPDATE vector_outbox
|
|
3197
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3198
|
+
WHERE status = 'processing'
|
|
3199
|
+
AND datetime(updated_at) < datetime(?)`,
|
|
3200
|
+
[now.toISOString(), threshold]
|
|
3201
|
+
);
|
|
3202
|
+
result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
|
|
3203
|
+
const vectorRetried = sqliteRun(
|
|
3204
|
+
this.db,
|
|
3205
|
+
`UPDATE vector_outbox
|
|
3206
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3207
|
+
WHERE status = 'failed'
|
|
3208
|
+
AND retry_count < ?`,
|
|
3209
|
+
[now.toISOString(), maxRetries]
|
|
3210
|
+
);
|
|
3211
|
+
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3212
|
+
return result;
|
|
3213
|
+
}
|
|
3152
3214
|
/**
|
|
3153
3215
|
* Get embedding/vector outbox health statistics
|
|
3154
3216
|
*/
|
|
@@ -4020,6 +4082,7 @@ var VectorStore = class {
|
|
|
4020
4082
|
* Get total count of vectors
|
|
4021
4083
|
*/
|
|
4022
4084
|
async count() {
|
|
4085
|
+
await this.initialize();
|
|
4023
4086
|
if (!this.table)
|
|
4024
4087
|
return 0;
|
|
4025
4088
|
const result = await this.table.countRows();
|
|
@@ -4458,6 +4521,10 @@ var MemoryQueryService = class {
|
|
|
4458
4521
|
await this.initialize();
|
|
4459
4522
|
return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
|
|
4460
4523
|
}
|
|
4524
|
+
async recoverStuckOutboxItems(options) {
|
|
4525
|
+
await this.initialize();
|
|
4526
|
+
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4527
|
+
}
|
|
4461
4528
|
async getStats() {
|
|
4462
4529
|
await this.initialize();
|
|
4463
4530
|
const deps = this.getStatsDeps();
|
|
@@ -7755,6 +7822,9 @@ var MemoryService = class {
|
|
|
7755
7822
|
async getOutboxStats() {
|
|
7756
7823
|
return this.queryService.getOutboxStats();
|
|
7757
7824
|
}
|
|
7825
|
+
async recoverStuckOutboxItems(options) {
|
|
7826
|
+
return this.queryService.recoverStuckOutboxItems(options);
|
|
7827
|
+
}
|
|
7758
7828
|
async getRetrievalTraceStats() {
|
|
7759
7829
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7760
7830
|
}
|
|
@@ -10345,6 +10415,26 @@ function getServiceFromQuery(c) {
|
|
|
10345
10415
|
}
|
|
10346
10416
|
return getReadOnlyMemoryService();
|
|
10347
10417
|
}
|
|
10418
|
+
function getWritableServiceFromQuery(c) {
|
|
10419
|
+
const project = c.req.query("project") || c.req.query("projectId");
|
|
10420
|
+
if (project) {
|
|
10421
|
+
const storagePath = resolveProjectStoragePath(project);
|
|
10422
|
+
return new MemoryService({
|
|
10423
|
+
storagePath,
|
|
10424
|
+
readOnly: false,
|
|
10425
|
+
lightweightMode: true,
|
|
10426
|
+
analyticsEnabled: false,
|
|
10427
|
+
sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10430
|
+
return new MemoryService({
|
|
10431
|
+
storagePath: "~/.claude-code/memory",
|
|
10432
|
+
readOnly: false,
|
|
10433
|
+
lightweightMode: true,
|
|
10434
|
+
analyticsEnabled: false,
|
|
10435
|
+
sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
|
|
10436
|
+
});
|
|
10437
|
+
}
|
|
10348
10438
|
function getLightweightServiceFromQuery(c) {
|
|
10349
10439
|
const project = c.req.query("project") || c.req.query("projectId");
|
|
10350
10440
|
if (project) {
|
|
@@ -10371,7 +10461,7 @@ var sessionsRouter = new Hono();
|
|
|
10371
10461
|
sessionsRouter.get("/", async (c) => {
|
|
10372
10462
|
const page = parseInt(c.req.query("page") || "1", 10);
|
|
10373
10463
|
const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
|
|
10374
|
-
const memoryService =
|
|
10464
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10375
10465
|
try {
|
|
10376
10466
|
await memoryService.initialize();
|
|
10377
10467
|
const recentEvents = await memoryService.getRecentEvents(1e3);
|
|
@@ -10415,7 +10505,7 @@ sessionsRouter.get("/", async (c) => {
|
|
|
10415
10505
|
});
|
|
10416
10506
|
sessionsRouter.get("/:id", async (c) => {
|
|
10417
10507
|
const { id } = c.req.param();
|
|
10418
|
-
const memoryService =
|
|
10508
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10419
10509
|
try {
|
|
10420
10510
|
await memoryService.initialize();
|
|
10421
10511
|
const events = await memoryService.getSessionHistory(id);
|
|
@@ -10461,7 +10551,7 @@ eventsRouter.get("/", async (c) => {
|
|
|
10461
10551
|
const q = (c.req.query("q") || "").trim().toLowerCase();
|
|
10462
10552
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
10463
10553
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
10464
|
-
const memoryService =
|
|
10554
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10465
10555
|
try {
|
|
10466
10556
|
await memoryService.initialize();
|
|
10467
10557
|
let events;
|
|
@@ -10517,7 +10607,7 @@ eventsRouter.get("/", async (c) => {
|
|
|
10517
10607
|
});
|
|
10518
10608
|
eventsRouter.get("/:id", async (c) => {
|
|
10519
10609
|
const { id } = c.req.param();
|
|
10520
|
-
const memoryService =
|
|
10610
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10521
10611
|
try {
|
|
10522
10612
|
await memoryService.initialize();
|
|
10523
10613
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
@@ -10556,6 +10646,10 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
10556
10646
|
// src/apps/server/api/search.ts
|
|
10557
10647
|
import { Hono as Hono3 } from "hono";
|
|
10558
10648
|
var searchRouter = new Hono3();
|
|
10649
|
+
function isEmbeddingBackendUnavailable(error) {
|
|
10650
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10651
|
+
return /model file path or buffer|onnxruntime|transformers|embedding backend/i.test(message);
|
|
10652
|
+
}
|
|
10559
10653
|
searchRouter.post("/", async (c) => {
|
|
10560
10654
|
const memoryService = getServiceFromQuery(c);
|
|
10561
10655
|
try {
|
|
@@ -10597,15 +10691,41 @@ searchRouter.post("/", async (c) => {
|
|
|
10597
10691
|
});
|
|
10598
10692
|
searchRouter.post("/disclosure", async (c) => {
|
|
10599
10693
|
let memoryService;
|
|
10694
|
+
let body;
|
|
10600
10695
|
try {
|
|
10601
|
-
|
|
10696
|
+
body = await c.req.json();
|
|
10602
10697
|
if (!body.query) {
|
|
10603
10698
|
return c.json({ error: "Query is required" }, 400);
|
|
10604
10699
|
}
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10700
|
+
const useFastStrategy = body.options?.strategy === "fast";
|
|
10701
|
+
memoryService = useFastStrategy ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
|
|
10702
|
+
try {
|
|
10703
|
+
await memoryService.initialize();
|
|
10704
|
+
const result = await memoryService.searchDisclosure(body.query, body.options);
|
|
10705
|
+
return c.json(result);
|
|
10706
|
+
} catch (error) {
|
|
10707
|
+
if (!useFastStrategy && isEmbeddingBackendUnavailable(error)) {
|
|
10708
|
+
await memoryService.shutdown();
|
|
10709
|
+
memoryService = getLightweightServiceFromQuery(c);
|
|
10710
|
+
await memoryService.initialize();
|
|
10711
|
+
const result = await memoryService.searchDisclosure(body.query, {
|
|
10712
|
+
...body.options,
|
|
10713
|
+
strategy: "fast"
|
|
10714
|
+
});
|
|
10715
|
+
return c.json({
|
|
10716
|
+
...result,
|
|
10717
|
+
meta: {
|
|
10718
|
+
...result.meta,
|
|
10719
|
+
fallbackApplied: true,
|
|
10720
|
+
fallbackTrace: [
|
|
10721
|
+
...result.meta.fallbackTrace || [],
|
|
10722
|
+
"fallback:embedding-backend-unavailable:fast"
|
|
10723
|
+
]
|
|
10724
|
+
}
|
|
10725
|
+
});
|
|
10726
|
+
}
|
|
10727
|
+
throw error;
|
|
10728
|
+
}
|
|
10609
10729
|
} catch (error) {
|
|
10610
10730
|
return c.json({ error: error.message }, 500);
|
|
10611
10731
|
} finally {
|
|
@@ -10613,7 +10733,7 @@ searchRouter.post("/disclosure", async (c) => {
|
|
|
10613
10733
|
}
|
|
10614
10734
|
});
|
|
10615
10735
|
searchRouter.get("/disclosure/:resultId/expand", async (c) => {
|
|
10616
|
-
const memoryService =
|
|
10736
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10617
10737
|
try {
|
|
10618
10738
|
const resultId = c.req.param("resultId");
|
|
10619
10739
|
const rawWindowSize = c.req.query("windowSize");
|
|
@@ -10633,7 +10753,7 @@ searchRouter.get("/disclosure/:resultId/expand", async (c) => {
|
|
|
10633
10753
|
}
|
|
10634
10754
|
});
|
|
10635
10755
|
searchRouter.get("/disclosure/:resultId/source", async (c) => {
|
|
10636
|
-
const memoryService =
|
|
10756
|
+
const memoryService = getLightweightServiceFromQuery(c);
|
|
10637
10757
|
try {
|
|
10638
10758
|
const resultId = c.req.param("resultId");
|
|
10639
10759
|
const result = await memoryService.sourceDisclosure(resultId);
|
|
@@ -11970,6 +12090,13 @@ import { Hono as Hono8 } from "hono";
|
|
|
11970
12090
|
import { streamSSE } from "hono/streaming";
|
|
11971
12091
|
import { spawn } from "child_process";
|
|
11972
12092
|
var chatRouter = new Hono8();
|
|
12093
|
+
var ProviderFailure = class extends Error {
|
|
12094
|
+
constructor(code, message) {
|
|
12095
|
+
super(message);
|
|
12096
|
+
this.code = code;
|
|
12097
|
+
this.name = "ProviderFailure";
|
|
12098
|
+
}
|
|
12099
|
+
};
|
|
11973
12100
|
var CLAUDE_TIMEOUT_MS = 12e4;
|
|
11974
12101
|
chatRouter.post("/", async (c) => {
|
|
11975
12102
|
let body;
|
|
@@ -11981,43 +12108,12 @@ chatRouter.post("/", async (c) => {
|
|
|
11981
12108
|
if (!body.message?.trim()) {
|
|
11982
12109
|
return c.json({ error: "Message is required" }, 400);
|
|
11983
12110
|
}
|
|
11984
|
-
const
|
|
12111
|
+
const memoryOnly = body.mode === "memory-only" || body.memoryOnly === true;
|
|
12112
|
+
const memoryService = memoryOnly ? getLightweightServiceFromQuery(c) : getServiceFromQuery(c);
|
|
11985
12113
|
try {
|
|
11986
12114
|
await memoryService.initialize();
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
try {
|
|
11990
|
-
const result = await memoryService.retrieveMemories(body.message, {
|
|
11991
|
-
topK: 8,
|
|
11992
|
-
minScore: 0.5
|
|
11993
|
-
});
|
|
11994
|
-
if (result.memories.length > 0) {
|
|
11995
|
-
const parts = ["## Relevant Memories\n"];
|
|
11996
|
-
for (const m of result.memories) {
|
|
11997
|
-
const date = new Date(m.event.timestamp).toISOString().split("T")[0];
|
|
11998
|
-
const content = m.event.content.slice(0, 500);
|
|
11999
|
-
parts.push(`### [${m.event.eventType}] ${date} (score: ${m.score.toFixed(2)})`);
|
|
12000
|
-
parts.push(content);
|
|
12001
|
-
if (m.sessionContext) {
|
|
12002
|
-
parts.push(`_Context: ${m.sessionContext}_`);
|
|
12003
|
-
}
|
|
12004
|
-
parts.push("");
|
|
12005
|
-
}
|
|
12006
|
-
memoryContext = parts.join("\n");
|
|
12007
|
-
}
|
|
12008
|
-
} catch {
|
|
12009
|
-
}
|
|
12010
|
-
try {
|
|
12011
|
-
const stats = await memoryService.getStats();
|
|
12012
|
-
const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
|
|
12013
|
-
statsContext = [
|
|
12014
|
-
"## Memory Stats",
|
|
12015
|
-
`- Total events: ${stats.totalEvents}`,
|
|
12016
|
-
`- Vector nodes: ${stats.vectorCount}`,
|
|
12017
|
-
`- By level: ${levels}`
|
|
12018
|
-
].join("\n");
|
|
12019
|
-
} catch {
|
|
12020
|
-
}
|
|
12115
|
+
const { memoryContext, memoryHits } = await collectMemoryContext(memoryService, body.message);
|
|
12116
|
+
const statsContext = await collectStatsContext(memoryService);
|
|
12021
12117
|
const fullPrompt = buildPrompt(
|
|
12022
12118
|
statsContext,
|
|
12023
12119
|
memoryContext,
|
|
@@ -12025,13 +12121,23 @@ chatRouter.post("/", async (c) => {
|
|
|
12025
12121
|
body.message
|
|
12026
12122
|
);
|
|
12027
12123
|
return streamSSE(c, async (stream) => {
|
|
12124
|
+
if (memoryOnly) {
|
|
12125
|
+
await streamMemoryOnlyResponse(stream, {
|
|
12126
|
+
memoryContext,
|
|
12127
|
+
memoryHits,
|
|
12128
|
+
reason: "memory-only-mode"
|
|
12129
|
+
});
|
|
12130
|
+
return;
|
|
12131
|
+
}
|
|
12028
12132
|
try {
|
|
12029
12133
|
await streamClaudeResponse(fullPrompt, stream);
|
|
12030
12134
|
} catch (err) {
|
|
12135
|
+
const diagnostic = providerDiagnostic(err);
|
|
12031
12136
|
await stream.writeSSE({
|
|
12032
|
-
event: "
|
|
12033
|
-
data: JSON.stringify(
|
|
12137
|
+
event: "provider_error",
|
|
12138
|
+
data: JSON.stringify(diagnostic)
|
|
12034
12139
|
});
|
|
12140
|
+
await streamMemoryOnlyFallback(stream, memoryContext, memoryHits);
|
|
12035
12141
|
}
|
|
12036
12142
|
});
|
|
12037
12143
|
} catch (error) {
|
|
@@ -12040,6 +12146,115 @@ chatRouter.post("/", async (c) => {
|
|
|
12040
12146
|
await memoryService.shutdown();
|
|
12041
12147
|
}
|
|
12042
12148
|
});
|
|
12149
|
+
async function collectMemoryContext(memoryService, query) {
|
|
12150
|
+
let memoryHits = [];
|
|
12151
|
+
try {
|
|
12152
|
+
const result = await memoryService.retrieveMemories?.(query, {
|
|
12153
|
+
topK: 8,
|
|
12154
|
+
minScore: 0.5
|
|
12155
|
+
});
|
|
12156
|
+
memoryHits = result?.memories ?? [];
|
|
12157
|
+
} catch {
|
|
12158
|
+
memoryHits = [];
|
|
12159
|
+
}
|
|
12160
|
+
if (memoryHits.length === 0) {
|
|
12161
|
+
try {
|
|
12162
|
+
memoryHits = await memoryService.keywordSearch?.(query, { topK: 8, minScore: 0.05 }) ?? [];
|
|
12163
|
+
} catch {
|
|
12164
|
+
memoryHits = [];
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
return {
|
|
12168
|
+
memoryContext: formatMemoryContext(memoryHits),
|
|
12169
|
+
memoryHits
|
|
12170
|
+
};
|
|
12171
|
+
}
|
|
12172
|
+
async function collectStatsContext(memoryService) {
|
|
12173
|
+
try {
|
|
12174
|
+
const stats = await memoryService.getStats?.();
|
|
12175
|
+
if (!stats)
|
|
12176
|
+
return "";
|
|
12177
|
+
const levels = stats.levelStats.map((l) => `${l.level}: ${l.count}`).join(", ");
|
|
12178
|
+
return [
|
|
12179
|
+
"## Memory Stats",
|
|
12180
|
+
`- Total events: ${stats.totalEvents}`,
|
|
12181
|
+
`- Vector nodes: ${stats.vectorCount}`,
|
|
12182
|
+
`- By level: ${levels}`
|
|
12183
|
+
].join("\n");
|
|
12184
|
+
} catch {
|
|
12185
|
+
return "";
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
function formatMemoryContext(memoryHits) {
|
|
12189
|
+
if (memoryHits.length === 0)
|
|
12190
|
+
return "";
|
|
12191
|
+
const parts = ["## Relevant Memories\n"];
|
|
12192
|
+
for (const m of memoryHits) {
|
|
12193
|
+
const date = m.event.timestamp ? new Date(m.event.timestamp).toISOString().split("T")[0] : "unknown-date";
|
|
12194
|
+
const content = (m.event.content ?? "").slice(0, 500);
|
|
12195
|
+
parts.push(`### [${m.event.eventType ?? "memory"}] ${date} (score: ${m.score.toFixed(2)})`);
|
|
12196
|
+
parts.push(content);
|
|
12197
|
+
if (m.sessionContext) {
|
|
12198
|
+
parts.push(`_Context: ${m.sessionContext}_`);
|
|
12199
|
+
}
|
|
12200
|
+
parts.push("");
|
|
12201
|
+
}
|
|
12202
|
+
return parts.join("\n");
|
|
12203
|
+
}
|
|
12204
|
+
async function streamMemoryOnlyResponse(stream, options) {
|
|
12205
|
+
await stream.writeSSE({
|
|
12206
|
+
event: "diagnostic",
|
|
12207
|
+
data: JSON.stringify({
|
|
12208
|
+
provider: "claude-cli",
|
|
12209
|
+
status: "skipped",
|
|
12210
|
+
mode: "memory-only",
|
|
12211
|
+
reason: options.reason,
|
|
12212
|
+
retrievedMemories: options.memoryHits.length
|
|
12213
|
+
})
|
|
12214
|
+
});
|
|
12215
|
+
await streamMemoryOnlyFallback(stream, options.memoryContext, options.memoryHits);
|
|
12216
|
+
}
|
|
12217
|
+
async function streamMemoryOnlyFallback(stream, memoryContext, memoryHits) {
|
|
12218
|
+
const content = memoryHits.length > 0 ? [
|
|
12219
|
+
"Provider unavailable or skipped; showing retrieved memory context directly.",
|
|
12220
|
+
"",
|
|
12221
|
+
memoryContext
|
|
12222
|
+
].join("\n") : "Provider unavailable or skipped, and no directly relevant memories were found for this query.";
|
|
12223
|
+
await stream.writeSSE({
|
|
12224
|
+
event: "message",
|
|
12225
|
+
data: JSON.stringify({ content, mode: "memory-only" })
|
|
12226
|
+
});
|
|
12227
|
+
await stream.writeSSE({ event: "done", data: "{}" });
|
|
12228
|
+
}
|
|
12229
|
+
function providerDiagnostic(err) {
|
|
12230
|
+
if (err instanceof ProviderFailure) {
|
|
12231
|
+
return {
|
|
12232
|
+
provider: "claude-cli",
|
|
12233
|
+
code: err.code,
|
|
12234
|
+
message: err.message,
|
|
12235
|
+
fallback: "memory-only"
|
|
12236
|
+
};
|
|
12237
|
+
}
|
|
12238
|
+
return {
|
|
12239
|
+
provider: "claude-cli",
|
|
12240
|
+
code: "claude-cli-error",
|
|
12241
|
+
message: err instanceof Error ? err.message : "Unknown Claude CLI failure",
|
|
12242
|
+
fallback: "memory-only"
|
|
12243
|
+
};
|
|
12244
|
+
}
|
|
12245
|
+
function classifyProviderFailure(message) {
|
|
12246
|
+
const normalized = message.toLowerCase();
|
|
12247
|
+
if (normalized.includes("401") || normalized.includes("unauthorized") || normalized.includes("auth")) {
|
|
12248
|
+
return new ProviderFailure("claude-cli-auth", "Claude CLI authentication failed; showing memory-only context.");
|
|
12249
|
+
}
|
|
12250
|
+
if (normalized.includes("not found") || normalized.includes("enoent")) {
|
|
12251
|
+
return new ProviderFailure("claude-cli-not-found", "Claude CLI was not found; showing memory-only context.");
|
|
12252
|
+
}
|
|
12253
|
+
if (normalized.includes("timed out")) {
|
|
12254
|
+
return new ProviderFailure("claude-cli-timeout", "Claude CLI timed out; showing memory-only context.");
|
|
12255
|
+
}
|
|
12256
|
+
return new ProviderFailure("claude-cli-error", "Claude CLI failed; showing memory-only context.");
|
|
12257
|
+
}
|
|
12043
12258
|
function buildPrompt(statsContext, memoryContext, history, currentMessage) {
|
|
12044
12259
|
const parts = [];
|
|
12045
12260
|
parts.push("You are a helpful assistant that answers questions about the user's code memory data.");
|
|
@@ -12082,12 +12297,13 @@ function streamClaudeResponse(prompt, stream) {
|
|
|
12082
12297
|
});
|
|
12083
12298
|
const timeout = setTimeout(() => {
|
|
12084
12299
|
proc.kill("SIGTERM");
|
|
12085
|
-
reject(
|
|
12300
|
+
reject(classifyProviderFailure("timed out"));
|
|
12086
12301
|
}, CLAUDE_TIMEOUT_MS);
|
|
12087
12302
|
proc.stdin.write(prompt);
|
|
12088
12303
|
proc.stdin.end();
|
|
12089
12304
|
let buffer = "";
|
|
12090
12305
|
let lastSentText = "";
|
|
12306
|
+
let stderrText = "";
|
|
12091
12307
|
proc.stdout.on("data", async (chunk) => {
|
|
12092
12308
|
buffer += chunk.toString();
|
|
12093
12309
|
const lines = buffer.split("\n");
|
|
@@ -12116,6 +12332,7 @@ function streamClaudeResponse(prompt, stream) {
|
|
|
12116
12332
|
}
|
|
12117
12333
|
});
|
|
12118
12334
|
proc.stderr.on("data", (chunk) => {
|
|
12335
|
+
stderrText += chunk.toString();
|
|
12119
12336
|
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
12120
12337
|
console.error("[chat] claude stderr:", chunk.toString());
|
|
12121
12338
|
}
|
|
@@ -12123,7 +12340,7 @@ function streamClaudeResponse(prompt, stream) {
|
|
|
12123
12340
|
proc.on("error", (err) => {
|
|
12124
12341
|
clearTimeout(timeout);
|
|
12125
12342
|
if (err.code === "ENOENT") {
|
|
12126
|
-
reject(
|
|
12343
|
+
reject(classifyProviderFailure("ENOENT not found"));
|
|
12127
12344
|
} else {
|
|
12128
12345
|
reject(err);
|
|
12129
12346
|
}
|
|
@@ -12140,7 +12357,7 @@ function streamClaudeResponse(prompt, stream) {
|
|
|
12140
12357
|
}
|
|
12141
12358
|
}
|
|
12142
12359
|
if (code !== 0 && code !== null) {
|
|
12143
|
-
reject(
|
|
12360
|
+
reject(classifyProviderFailure(stderrText || `Claude CLI exited with code ${code}`));
|
|
12144
12361
|
} else {
|
|
12145
12362
|
resolve8();
|
|
12146
12363
|
}
|
|
@@ -12189,6 +12406,49 @@ healthRouter.get("/", async (c) => {
|
|
|
12189
12406
|
await memoryService.shutdown();
|
|
12190
12407
|
}
|
|
12191
12408
|
});
|
|
12409
|
+
healthRouter.post("/recover", async (c) => {
|
|
12410
|
+
const memoryService = getWritableServiceFromQuery(c);
|
|
12411
|
+
try {
|
|
12412
|
+
await memoryService.initialize();
|
|
12413
|
+
const body = await c.req.json().catch(() => ({}));
|
|
12414
|
+
const options = {};
|
|
12415
|
+
if (typeof body.stuckThresholdMs === "number" && Number.isFinite(body.stuckThresholdMs)) {
|
|
12416
|
+
options.stuckThresholdMs = body.stuckThresholdMs;
|
|
12417
|
+
}
|
|
12418
|
+
if (typeof body.maxRetries === "number" && Number.isFinite(body.maxRetries)) {
|
|
12419
|
+
options.maxRetries = body.maxRetries;
|
|
12420
|
+
}
|
|
12421
|
+
const before = await memoryService.getOutboxStats();
|
|
12422
|
+
const recovered = await memoryService.recoverStuckOutboxItems(options);
|
|
12423
|
+
const [stats, outbox] = await Promise.all([
|
|
12424
|
+
memoryService.getStats(),
|
|
12425
|
+
memoryService.getOutboxStats()
|
|
12426
|
+
]);
|
|
12427
|
+
return c.json({
|
|
12428
|
+
status: "ok",
|
|
12429
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12430
|
+
recovered,
|
|
12431
|
+
before: {
|
|
12432
|
+
outbox: before
|
|
12433
|
+
},
|
|
12434
|
+
after: {
|
|
12435
|
+
storage: {
|
|
12436
|
+
totalEvents: stats.totalEvents,
|
|
12437
|
+
vectorCount: stats.vectorCount
|
|
12438
|
+
},
|
|
12439
|
+
outbox
|
|
12440
|
+
}
|
|
12441
|
+
});
|
|
12442
|
+
} catch (error) {
|
|
12443
|
+
return c.json({
|
|
12444
|
+
status: "error",
|
|
12445
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12446
|
+
error: error.message
|
|
12447
|
+
}, 500);
|
|
12448
|
+
} finally {
|
|
12449
|
+
await memoryService.shutdown();
|
|
12450
|
+
}
|
|
12451
|
+
});
|
|
12192
12452
|
|
|
12193
12453
|
// src/apps/server/api/index.ts
|
|
12194
12454
|
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);
|
|
@@ -13982,7 +14242,7 @@ async function runMarketContextCommand(options) {
|
|
|
13982
14242
|
}
|
|
13983
14243
|
}
|
|
13984
14244
|
var program = new Command();
|
|
13985
|
-
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.
|
|
14245
|
+
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.37");
|
|
13986
14246
|
program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
|
|
13987
14247
|
try {
|
|
13988
14248
|
await runMarketContextCommand(options);
|
|
@@ -14217,11 +14477,18 @@ program.command("forget [eventId]").description("Remove memories from storage").
|
|
|
14217
14477
|
process.exit(1);
|
|
14218
14478
|
}
|
|
14219
14479
|
});
|
|
14220
|
-
program.command("process").description("Process pending embeddings").option("-p, --project <path>", "Project path (defaults to cwd)").action(async (options) => {
|
|
14480
|
+
program.command("process").description("Process pending embeddings").option("-p, --project <path>", "Project path (defaults to cwd)").option("--no-recover-stuck", "Skip stale processing outbox recovery before processing").action(async (options) => {
|
|
14221
14481
|
const projectPath = options.project || process.cwd();
|
|
14222
14482
|
const service = getMemoryServiceForProject(projectPath);
|
|
14223
14483
|
try {
|
|
14224
14484
|
await service.initialize();
|
|
14485
|
+
if (options.recoverStuck !== false) {
|
|
14486
|
+
const recovered = await service.recoverStuckOutboxItems();
|
|
14487
|
+
const recoveredCount = recovered.embedding.recoveredProcessing + recovered.embedding.retriedFailed + recovered.vector.recoveredProcessing + recovered.vector.retriedFailed;
|
|
14488
|
+
if (recoveredCount > 0) {
|
|
14489
|
+
console.log(`\u267B\uFE0F Recovered stuck outbox work: embedding=${recovered.embedding.recoveredProcessing}/${recovered.embedding.retriedFailed}, vector=${recovered.vector.recoveredProcessing}/${recovered.vector.retriedFailed}`);
|
|
14490
|
+
}
|
|
14491
|
+
}
|
|
14225
14492
|
console.log("\u23F3 Processing pending embeddings...");
|
|
14226
14493
|
const count = await service.processPendingEmbeddings();
|
|
14227
14494
|
console.log(`\u2705 Processed ${count} embeddings`);
|