@agentmemory/agentmemory 0.9.19 → 0.9.21
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/.env.example +2 -0
- package/README.md +16 -5
- package/dist/.env.example +2 -0
- package/dist/cli.mjs +24 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +417 -24
- package/dist/index.mjs.map +1 -1
- package/dist/{src-2wwYDPGA.mjs → src-D5arboxc.mjs} +415 -25
- package/dist/src-D5arboxc.mjs.map +1 -0
- package/dist/{standalone-DMLk7YxP.mjs → standalone-C7BgzzIN.mjs} +32 -8
- package/dist/standalone-C7BgzzIN.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +31 -7
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-Dz8ssuMf.mjs → tools-registry-CRTWUFw9.mjs} +5 -2
- package/dist/tools-registry-CRTWUFw9.mjs.map +1 -0
- package/dist/viewer/index.html +57 -12
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.codex.json +6 -10
- package/plugin/hooks/hooks.json +12 -12
- package/plugin/opencode/README.md +229 -0
- package/plugin/opencode/agentmemory-capture.ts +662 -0
- package/plugin/opencode/commands/recall.md +19 -0
- package/plugin/opencode/commands/remember.md +19 -0
- package/plugin/opencode/plugin.json +12 -0
- package/dist/src-2wwYDPGA.mjs.map +0 -1
- package/dist/standalone-DMLk7YxP.mjs.map +0 -1
- package/dist/tools-registry-Dz8ssuMf.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -137,6 +137,9 @@ function getMergedEnv(overrides) {
|
|
|
137
137
|
function getEnvVar(key) {
|
|
138
138
|
return getMergedEnv()[key];
|
|
139
139
|
}
|
|
140
|
+
function isDropStaleIndexEnabled() {
|
|
141
|
+
return getMergedEnv()["AGENTMEMORY_DROP_STALE_INDEX"] === "true";
|
|
142
|
+
}
|
|
140
143
|
function detectLlmProviderKind() {
|
|
141
144
|
const env = getMergedEnv();
|
|
142
145
|
if (hasRealValue(env["ANTHROPIC_API_KEY"]) || hasRealValue(env["GEMINI_API_KEY"]) || hasRealValue(env["GOOGLE_API_KEY"]) || hasRealValue(env["OPENROUTER_API_KEY"]) || hasRealValue(env["MINIMAX_API_KEY"]) || hasRealValue(env["OPENAI_API_KEY"]) && env["OPENAI_API_KEY_FOR_LLM"] !== "false") return "llm";
|
|
@@ -2985,20 +2988,108 @@ async function vectorIndexAddGuarded(id, sessionId, text, context) {
|
|
|
2985
2988
|
return false;
|
|
2986
2989
|
}
|
|
2987
2990
|
}
|
|
2991
|
+
async function vectorIndexAddBatchGuarded(items) {
|
|
2992
|
+
const vi = vectorIndex;
|
|
2993
|
+
const ep = currentEmbeddingProvider;
|
|
2994
|
+
if (!vi || !ep || items.length === 0) return {
|
|
2995
|
+
ok: 0,
|
|
2996
|
+
fail: 0
|
|
2997
|
+
};
|
|
2998
|
+
let embeddings;
|
|
2999
|
+
try {
|
|
3000
|
+
embeddings = await ep.embedBatch(items.map((i) => clipEmbedInput(i.text)));
|
|
3001
|
+
} catch (err) {
|
|
3002
|
+
logger.warn("vector-index add batch: embed failed — skipping batch", {
|
|
3003
|
+
batchSize: items.length,
|
|
3004
|
+
provider: ep.name,
|
|
3005
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3006
|
+
});
|
|
3007
|
+
return {
|
|
3008
|
+
ok: 0,
|
|
3009
|
+
fail: items.length
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
if (embeddings.length !== items.length) {
|
|
3013
|
+
logger.warn("vector-index add batch: provider returned wrong length — skipping batch", {
|
|
3014
|
+
batchSize: items.length,
|
|
3015
|
+
returned: embeddings.length,
|
|
3016
|
+
provider: ep.name
|
|
3017
|
+
});
|
|
3018
|
+
return {
|
|
3019
|
+
ok: 0,
|
|
3020
|
+
fail: items.length
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
let ok = 0;
|
|
3024
|
+
let fail = 0;
|
|
3025
|
+
for (let i = 0; i < items.length; i++) {
|
|
3026
|
+
const item = items[i];
|
|
3027
|
+
const embedding = embeddings[i];
|
|
3028
|
+
if (embedding.length !== ep.dimensions) {
|
|
3029
|
+
logger.warn("vector-index add batch: dimension mismatch — skipping item", {
|
|
3030
|
+
kind: item.context.kind,
|
|
3031
|
+
id: item.context.logId,
|
|
3032
|
+
provider: ep.name,
|
|
3033
|
+
expected: ep.dimensions,
|
|
3034
|
+
received: embedding.length
|
|
3035
|
+
});
|
|
3036
|
+
fail++;
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
try {
|
|
3040
|
+
vi.add(item.id, item.sessionId, embedding);
|
|
3041
|
+
ok++;
|
|
3042
|
+
} catch (err) {
|
|
3043
|
+
logger.warn("vector-index add batch: index write failed — skipping item", {
|
|
3044
|
+
kind: item.context.kind,
|
|
3045
|
+
id: item.context.logId,
|
|
3046
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3047
|
+
});
|
|
3048
|
+
fail++;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
return {
|
|
3052
|
+
ok,
|
|
3053
|
+
fail
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
const DEFAULT_REBUILD_EMBED_BATCH = 32;
|
|
3057
|
+
function getRebuildEmbedBatchSize() {
|
|
3058
|
+
const raw = process.env.REBUILD_EMBED_BATCH_SIZE;
|
|
3059
|
+
if (!raw) return DEFAULT_REBUILD_EMBED_BATCH;
|
|
3060
|
+
const n = parseInt(raw, 10);
|
|
3061
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_REBUILD_EMBED_BATCH;
|
|
3062
|
+
}
|
|
2988
3063
|
async function rebuildIndex(kv) {
|
|
2989
3064
|
const idx = getSearchIndex();
|
|
2990
3065
|
idx.clear();
|
|
2991
3066
|
vectorIndex?.clear();
|
|
3067
|
+
const batchSize = getRebuildEmbedBatchSize();
|
|
3068
|
+
const pending = [];
|
|
2992
3069
|
let count = 0;
|
|
3070
|
+
const flush = async () => {
|
|
3071
|
+
if (pending.length === 0) return;
|
|
3072
|
+
await vectorIndexAddBatchGuarded(pending);
|
|
3073
|
+
pending.length = 0;
|
|
3074
|
+
};
|
|
3075
|
+
const enqueue = async (job) => {
|
|
3076
|
+
pending.push(job);
|
|
3077
|
+
if (pending.length >= batchSize) await flush();
|
|
3078
|
+
};
|
|
2993
3079
|
try {
|
|
2994
3080
|
const memories = await kv.list(KV.memories);
|
|
2995
3081
|
for (const memory of memories) {
|
|
2996
3082
|
if (memory.isLatest === false) continue;
|
|
2997
3083
|
if (!memory.title || !memory.content) continue;
|
|
2998
3084
|
idx.add(memoryToObservation(memory));
|
|
2999
|
-
await
|
|
3000
|
-
|
|
3001
|
-
|
|
3085
|
+
await enqueue({
|
|
3086
|
+
id: memory.id,
|
|
3087
|
+
sessionId: memory.sessionIds[0] ?? "memory",
|
|
3088
|
+
text: memory.title + " " + memory.content,
|
|
3089
|
+
context: {
|
|
3090
|
+
kind: "memory",
|
|
3091
|
+
logId: memory.id
|
|
3092
|
+
}
|
|
3002
3093
|
});
|
|
3003
3094
|
count++;
|
|
3004
3095
|
}
|
|
@@ -3006,7 +3097,10 @@ async function rebuildIndex(kv) {
|
|
|
3006
3097
|
logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
|
|
3007
3098
|
}
|
|
3008
3099
|
const sessions = await kv.list(KV.sessions);
|
|
3009
|
-
if (!sessions.length)
|
|
3100
|
+
if (!sessions.length) {
|
|
3101
|
+
await flush();
|
|
3102
|
+
return count;
|
|
3103
|
+
}
|
|
3010
3104
|
const obsPerSession = [];
|
|
3011
3105
|
const failedSessions = [];
|
|
3012
3106
|
for (let batch = 0; batch < sessions.length; batch += 10) {
|
|
@@ -3024,12 +3118,18 @@ async function rebuildIndex(kv) {
|
|
|
3024
3118
|
if (failedSessions.length > 0) logger.warn("rebuildIndex: failed to load observations for sessions", { failedSessions });
|
|
3025
3119
|
for (const observations of obsPerSession) for (const obs of observations) if (obs.title && obs.narrative) {
|
|
3026
3120
|
idx.add(obs);
|
|
3027
|
-
await
|
|
3028
|
-
|
|
3029
|
-
|
|
3121
|
+
await enqueue({
|
|
3122
|
+
id: obs.id,
|
|
3123
|
+
sessionId: obs.sessionId,
|
|
3124
|
+
text: obs.title + " " + obs.narrative,
|
|
3125
|
+
context: {
|
|
3126
|
+
kind: "observation",
|
|
3127
|
+
logId: obs.id
|
|
3128
|
+
}
|
|
3030
3129
|
});
|
|
3031
3130
|
count++;
|
|
3032
3131
|
}
|
|
3132
|
+
await flush();
|
|
3033
3133
|
return count;
|
|
3034
3134
|
}
|
|
3035
3135
|
function registerSearchFunction(sdk, kv) {
|
|
@@ -4814,9 +4914,134 @@ function buildSummaryPrompt(observations) {
|
|
|
4814
4914
|
});
|
|
4815
4915
|
return `Session observations (${observations.length} total):\n\n${lines.join("\n\n---\n\n")}`;
|
|
4816
4916
|
}
|
|
4917
|
+
const REDUCE_SYSTEM = `You are merging multiple partial summaries of the SAME coding session into one final session summary. The partials are chronological chunks of one continuous session — not separate sessions.
|
|
4918
|
+
|
|
4919
|
+
Output EXACTLY this XML format with no additional text:
|
|
4920
|
+
|
|
4921
|
+
<summary>
|
|
4922
|
+
<title>Short session title (max 100 chars)</title>
|
|
4923
|
+
<narrative>3-5 sentence narrative covering the whole session</narrative>
|
|
4924
|
+
<decisions>
|
|
4925
|
+
<decision>Key technical decision made</decision>
|
|
4926
|
+
</decisions>
|
|
4927
|
+
<files>
|
|
4928
|
+
<file>path/to/modified/file</file>
|
|
4929
|
+
</files>
|
|
4930
|
+
<concepts>
|
|
4931
|
+
<concept>key concept from session</concept>
|
|
4932
|
+
</concepts>
|
|
4933
|
+
</summary>
|
|
4934
|
+
|
|
4935
|
+
Rules:
|
|
4936
|
+
- Synthesize a single narrative that reflects the whole arc, not a chunk-by-chunk recap
|
|
4937
|
+
- Preserve every distinct decision across chunks
|
|
4938
|
+
- Union (deduplicate) all files and concepts
|
|
4939
|
+
- Title should capture the session's overall outcome`;
|
|
4940
|
+
function buildReducePrompt(partials) {
|
|
4941
|
+
const sections = partials.map((p, i) => {
|
|
4942
|
+
const decisions = p.keyDecisions.map((d) => ` - ${d}`).join("\n");
|
|
4943
|
+
const files = p.filesModified.map((f) => ` - ${f}`).join("\n");
|
|
4944
|
+
const concepts = p.concepts.join(", ");
|
|
4945
|
+
return `[Chunk ${i + 1} of ${partials.length} — obs ${p.obsRangeStart}-${p.obsRangeEnd}]
|
|
4946
|
+
Title: ${p.title}
|
|
4947
|
+
Narrative: ${p.narrative}
|
|
4948
|
+
Decisions:
|
|
4949
|
+
${decisions}
|
|
4950
|
+
Files:
|
|
4951
|
+
${files}
|
|
4952
|
+
Concepts: ${concepts}`;
|
|
4953
|
+
});
|
|
4954
|
+
return `Partial summaries (${partials.length} chunks of one session, chronological):\n\n${sections.join("\n\n---\n\n")}`;
|
|
4955
|
+
}
|
|
4817
4956
|
|
|
4818
4957
|
//#endregion
|
|
4819
4958
|
//#region src/functions/summarize.ts
|
|
4959
|
+
const CHUNK_SIZE_DEFAULT = 400;
|
|
4960
|
+
const CHUNK_CONCURRENCY_DEFAULT = 6;
|
|
4961
|
+
const MAX_SKIP_RATIO = .5;
|
|
4962
|
+
function getChunkSize() {
|
|
4963
|
+
const raw = process.env.SUMMARIZE_CHUNK_SIZE;
|
|
4964
|
+
if (!raw) return CHUNK_SIZE_DEFAULT;
|
|
4965
|
+
const n = parseInt(raw, 10);
|
|
4966
|
+
return Number.isFinite(n) && n > 0 ? n : CHUNK_SIZE_DEFAULT;
|
|
4967
|
+
}
|
|
4968
|
+
function getChunkConcurrency() {
|
|
4969
|
+
const raw = process.env.SUMMARIZE_CHUNK_CONCURRENCY;
|
|
4970
|
+
if (!raw) return CHUNK_CONCURRENCY_DEFAULT;
|
|
4971
|
+
const n = parseInt(raw, 10);
|
|
4972
|
+
return Number.isFinite(n) && n > 0 ? n : CHUNK_CONCURRENCY_DEFAULT;
|
|
4973
|
+
}
|
|
4974
|
+
async function summarizeChunkWithRetry(provider, chunk, sessionId, project, idx, total) {
|
|
4975
|
+
for (let attempt = 1; attempt <= 2; attempt++) try {
|
|
4976
|
+
const parsed = parseSummaryXml(await provider.summarize(SUMMARY_SYSTEM, buildSummaryPrompt(chunk)), sessionId, project, chunk.length);
|
|
4977
|
+
if (parsed) return parsed;
|
|
4978
|
+
logger.warn("Summarize chunk parse failed", {
|
|
4979
|
+
sessionId,
|
|
4980
|
+
chunk: `${idx + 1}/${total}`,
|
|
4981
|
+
attempt
|
|
4982
|
+
});
|
|
4983
|
+
} catch (err) {
|
|
4984
|
+
logger.warn("Summarize chunk LLM call failed", {
|
|
4985
|
+
sessionId,
|
|
4986
|
+
chunk: `${idx + 1}/${total}`,
|
|
4987
|
+
attempt,
|
|
4988
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4989
|
+
});
|
|
4990
|
+
}
|
|
4991
|
+
return null;
|
|
4992
|
+
}
|
|
4993
|
+
async function produceSummaryXml(provider, compressed, sessionId, project) {
|
|
4994
|
+
const chunkSize = getChunkSize();
|
|
4995
|
+
if (compressed.length <= chunkSize) return {
|
|
4996
|
+
response: await provider.summarize(SUMMARY_SYSTEM, buildSummaryPrompt(compressed)),
|
|
4997
|
+
mode: "single",
|
|
4998
|
+
chunks: 1
|
|
4999
|
+
};
|
|
5000
|
+
const chunks = [];
|
|
5001
|
+
for (let i = 0; i < compressed.length; i += chunkSize) chunks.push(compressed.slice(i, i + chunkSize));
|
|
5002
|
+
const concurrency = getChunkConcurrency();
|
|
5003
|
+
logger.info("Summarize chunking session", {
|
|
5004
|
+
sessionId,
|
|
5005
|
+
chunks: chunks.length,
|
|
5006
|
+
chunkSize,
|
|
5007
|
+
concurrency,
|
|
5008
|
+
totalObservations: compressed.length
|
|
5009
|
+
});
|
|
5010
|
+
const partialByIdx = new Array(chunks.length).fill(null);
|
|
5011
|
+
for (let batchStart = 0; batchStart < chunks.length; batchStart += concurrency) {
|
|
5012
|
+
const batch = chunks.slice(batchStart, batchStart + concurrency);
|
|
5013
|
+
await Promise.all(batch.map(async (chunk, j) => {
|
|
5014
|
+
const idx = batchStart + j;
|
|
5015
|
+
partialByIdx[idx] = await summarizeChunkWithRetry(provider, chunk, sessionId, project, idx, chunks.length);
|
|
5016
|
+
}));
|
|
5017
|
+
}
|
|
5018
|
+
const skipped = partialByIdx.filter((p) => p === null).length;
|
|
5019
|
+
const partials = partialByIdx.filter((p) => p !== null);
|
|
5020
|
+
if (skipped > Math.floor(chunks.length * MAX_SKIP_RATIO)) throw new Error(`too_many_chunks_skipped: ${skipped}/${chunks.length} chunks failed to parse after retry`);
|
|
5021
|
+
if (skipped > 0) logger.warn("Summarize chunks partially skipped", {
|
|
5022
|
+
sessionId,
|
|
5023
|
+
skipped,
|
|
5024
|
+
total: chunks.length
|
|
5025
|
+
});
|
|
5026
|
+
const reduceInput = partials.map((p) => {
|
|
5027
|
+
const originalIdx = partialByIdx.indexOf(p);
|
|
5028
|
+
return {
|
|
5029
|
+
title: p.title,
|
|
5030
|
+
narrative: p.narrative,
|
|
5031
|
+
keyDecisions: p.keyDecisions,
|
|
5032
|
+
filesModified: p.filesModified,
|
|
5033
|
+
concepts: p.concepts,
|
|
5034
|
+
obsRangeStart: originalIdx * chunkSize + 1,
|
|
5035
|
+
obsRangeEnd: Math.min((originalIdx + 1) * chunkSize, compressed.length)
|
|
5036
|
+
};
|
|
5037
|
+
});
|
|
5038
|
+
return {
|
|
5039
|
+
response: await provider.summarize(REDUCE_SYSTEM, buildReducePrompt(reduceInput)),
|
|
5040
|
+
mode: "chunked",
|
|
5041
|
+
chunks: chunks.length,
|
|
5042
|
+
skipped
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
4820
5045
|
function parseSummaryXml(xml, sessionId, project, obsCount) {
|
|
4821
5046
|
const title = getXmlTag(xml, "title");
|
|
4822
5047
|
if (!title) return null;
|
|
@@ -4865,16 +5090,15 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
|
|
|
4865
5090
|
};
|
|
4866
5091
|
}
|
|
4867
5092
|
try {
|
|
4868
|
-
const
|
|
4869
|
-
const response = await provider.summarize(SUMMARY_SYSTEM, prompt);
|
|
5093
|
+
const { response, mode, chunks } = await produceSummaryXml(provider, compressed, sessionId, session.project);
|
|
4870
5094
|
if (!response || !response.trim()) {
|
|
4871
5095
|
const latencyMs = Date.now() - startMs;
|
|
4872
5096
|
if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
|
|
4873
5097
|
logger.warn("Empty provider response on summarize", {
|
|
4874
5098
|
sessionId,
|
|
4875
5099
|
provider: provider.name,
|
|
4876
|
-
|
|
4877
|
-
|
|
5100
|
+
mode,
|
|
5101
|
+
chunks,
|
|
4878
5102
|
observationCount: compressed.length
|
|
4879
5103
|
});
|
|
4880
5104
|
return {
|
|
@@ -6031,6 +6255,7 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
6031
6255
|
|
|
6032
6256
|
//#endregion
|
|
6033
6257
|
//#region src/functions/smart-search.ts
|
|
6258
|
+
const LESSON_CONTENT_PREVIEW_CHARS = 240;
|
|
6034
6259
|
function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
6035
6260
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
6036
6261
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
@@ -6073,7 +6298,10 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6073
6298
|
error: "query is required"
|
|
6074
6299
|
};
|
|
6075
6300
|
const limit = Math.max(1, Math.min(data.limit ?? 20, 100));
|
|
6076
|
-
const
|
|
6301
|
+
const lessonLimit = Math.min(limit, 10);
|
|
6302
|
+
const includeLessons = data.includeLessons !== false;
|
|
6303
|
+
const [hybridResults, lessons] = await Promise.all([searchFn(data.query, limit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
|
|
6304
|
+
const compact = hybridResults.map((r) => ({
|
|
6077
6305
|
obsId: r.observation.id,
|
|
6078
6306
|
sessionId: r.sessionId,
|
|
6079
6307
|
title: r.observation.title,
|
|
@@ -6084,14 +6312,42 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6084
6312
|
recordAccessBatch(kv, compact.map((r) => r.obsId));
|
|
6085
6313
|
logger.info("Smart search compact", {
|
|
6086
6314
|
query: data.query,
|
|
6087
|
-
results: compact.length
|
|
6315
|
+
results: compact.length,
|
|
6316
|
+
lessons: lessons.length
|
|
6088
6317
|
});
|
|
6089
|
-
|
|
6318
|
+
const response = {
|
|
6090
6319
|
mode: "compact",
|
|
6091
6320
|
results: compact
|
|
6092
6321
|
};
|
|
6322
|
+
if (includeLessons) response.lessons = lessons;
|
|
6323
|
+
return response;
|
|
6093
6324
|
});
|
|
6094
6325
|
}
|
|
6326
|
+
async function recallLessons(sdk, query, limit, project) {
|
|
6327
|
+
try {
|
|
6328
|
+
const result = await sdk.trigger({
|
|
6329
|
+
function_id: "mem::lesson-recall",
|
|
6330
|
+
payload: {
|
|
6331
|
+
query,
|
|
6332
|
+
limit,
|
|
6333
|
+
project
|
|
6334
|
+
}
|
|
6335
|
+
});
|
|
6336
|
+
if (!result?.success || !Array.isArray(result.lessons)) return [];
|
|
6337
|
+
return result.lessons.map((l) => ({
|
|
6338
|
+
lessonId: l.id,
|
|
6339
|
+
content: l.content.length > LESSON_CONTENT_PREVIEW_CHARS ? l.content.slice(0, LESSON_CONTENT_PREVIEW_CHARS) + "…" : l.content,
|
|
6340
|
+
confidence: l.confidence,
|
|
6341
|
+
score: l.score ?? l.confidence,
|
|
6342
|
+
createdAt: l.createdAt,
|
|
6343
|
+
project: l.project,
|
|
6344
|
+
tags: l.tags ?? []
|
|
6345
|
+
}));
|
|
6346
|
+
} catch (err) {
|
|
6347
|
+
logger.warn("Smart search: mem::lesson-recall failed; returning empty lesson list", { error: err instanceof Error ? err.message : String(err) });
|
|
6348
|
+
return [];
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6095
6351
|
async function findObservation$1(kv, obsId, sessionIdHint) {
|
|
6096
6352
|
if (sessionIdHint) {
|
|
6097
6353
|
const obs = await kv.get(KV.observations(sessionIdHint), obsId).catch(() => null);
|
|
@@ -6317,7 +6573,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6317
6573
|
|
|
6318
6574
|
//#endregion
|
|
6319
6575
|
//#region src/version.ts
|
|
6320
|
-
const VERSION = "0.9.
|
|
6576
|
+
const VERSION = "0.9.21";
|
|
6321
6577
|
|
|
6322
6578
|
//#endregion
|
|
6323
6579
|
//#region src/functions/export-import.ts
|
|
@@ -6454,7 +6710,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6454
6710
|
"0.9.16",
|
|
6455
6711
|
"0.9.17",
|
|
6456
6712
|
"0.9.18",
|
|
6457
|
-
"0.9.19"
|
|
6713
|
+
"0.9.19",
|
|
6714
|
+
"0.9.20",
|
|
6715
|
+
"0.9.21"
|
|
6458
6716
|
]).has(importData.version)) return {
|
|
6459
6717
|
success: false,
|
|
6460
6718
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -10156,6 +10414,12 @@ const ALL_CATEGORIES = [
|
|
|
10156
10414
|
"signals",
|
|
10157
10415
|
"sessions",
|
|
10158
10416
|
"memories",
|
|
10417
|
+
"lessons",
|
|
10418
|
+
"summaries",
|
|
10419
|
+
"semantic",
|
|
10420
|
+
"procedural",
|
|
10421
|
+
"crystals",
|
|
10422
|
+
"insights",
|
|
10159
10423
|
"mesh"
|
|
10160
10424
|
];
|
|
10161
10425
|
const TWENTY_FOUR_HOURS_MS = 1440 * 60 * 1e3;
|
|
@@ -10388,6 +10652,133 @@ function registerDiagnosticsFunction(sdk, kv) {
|
|
|
10388
10652
|
fixable: false
|
|
10389
10653
|
});
|
|
10390
10654
|
}
|
|
10655
|
+
if (categories.includes("lessons")) {
|
|
10656
|
+
const lessons = await kv.list(KV.lessons);
|
|
10657
|
+
const live = lessons.filter((l) => !l.deleted);
|
|
10658
|
+
let lessonIssues = 0;
|
|
10659
|
+
for (const l of live) if (!Number.isFinite(l.confidence) || l.confidence < 0 || l.confidence > 1) {
|
|
10660
|
+
checks.push({
|
|
10661
|
+
name: `lesson-bad-confidence:${l.id}`,
|
|
10662
|
+
category: "lessons",
|
|
10663
|
+
status: "warn",
|
|
10664
|
+
message: `Lesson ${l.id} has confidence ${l.confidence} (expected finite number in 0..1)`,
|
|
10665
|
+
fixable: false
|
|
10666
|
+
});
|
|
10667
|
+
lessonIssues++;
|
|
10668
|
+
}
|
|
10669
|
+
if (lessonIssues === 0) checks.push({
|
|
10670
|
+
name: "lessons-ok",
|
|
10671
|
+
category: "lessons",
|
|
10672
|
+
status: "pass",
|
|
10673
|
+
message: `All ${live.length} lessons are healthy (${lessons.length - live.length} tombstoned)`,
|
|
10674
|
+
fixable: false
|
|
10675
|
+
});
|
|
10676
|
+
}
|
|
10677
|
+
if (categories.includes("summaries")) {
|
|
10678
|
+
const summaries = await kv.list(KV.summaries);
|
|
10679
|
+
let summaryIssues = 0;
|
|
10680
|
+
for (const s of summaries) if (typeof s.title !== "string" || s.title.trim().length === 0) {
|
|
10681
|
+
checks.push({
|
|
10682
|
+
name: `summary-missing-title:${s.sessionId}`,
|
|
10683
|
+
category: "summaries",
|
|
10684
|
+
status: "warn",
|
|
10685
|
+
message: `Summary for session ${s.sessionId} has no title`,
|
|
10686
|
+
fixable: false
|
|
10687
|
+
});
|
|
10688
|
+
summaryIssues++;
|
|
10689
|
+
}
|
|
10690
|
+
if (summaryIssues === 0) checks.push({
|
|
10691
|
+
name: "summaries-ok",
|
|
10692
|
+
category: "summaries",
|
|
10693
|
+
status: "pass",
|
|
10694
|
+
message: `All ${summaries.length} session summaries are consistent`,
|
|
10695
|
+
fixable: false
|
|
10696
|
+
});
|
|
10697
|
+
}
|
|
10698
|
+
if (categories.includes("semantic")) {
|
|
10699
|
+
const semantic = await kv.list(KV.semantic);
|
|
10700
|
+
let semanticIssues = 0;
|
|
10701
|
+
for (const s of semantic) if (!Number.isFinite(s.confidence) || s.confidence < 0 || s.confidence > 1) {
|
|
10702
|
+
checks.push({
|
|
10703
|
+
name: `semantic-bad-confidence:${s.id}`,
|
|
10704
|
+
category: "semantic",
|
|
10705
|
+
status: "warn",
|
|
10706
|
+
message: `Semantic fact ${s.id} has confidence ${s.confidence} (expected finite number in 0..1)`,
|
|
10707
|
+
fixable: false
|
|
10708
|
+
});
|
|
10709
|
+
semanticIssues++;
|
|
10710
|
+
}
|
|
10711
|
+
if (semanticIssues === 0) checks.push({
|
|
10712
|
+
name: "semantic-ok",
|
|
10713
|
+
category: "semantic",
|
|
10714
|
+
status: "pass",
|
|
10715
|
+
message: `All ${semantic.length} semantic memories are consistent`,
|
|
10716
|
+
fixable: false
|
|
10717
|
+
});
|
|
10718
|
+
}
|
|
10719
|
+
if (categories.includes("procedural")) {
|
|
10720
|
+
const procedural = await kv.list(KV.procedural);
|
|
10721
|
+
let proceduralIssues = 0;
|
|
10722
|
+
for (const p of procedural) if (!Array.isArray(p.steps) || p.steps.length === 0) {
|
|
10723
|
+
checks.push({
|
|
10724
|
+
name: `procedural-empty-steps:${p.id}`,
|
|
10725
|
+
category: "procedural",
|
|
10726
|
+
status: "warn",
|
|
10727
|
+
message: `Procedural memory "${p.name}" (${p.id}) has no steps`,
|
|
10728
|
+
fixable: false
|
|
10729
|
+
});
|
|
10730
|
+
proceduralIssues++;
|
|
10731
|
+
}
|
|
10732
|
+
if (proceduralIssues === 0) checks.push({
|
|
10733
|
+
name: "procedural-ok",
|
|
10734
|
+
category: "procedural",
|
|
10735
|
+
status: "pass",
|
|
10736
|
+
message: `All ${procedural.length} procedural memories are consistent`,
|
|
10737
|
+
fixable: false
|
|
10738
|
+
});
|
|
10739
|
+
}
|
|
10740
|
+
if (categories.includes("crystals")) {
|
|
10741
|
+
const crystals = await kv.list(KV.crystals);
|
|
10742
|
+
let crystalIssues = 0;
|
|
10743
|
+
for (const c of crystals) if (typeof c.narrative !== "string" || c.narrative.trim().length === 0) {
|
|
10744
|
+
checks.push({
|
|
10745
|
+
name: `crystal-empty-narrative:${c.id}`,
|
|
10746
|
+
category: "crystals",
|
|
10747
|
+
status: "warn",
|
|
10748
|
+
message: `Crystal ${c.id} has empty narrative`,
|
|
10749
|
+
fixable: false
|
|
10750
|
+
});
|
|
10751
|
+
crystalIssues++;
|
|
10752
|
+
}
|
|
10753
|
+
if (crystalIssues === 0) checks.push({
|
|
10754
|
+
name: "crystals-ok",
|
|
10755
|
+
category: "crystals",
|
|
10756
|
+
status: "pass",
|
|
10757
|
+
message: `All ${crystals.length} crystals are consistent`,
|
|
10758
|
+
fixable: false
|
|
10759
|
+
});
|
|
10760
|
+
}
|
|
10761
|
+
if (categories.includes("insights")) {
|
|
10762
|
+
const insights = await kv.list(KV.insights);
|
|
10763
|
+
let insightIssues = 0;
|
|
10764
|
+
for (const i of insights) if (!Number.isFinite(i.confidence) || i.confidence < 0 || i.confidence > 1) {
|
|
10765
|
+
checks.push({
|
|
10766
|
+
name: `insight-bad-confidence:${i.id}`,
|
|
10767
|
+
category: "insights",
|
|
10768
|
+
status: "warn",
|
|
10769
|
+
message: `Insight ${i.id} has confidence ${i.confidence} (expected finite number in 0..1)`,
|
|
10770
|
+
fixable: false
|
|
10771
|
+
});
|
|
10772
|
+
insightIssues++;
|
|
10773
|
+
}
|
|
10774
|
+
if (insightIssues === 0) checks.push({
|
|
10775
|
+
name: "insights-ok",
|
|
10776
|
+
category: "insights",
|
|
10777
|
+
status: "pass",
|
|
10778
|
+
message: `All ${insights.length} insights are consistent`,
|
|
10779
|
+
fixable: false
|
|
10780
|
+
});
|
|
10781
|
+
}
|
|
10391
10782
|
if (categories.includes("mesh")) {
|
|
10392
10783
|
const peers = await kv.list(KV.mesh);
|
|
10393
10784
|
let meshIssues = 0;
|
|
@@ -14198,13 +14589,16 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14198
14589
|
status_code: 400,
|
|
14199
14590
|
body: { error: "sessionId, project, and cwd are required non-empty strings" }
|
|
14200
14591
|
};
|
|
14592
|
+
const title = typeof body.title === "string" ? body.title.trim() : void 0;
|
|
14201
14593
|
const session = {
|
|
14202
14594
|
id: sessionId,
|
|
14203
14595
|
project,
|
|
14204
14596
|
cwd,
|
|
14205
14597
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14206
14598
|
status: "active",
|
|
14207
|
-
observationCount: 0
|
|
14599
|
+
observationCount: 0,
|
|
14600
|
+
...title ? { summary: title.slice(0, 200) } : {},
|
|
14601
|
+
...title ? { firstPrompt: title.slice(0, 200) } : {}
|
|
14208
14602
|
};
|
|
14209
14603
|
await kv.set(KV.sessions, sessionId, session);
|
|
14210
14604
|
return {
|
|
@@ -20385,23 +20779,22 @@ async function main() {
|
|
|
20385
20779
|
if (mismatches.length > 0) {
|
|
20386
20780
|
const sample = mismatches.slice(0, 5).map((m) => `${m.obsId} (dim=${m.dim})`).join(", ");
|
|
20387
20781
|
const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", ");
|
|
20388
|
-
if (
|
|
20782
|
+
if (isDropStaleIndexEnabled()) console.warn(`[agentmemory] Persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. AGENTMEMORY_DROP_STALE_INDEX=true is set — discarding the persisted vectors. Live observations will rebuild the index over time.`);
|
|
20389
20783
|
else throw new Error(`[agentmemory] Refusing to start: persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. First mismatched obsIds: ${sample}. Loading would silently corrupt search (cross-dimension cosine returns 0). Choose one:\n - Re-embed the existing index against the new provider, then start.\n - Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted vectors and rebuild from live observations.\n - Switch the embedding provider back to the one that wrote the index.`);
|
|
20390
20784
|
} else {
|
|
20391
20785
|
vectorIndex.restoreFrom(loaded.vector);
|
|
20392
20786
|
bootLog(`Loaded persisted vector index (${vectorIndex.size} vectors)`);
|
|
20393
20787
|
}
|
|
20394
20788
|
}
|
|
20395
|
-
if (bm25Index.size === 0) {
|
|
20396
|
-
const indexCount = await rebuildIndex(kv).catch((err) => {
|
|
20397
|
-
console.warn(`[agentmemory] Failed to rebuild search index:`, err);
|
|
20398
|
-
return 0;
|
|
20399
|
-
});
|
|
20789
|
+
if (bm25Index.size === 0) rebuildIndex(kv).then((indexCount) => {
|
|
20400
20790
|
if (indexCount > 0) {
|
|
20401
20791
|
bootLog(`Search index rebuilt: ${indexCount} entries`);
|
|
20402
20792
|
indexPersistence.scheduleSave();
|
|
20403
20793
|
}
|
|
20404
|
-
}
|
|
20794
|
+
}).catch((err) => {
|
|
20795
|
+
console.warn(`[agentmemory] Failed to rebuild search index:`, err);
|
|
20796
|
+
});
|
|
20797
|
+
else try {
|
|
20405
20798
|
const memories = await kv.list(KV.memories);
|
|
20406
20799
|
let backfilled = 0;
|
|
20407
20800
|
for (const memory of memories) {
|