@agentmemory/agentmemory 0.9.20 → 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.
@@ -1,6 +1,6 @@
1
1
  import { a as STREAM, c as jaccardSimilarity, i as KV, n as bootLog, o as fingerprintId, r as logger, s as generateId, t as VERSION } from "./cli.mjs";
2
2
  import { a as isManagedImagePath, getImageRefCount, i as getMaxBytes, n as IMAGES_DIR, r as deleteImage, t as withKeyedLock } from "./image-refs-R3tin9MR.mjs";
3
- import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-Dz8ssuMf.mjs";
3
+ import { _ as loadSnapshotConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isDropStaleIndexEnabled, f as isGraphExtractionEnabled, g as loadFallbackConfig, h as loadEmbeddingConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadConfig, n as getVisibleTools, o as getEnvVar, p as loadClaudeBridgeConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled, v as loadTeamConfig } from "./tools-registry-CRTWUFw9.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { execFile } from "node:child_process";
6
6
  import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -2643,20 +2643,108 @@ async function vectorIndexAddGuarded(id, sessionId, text, context) {
2643
2643
  return false;
2644
2644
  }
2645
2645
  }
2646
+ async function vectorIndexAddBatchGuarded(items) {
2647
+ const vi = vectorIndex;
2648
+ const ep = currentEmbeddingProvider;
2649
+ if (!vi || !ep || items.length === 0) return {
2650
+ ok: 0,
2651
+ fail: 0
2652
+ };
2653
+ let embeddings;
2654
+ try {
2655
+ embeddings = await ep.embedBatch(items.map((i) => clipEmbedInput(i.text)));
2656
+ } catch (err) {
2657
+ logger.warn("vector-index add batch: embed failed — skipping batch", {
2658
+ batchSize: items.length,
2659
+ provider: ep.name,
2660
+ error: err instanceof Error ? err.message : String(err)
2661
+ });
2662
+ return {
2663
+ ok: 0,
2664
+ fail: items.length
2665
+ };
2666
+ }
2667
+ if (embeddings.length !== items.length) {
2668
+ logger.warn("vector-index add batch: provider returned wrong length — skipping batch", {
2669
+ batchSize: items.length,
2670
+ returned: embeddings.length,
2671
+ provider: ep.name
2672
+ });
2673
+ return {
2674
+ ok: 0,
2675
+ fail: items.length
2676
+ };
2677
+ }
2678
+ let ok = 0;
2679
+ let fail = 0;
2680
+ for (let i = 0; i < items.length; i++) {
2681
+ const item = items[i];
2682
+ const embedding = embeddings[i];
2683
+ if (embedding.length !== ep.dimensions) {
2684
+ logger.warn("vector-index add batch: dimension mismatch — skipping item", {
2685
+ kind: item.context.kind,
2686
+ id: item.context.logId,
2687
+ provider: ep.name,
2688
+ expected: ep.dimensions,
2689
+ received: embedding.length
2690
+ });
2691
+ fail++;
2692
+ continue;
2693
+ }
2694
+ try {
2695
+ vi.add(item.id, item.sessionId, embedding);
2696
+ ok++;
2697
+ } catch (err) {
2698
+ logger.warn("vector-index add batch: index write failed — skipping item", {
2699
+ kind: item.context.kind,
2700
+ id: item.context.logId,
2701
+ error: err instanceof Error ? err.message : String(err)
2702
+ });
2703
+ fail++;
2704
+ }
2705
+ }
2706
+ return {
2707
+ ok,
2708
+ fail
2709
+ };
2710
+ }
2711
+ const DEFAULT_REBUILD_EMBED_BATCH = 32;
2712
+ function getRebuildEmbedBatchSize() {
2713
+ const raw = process.env.REBUILD_EMBED_BATCH_SIZE;
2714
+ if (!raw) return DEFAULT_REBUILD_EMBED_BATCH;
2715
+ const n = parseInt(raw, 10);
2716
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_REBUILD_EMBED_BATCH;
2717
+ }
2646
2718
  async function rebuildIndex(kv) {
2647
2719
  const idx = getSearchIndex();
2648
2720
  idx.clear();
2649
2721
  vectorIndex?.clear();
2722
+ const batchSize = getRebuildEmbedBatchSize();
2723
+ const pending = [];
2650
2724
  let count = 0;
2725
+ const flush = async () => {
2726
+ if (pending.length === 0) return;
2727
+ await vectorIndexAddBatchGuarded(pending);
2728
+ pending.length = 0;
2729
+ };
2730
+ const enqueue = async (job) => {
2731
+ pending.push(job);
2732
+ if (pending.length >= batchSize) await flush();
2733
+ };
2651
2734
  try {
2652
2735
  const memories = await kv.list(KV.memories);
2653
2736
  for (const memory of memories) {
2654
2737
  if (memory.isLatest === false) continue;
2655
2738
  if (!memory.title || !memory.content) continue;
2656
2739
  idx.add(memoryToObservation(memory));
2657
- await vectorIndexAddGuarded(memory.id, memory.sessionIds[0] ?? "memory", memory.title + " " + memory.content, {
2658
- kind: "memory",
2659
- logId: memory.id
2740
+ await enqueue({
2741
+ id: memory.id,
2742
+ sessionId: memory.sessionIds[0] ?? "memory",
2743
+ text: memory.title + " " + memory.content,
2744
+ context: {
2745
+ kind: "memory",
2746
+ logId: memory.id
2747
+ }
2660
2748
  });
2661
2749
  count++;
2662
2750
  }
@@ -2664,7 +2752,10 @@ async function rebuildIndex(kv) {
2664
2752
  logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
2665
2753
  }
2666
2754
  const sessions = await kv.list(KV.sessions);
2667
- if (!sessions.length) return count;
2755
+ if (!sessions.length) {
2756
+ await flush();
2757
+ return count;
2758
+ }
2668
2759
  const obsPerSession = [];
2669
2760
  const failedSessions = [];
2670
2761
  for (let batch = 0; batch < sessions.length; batch += 10) {
@@ -2682,12 +2773,18 @@ async function rebuildIndex(kv) {
2682
2773
  if (failedSessions.length > 0) logger.warn("rebuildIndex: failed to load observations for sessions", { failedSessions });
2683
2774
  for (const observations of obsPerSession) for (const obs of observations) if (obs.title && obs.narrative) {
2684
2775
  idx.add(obs);
2685
- await vectorIndexAddGuarded(obs.id, obs.sessionId, obs.title + " " + obs.narrative, {
2686
- kind: "observation",
2687
- logId: obs.id
2776
+ await enqueue({
2777
+ id: obs.id,
2778
+ sessionId: obs.sessionId,
2779
+ text: obs.title + " " + obs.narrative,
2780
+ context: {
2781
+ kind: "observation",
2782
+ logId: obs.id
2783
+ }
2688
2784
  });
2689
2785
  count++;
2690
2786
  }
2787
+ await flush();
2691
2788
  return count;
2692
2789
  }
2693
2790
  function registerSearchFunction(sdk, kv) {
@@ -4365,9 +4462,134 @@ function buildSummaryPrompt(observations) {
4365
4462
  });
4366
4463
  return `Session observations (${observations.length} total):\n\n${lines.join("\n\n---\n\n")}`;
4367
4464
  }
4465
+ 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.
4466
+
4467
+ Output EXACTLY this XML format with no additional text:
4468
+
4469
+ <summary>
4470
+ <title>Short session title (max 100 chars)</title>
4471
+ <narrative>3-5 sentence narrative covering the whole session</narrative>
4472
+ <decisions>
4473
+ <decision>Key technical decision made</decision>
4474
+ </decisions>
4475
+ <files>
4476
+ <file>path/to/modified/file</file>
4477
+ </files>
4478
+ <concepts>
4479
+ <concept>key concept from session</concept>
4480
+ </concepts>
4481
+ </summary>
4482
+
4483
+ Rules:
4484
+ - Synthesize a single narrative that reflects the whole arc, not a chunk-by-chunk recap
4485
+ - Preserve every distinct decision across chunks
4486
+ - Union (deduplicate) all files and concepts
4487
+ - Title should capture the session's overall outcome`;
4488
+ function buildReducePrompt(partials) {
4489
+ const sections = partials.map((p, i) => {
4490
+ const decisions = p.keyDecisions.map((d) => ` - ${d}`).join("\n");
4491
+ const files = p.filesModified.map((f) => ` - ${f}`).join("\n");
4492
+ const concepts = p.concepts.join(", ");
4493
+ return `[Chunk ${i + 1} of ${partials.length} — obs ${p.obsRangeStart}-${p.obsRangeEnd}]
4494
+ Title: ${p.title}
4495
+ Narrative: ${p.narrative}
4496
+ Decisions:
4497
+ ${decisions}
4498
+ Files:
4499
+ ${files}
4500
+ Concepts: ${concepts}`;
4501
+ });
4502
+ return `Partial summaries (${partials.length} chunks of one session, chronological):\n\n${sections.join("\n\n---\n\n")}`;
4503
+ }
4368
4504
 
4369
4505
  //#endregion
4370
4506
  //#region src/functions/summarize.ts
4507
+ const CHUNK_SIZE_DEFAULT = 400;
4508
+ const CHUNK_CONCURRENCY_DEFAULT = 6;
4509
+ const MAX_SKIP_RATIO = .5;
4510
+ function getChunkSize() {
4511
+ const raw = process.env.SUMMARIZE_CHUNK_SIZE;
4512
+ if (!raw) return CHUNK_SIZE_DEFAULT;
4513
+ const n = parseInt(raw, 10);
4514
+ return Number.isFinite(n) && n > 0 ? n : CHUNK_SIZE_DEFAULT;
4515
+ }
4516
+ function getChunkConcurrency() {
4517
+ const raw = process.env.SUMMARIZE_CHUNK_CONCURRENCY;
4518
+ if (!raw) return CHUNK_CONCURRENCY_DEFAULT;
4519
+ const n = parseInt(raw, 10);
4520
+ return Number.isFinite(n) && n > 0 ? n : CHUNK_CONCURRENCY_DEFAULT;
4521
+ }
4522
+ async function summarizeChunkWithRetry(provider, chunk, sessionId, project, idx, total) {
4523
+ for (let attempt = 1; attempt <= 2; attempt++) try {
4524
+ const parsed = parseSummaryXml(await provider.summarize(SUMMARY_SYSTEM, buildSummaryPrompt(chunk)), sessionId, project, chunk.length);
4525
+ if (parsed) return parsed;
4526
+ logger.warn("Summarize chunk parse failed", {
4527
+ sessionId,
4528
+ chunk: `${idx + 1}/${total}`,
4529
+ attempt
4530
+ });
4531
+ } catch (err) {
4532
+ logger.warn("Summarize chunk LLM call failed", {
4533
+ sessionId,
4534
+ chunk: `${idx + 1}/${total}`,
4535
+ attempt,
4536
+ error: err instanceof Error ? err.message : String(err)
4537
+ });
4538
+ }
4539
+ return null;
4540
+ }
4541
+ async function produceSummaryXml(provider, compressed, sessionId, project) {
4542
+ const chunkSize = getChunkSize();
4543
+ if (compressed.length <= chunkSize) return {
4544
+ response: await provider.summarize(SUMMARY_SYSTEM, buildSummaryPrompt(compressed)),
4545
+ mode: "single",
4546
+ chunks: 1
4547
+ };
4548
+ const chunks = [];
4549
+ for (let i = 0; i < compressed.length; i += chunkSize) chunks.push(compressed.slice(i, i + chunkSize));
4550
+ const concurrency = getChunkConcurrency();
4551
+ logger.info("Summarize chunking session", {
4552
+ sessionId,
4553
+ chunks: chunks.length,
4554
+ chunkSize,
4555
+ concurrency,
4556
+ totalObservations: compressed.length
4557
+ });
4558
+ const partialByIdx = new Array(chunks.length).fill(null);
4559
+ for (let batchStart = 0; batchStart < chunks.length; batchStart += concurrency) {
4560
+ const batch = chunks.slice(batchStart, batchStart + concurrency);
4561
+ await Promise.all(batch.map(async (chunk, j) => {
4562
+ const idx = batchStart + j;
4563
+ partialByIdx[idx] = await summarizeChunkWithRetry(provider, chunk, sessionId, project, idx, chunks.length);
4564
+ }));
4565
+ }
4566
+ const skipped = partialByIdx.filter((p) => p === null).length;
4567
+ const partials = partialByIdx.filter((p) => p !== null);
4568
+ 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`);
4569
+ if (skipped > 0) logger.warn("Summarize chunks partially skipped", {
4570
+ sessionId,
4571
+ skipped,
4572
+ total: chunks.length
4573
+ });
4574
+ const reduceInput = partials.map((p) => {
4575
+ const originalIdx = partialByIdx.indexOf(p);
4576
+ return {
4577
+ title: p.title,
4578
+ narrative: p.narrative,
4579
+ keyDecisions: p.keyDecisions,
4580
+ filesModified: p.filesModified,
4581
+ concepts: p.concepts,
4582
+ obsRangeStart: originalIdx * chunkSize + 1,
4583
+ obsRangeEnd: Math.min((originalIdx + 1) * chunkSize, compressed.length)
4584
+ };
4585
+ });
4586
+ return {
4587
+ response: await provider.summarize(REDUCE_SYSTEM, buildReducePrompt(reduceInput)),
4588
+ mode: "chunked",
4589
+ chunks: chunks.length,
4590
+ skipped
4591
+ };
4592
+ }
4371
4593
  function parseSummaryXml(xml, sessionId, project, obsCount) {
4372
4594
  const title = getXmlTag(xml, "title");
4373
4595
  if (!title) return null;
@@ -4416,16 +4638,15 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
4416
4638
  };
4417
4639
  }
4418
4640
  try {
4419
- const prompt = buildSummaryPrompt(compressed);
4420
- const response = await provider.summarize(SUMMARY_SYSTEM, prompt);
4641
+ const { response, mode, chunks } = await produceSummaryXml(provider, compressed, sessionId, session.project);
4421
4642
  if (!response || !response.trim()) {
4422
4643
  const latencyMs = Date.now() - startMs;
4423
4644
  if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
4424
4645
  logger.warn("Empty provider response on summarize", {
4425
4646
  sessionId,
4426
4647
  provider: provider.name,
4427
- promptBytes: prompt.length,
4428
- systemBytes: SUMMARY_SYSTEM.length,
4648
+ mode,
4649
+ chunks,
4429
4650
  observationCount: compressed.length
4430
4651
  });
4431
4652
  return {
@@ -5582,6 +5803,7 @@ async function findByKeyword(kv, keyword, project) {
5582
5803
 
5583
5804
  //#endregion
5584
5805
  //#region src/functions/smart-search.ts
5806
+ const LESSON_CONTENT_PREVIEW_CHARS = 240;
5585
5807
  function registerSmartSearchFunction(sdk, kv, searchFn) {
5586
5808
  sdk.registerFunction("mem::smart-search", async (data) => {
5587
5809
  if (data.expandIds && data.expandIds.length > 0) {
@@ -5624,7 +5846,10 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
5624
5846
  error: "query is required"
5625
5847
  };
5626
5848
  const limit = Math.max(1, Math.min(data.limit ?? 20, 100));
5627
- const compact = (await searchFn(data.query, limit)).map((r) => ({
5849
+ const lessonLimit = Math.min(limit, 10);
5850
+ const includeLessons = data.includeLessons !== false;
5851
+ const [hybridResults, lessons] = await Promise.all([searchFn(data.query, limit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
5852
+ const compact = hybridResults.map((r) => ({
5628
5853
  obsId: r.observation.id,
5629
5854
  sessionId: r.sessionId,
5630
5855
  title: r.observation.title,
@@ -5635,14 +5860,42 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
5635
5860
  recordAccessBatch(kv, compact.map((r) => r.obsId));
5636
5861
  logger.info("Smart search compact", {
5637
5862
  query: data.query,
5638
- results: compact.length
5863
+ results: compact.length,
5864
+ lessons: lessons.length
5639
5865
  });
5640
- return {
5866
+ const response = {
5641
5867
  mode: "compact",
5642
5868
  results: compact
5643
5869
  };
5870
+ if (includeLessons) response.lessons = lessons;
5871
+ return response;
5644
5872
  });
5645
5873
  }
5874
+ async function recallLessons(sdk, query, limit, project) {
5875
+ try {
5876
+ const result = await sdk.trigger({
5877
+ function_id: "mem::lesson-recall",
5878
+ payload: {
5879
+ query,
5880
+ limit,
5881
+ project
5882
+ }
5883
+ });
5884
+ if (!result?.success || !Array.isArray(result.lessons)) return [];
5885
+ return result.lessons.map((l) => ({
5886
+ lessonId: l.id,
5887
+ content: l.content.length > LESSON_CONTENT_PREVIEW_CHARS ? l.content.slice(0, LESSON_CONTENT_PREVIEW_CHARS) + "…" : l.content,
5888
+ confidence: l.confidence,
5889
+ score: l.score ?? l.confidence,
5890
+ createdAt: l.createdAt,
5891
+ project: l.project,
5892
+ tags: l.tags ?? []
5893
+ }));
5894
+ } catch (err) {
5895
+ logger.warn("Smart search: mem::lesson-recall failed; returning empty lesson list", { error: err instanceof Error ? err.message : String(err) });
5896
+ return [];
5897
+ }
5898
+ }
5646
5899
  async function findObservation$1(kv, obsId, sessionIdHint) {
5647
5900
  if (sessionIdHint) {
5648
5901
  const obs = await kv.get(KV.observations(sessionIdHint), obsId).catch(() => null);
@@ -6002,7 +6255,8 @@ function registerExportImportFunction(sdk, kv) {
6002
6255
  "0.9.17",
6003
6256
  "0.9.18",
6004
6257
  "0.9.19",
6005
- "0.9.20"
6258
+ "0.9.20",
6259
+ "0.9.21"
6006
6260
  ]).has(importData.version)) return {
6007
6261
  success: false,
6008
6262
  error: `Unsupported export version: ${importData.version}`
@@ -9704,6 +9958,12 @@ const ALL_CATEGORIES = [
9704
9958
  "signals",
9705
9959
  "sessions",
9706
9960
  "memories",
9961
+ "lessons",
9962
+ "summaries",
9963
+ "semantic",
9964
+ "procedural",
9965
+ "crystals",
9966
+ "insights",
9707
9967
  "mesh"
9708
9968
  ];
9709
9969
  const TWENTY_FOUR_HOURS_MS = 1440 * 60 * 1e3;
@@ -9936,6 +10196,133 @@ function registerDiagnosticsFunction(sdk, kv) {
9936
10196
  fixable: false
9937
10197
  });
9938
10198
  }
10199
+ if (categories.includes("lessons")) {
10200
+ const lessons = await kv.list(KV.lessons);
10201
+ const live = lessons.filter((l) => !l.deleted);
10202
+ let lessonIssues = 0;
10203
+ for (const l of live) if (!Number.isFinite(l.confidence) || l.confidence < 0 || l.confidence > 1) {
10204
+ checks.push({
10205
+ name: `lesson-bad-confidence:${l.id}`,
10206
+ category: "lessons",
10207
+ status: "warn",
10208
+ message: `Lesson ${l.id} has confidence ${l.confidence} (expected finite number in 0..1)`,
10209
+ fixable: false
10210
+ });
10211
+ lessonIssues++;
10212
+ }
10213
+ if (lessonIssues === 0) checks.push({
10214
+ name: "lessons-ok",
10215
+ category: "lessons",
10216
+ status: "pass",
10217
+ message: `All ${live.length} lessons are healthy (${lessons.length - live.length} tombstoned)`,
10218
+ fixable: false
10219
+ });
10220
+ }
10221
+ if (categories.includes("summaries")) {
10222
+ const summaries = await kv.list(KV.summaries);
10223
+ let summaryIssues = 0;
10224
+ for (const s of summaries) if (typeof s.title !== "string" || s.title.trim().length === 0) {
10225
+ checks.push({
10226
+ name: `summary-missing-title:${s.sessionId}`,
10227
+ category: "summaries",
10228
+ status: "warn",
10229
+ message: `Summary for session ${s.sessionId} has no title`,
10230
+ fixable: false
10231
+ });
10232
+ summaryIssues++;
10233
+ }
10234
+ if (summaryIssues === 0) checks.push({
10235
+ name: "summaries-ok",
10236
+ category: "summaries",
10237
+ status: "pass",
10238
+ message: `All ${summaries.length} session summaries are consistent`,
10239
+ fixable: false
10240
+ });
10241
+ }
10242
+ if (categories.includes("semantic")) {
10243
+ const semantic = await kv.list(KV.semantic);
10244
+ let semanticIssues = 0;
10245
+ for (const s of semantic) if (!Number.isFinite(s.confidence) || s.confidence < 0 || s.confidence > 1) {
10246
+ checks.push({
10247
+ name: `semantic-bad-confidence:${s.id}`,
10248
+ category: "semantic",
10249
+ status: "warn",
10250
+ message: `Semantic fact ${s.id} has confidence ${s.confidence} (expected finite number in 0..1)`,
10251
+ fixable: false
10252
+ });
10253
+ semanticIssues++;
10254
+ }
10255
+ if (semanticIssues === 0) checks.push({
10256
+ name: "semantic-ok",
10257
+ category: "semantic",
10258
+ status: "pass",
10259
+ message: `All ${semantic.length} semantic memories are consistent`,
10260
+ fixable: false
10261
+ });
10262
+ }
10263
+ if (categories.includes("procedural")) {
10264
+ const procedural = await kv.list(KV.procedural);
10265
+ let proceduralIssues = 0;
10266
+ for (const p of procedural) if (!Array.isArray(p.steps) || p.steps.length === 0) {
10267
+ checks.push({
10268
+ name: `procedural-empty-steps:${p.id}`,
10269
+ category: "procedural",
10270
+ status: "warn",
10271
+ message: `Procedural memory "${p.name}" (${p.id}) has no steps`,
10272
+ fixable: false
10273
+ });
10274
+ proceduralIssues++;
10275
+ }
10276
+ if (proceduralIssues === 0) checks.push({
10277
+ name: "procedural-ok",
10278
+ category: "procedural",
10279
+ status: "pass",
10280
+ message: `All ${procedural.length} procedural memories are consistent`,
10281
+ fixable: false
10282
+ });
10283
+ }
10284
+ if (categories.includes("crystals")) {
10285
+ const crystals = await kv.list(KV.crystals);
10286
+ let crystalIssues = 0;
10287
+ for (const c of crystals) if (typeof c.narrative !== "string" || c.narrative.trim().length === 0) {
10288
+ checks.push({
10289
+ name: `crystal-empty-narrative:${c.id}`,
10290
+ category: "crystals",
10291
+ status: "warn",
10292
+ message: `Crystal ${c.id} has empty narrative`,
10293
+ fixable: false
10294
+ });
10295
+ crystalIssues++;
10296
+ }
10297
+ if (crystalIssues === 0) checks.push({
10298
+ name: "crystals-ok",
10299
+ category: "crystals",
10300
+ status: "pass",
10301
+ message: `All ${crystals.length} crystals are consistent`,
10302
+ fixable: false
10303
+ });
10304
+ }
10305
+ if (categories.includes("insights")) {
10306
+ const insights = await kv.list(KV.insights);
10307
+ let insightIssues = 0;
10308
+ for (const i of insights) if (!Number.isFinite(i.confidence) || i.confidence < 0 || i.confidence > 1) {
10309
+ checks.push({
10310
+ name: `insight-bad-confidence:${i.id}`,
10311
+ category: "insights",
10312
+ status: "warn",
10313
+ message: `Insight ${i.id} has confidence ${i.confidence} (expected finite number in 0..1)`,
10314
+ fixable: false
10315
+ });
10316
+ insightIssues++;
10317
+ }
10318
+ if (insightIssues === 0) checks.push({
10319
+ name: "insights-ok",
10320
+ category: "insights",
10321
+ status: "pass",
10322
+ message: `All ${insights.length} insights are consistent`,
10323
+ fixable: false
10324
+ });
10325
+ }
9939
10326
  if (categories.includes("mesh")) {
9940
10327
  const peers = await kv.list(KV.mesh);
9941
10328
  let meshIssues = 0;
@@ -13746,13 +14133,16 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13746
14133
  status_code: 400,
13747
14134
  body: { error: "sessionId, project, and cwd are required non-empty strings" }
13748
14135
  };
14136
+ const title = typeof body.title === "string" ? body.title.trim() : void 0;
13749
14137
  const session = {
13750
14138
  id: sessionId,
13751
14139
  project,
13752
14140
  cwd,
13753
14141
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13754
14142
  status: "active",
13755
- observationCount: 0
14143
+ observationCount: 0,
14144
+ ...title ? { summary: title.slice(0, 200) } : {},
14145
+ ...title ? { firstPrompt: title.slice(0, 200) } : {}
13756
14146
  };
13757
14147
  await kv.set(KV.sessions, sessionId, session);
13758
14148
  return {
@@ -18827,23 +19217,22 @@ async function main() {
18827
19217
  if (mismatches.length > 0) {
18828
19218
  const sample = mismatches.slice(0, 5).map((m) => `${m.obsId} (dim=${m.dim})`).join(", ");
18829
19219
  const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", ");
18830
- if (process.env["AGENTMEMORY_DROP_STALE_INDEX"] === "true") 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.`);
19220
+ 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.`);
18831
19221
  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.`);
18832
19222
  } else {
18833
19223
  vectorIndex.restoreFrom(loaded.vector);
18834
19224
  bootLog(`Loaded persisted vector index (${vectorIndex.size} vectors)`);
18835
19225
  }
18836
19226
  }
18837
- if (bm25Index.size === 0) {
18838
- const indexCount = await rebuildIndex(kv).catch((err) => {
18839
- console.warn(`[agentmemory] Failed to rebuild search index:`, err);
18840
- return 0;
18841
- });
19227
+ if (bm25Index.size === 0) rebuildIndex(kv).then((indexCount) => {
18842
19228
  if (indexCount > 0) {
18843
19229
  bootLog(`Search index rebuilt: ${indexCount} entries`);
18844
19230
  indexPersistence.scheduleSave();
18845
19231
  }
18846
- } else try {
19232
+ }).catch((err) => {
19233
+ console.warn(`[agentmemory] Failed to rebuild search index:`, err);
19234
+ });
19235
+ else try {
18847
19236
  const memories = await kv.list(KV.memories);
18848
19237
  let backfilled = 0;
18849
19238
  for (const memory of memories) {
@@ -18940,4 +19329,4 @@ main().catch((err) => {
18940
19329
 
18941
19330
  //#endregion
18942
19331
  export { };
18943
- //# sourceMappingURL=src-DPSaLB5-.mjs.map
19332
+ //# sourceMappingURL=src-D5arboxc.mjs.map