agenr 0.13.4 → 0.14.1

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +5 -5
  3. package/dist/{chunk-WB6OH7LM.js → chunk-2JI5DOZ2.js} +413 -9
  4. package/dist/{chunk-EAIHUY6F.js → chunk-4JHTJXYY.js} +8 -8
  5. package/dist/{chunk-27X6QJCR.js → chunk-4QW3AAO2.js} +2 -2
  6. package/dist/{chunk-IJBDKQIX.js → chunk-FKKPPFS3.js} +38 -135
  7. package/dist/{chunk-NGV264GF.js → chunk-FUOKOMSN.js} +2 -2
  8. package/dist/{chunk-BIND34BY.js → chunk-LLHGEYS7.js} +132 -48
  9. package/dist/{chunk-QI23WU2O.js → chunk-SJN63MTD.js} +261 -10
  10. package/dist/{chunk-HGRL3DZC.js → chunk-TSIZVBIJ.js} +1 -1
  11. package/dist/{chunk-DK6MSILE.js → chunk-VNPHNLJT.js} +13 -13
  12. package/dist/{chunk-ODBWAJFI.js → chunk-Y36LO6ZS.js} +4 -4
  13. package/dist/{chunk-Q7YGZUAE.js → chunk-Y5DAJYYE.js} +4 -6
  14. package/dist/{chunk-GOV2RGHZ.js → chunk-YTYD266U.js} +0 -151
  15. package/dist/{classify-entries-ZI2ITP37.js → classify-entries-HFQTEO3X.js} +3 -3
  16. package/dist/cli-main.d.ts +1 -1
  17. package/dist/cli-main.js +4618 -3948
  18. package/dist/{config-2UMKE6BC.js → config-GUTSLBP4.js} +3 -1
  19. package/dist/edge/openclaw/index.d.ts +1 -1
  20. package/dist/edge/openclaw/index.js +1077 -1046
  21. package/dist/{eval-DLXPSFW5.js → eval-C2DQXIT4.js} +7 -7
  22. package/dist/{ingestion-M2BPZE5M.js → ingestion-N24I2BH3.js} +6 -6
  23. package/dist/modules/surgeon/adapters/prompts/passes/auto.md +4 -4
  24. package/dist/modules/surgeon/adapters/prompts/passes/contradictions.md +5 -5
  25. package/dist/modules/surgeon/adapters/prompts/passes/retirement.md +11 -8
  26. package/dist/modules/surgeon/adapters/prompts/system.md +3 -2
  27. package/dist/{openclaw-CCNGS56Y.js → openclaw-TEDSTLJ2.js} +5 -5
  28. package/dist/{operations-YRBFQMMG.js → operations-KETVRPYZ.js} +6 -6
  29. package/dist/{process-before-reset-recall-feedback-54DMNNO4.js → process-before-reset-recall-feedback-CGCLY6WY.js} +1 -1
  30. package/dist/{types-gds0qzmj.d.ts → types-jmyBA4zR.d.ts} +26 -10
  31. package/dist/{workflow-UBLUB6UB.js → workflow-BT24JCAZ.js} +7 -7
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.1] - 2026-03-24
4
+
5
+ ### Surgeon Health Fixes (001–006)
6
+
7
+ Six targeted fixes based on a traced production surgeon run investigation:
8
+
9
+ - **Temporary entry TTL enforcement.** Temporary entries with low importance and zero recalls auto-expire after a configurable window (default 21 days). Rebuild-aware — entries are protected after a corpus rebuild until they've had time to be recalled. Short-lived entries are excluded from merge clustering.
10
+ - **Co-recall / co-ingest split.** The co-recall edge graph now distinguishes real co-recall (entries recalled together in a session) from co-ingest (entries from the same source file). Legacy edges retyped to `co_ingested`. Quality evolution only reads `co_recalled` edges. Live recall events now carry `session_id`.
11
+ - **Quality scoring recalibration.** Replaced the quality evolution formula with a rebuild-aware, type-aware, positive-signal-only design. Grace periods per entry type (preferences 14d, decisions 60d, etc.) prevent new entries from collapsing to floor scores. Quality only goes up from evidence. Gentle importance-dampened decay for unrecalled entries. Centralized quality tier thresholds. Removed dead seed path.
12
+ - **Two-tier health forgetting metric.** Health stats now report both an actionable cleanup pool (what the surgeon would evaluate) and a mechanical auto-retire count (ultra-conservative threshold). Removed dead `lowScoreTriggerCount` config.
13
+ - **Generic subject key suppression.** Contradiction scanning and clustering now detect and suppress generic `entity/type` subject keys (e.g., `jim/decision`) that collapse unrelated entries into false-positive pairs.
14
+ - **Semantic cluster precision.** Type-aware similarity thresholds for dedup clustering — decisions and lessons require 0.75, events 0.72, facts and preferences 0.60. Prevents thematic neighborhoods from being clustered as duplicates. Type-aware diameter floor in post-union validation.
15
+
16
+ ### Other
17
+
18
+ - **`db vector-check` command.** New CLI command for diagnosing vector index health.
19
+
20
+ ## [0.14.0] - 2026-03-24
21
+
22
+ ### Surgeon — Budget Model Overhaul
23
+
24
+ - **`--budget` now means dollars, not tokens.** The cumulative token budget that double-counted context re-sends across turns is gone. The surgeon is now constrained by two things: cost (dollars) and context window (tokens). Config `surgeon.budget` is dollars per run. Default: $5.
25
+ - **Context-aware limits.** New `surgeon.contextLimit` config field and `--context-limit` CLI flag. Auto-detects from `model.contextWindow * 0.85` if not set. The surgeon stops when context is full, not from artificial token counting.
26
+ - **`costCap` removed.** Replaced by `budget`. Old `costCap` values are accepted as backward-compatible alias with deprecation warning.
27
+
28
+ ### Surgeon — Contradiction Improvements
29
+
30
+ - **`coexists` relation for `log_conflict`.** The surgeon can now log reviewed false-positive pairs as coexisting rather than forcing them into `contradicts` or `supersedes`. Coexists conflicts are auto-resolved as `keep-both` so they don't accumulate as pending conflicts and future scans skip them.
31
+ - **Improved contradiction candidate filtering.** Suppresses historical series pairs (release versions, prompt paths, roadmap snapshots) from claim divergence scanning. Prioritizes `current_state` claim divergence pairs.
32
+
33
+ ### Surgeon — Retirement Candidate Scoping
34
+
35
+ - **Scoped candidate queries.** `query_candidates` now accepts `scope` parameter: `actionable` (default) filters to entries with high retirement probability — temporary, ephemeral, todos, low-importance events, mislabeled temporal artifacts. `all` shows the full candidate pool for deep sweeps.
36
+ - **Improved candidate ordering.** Actionable scope returns temporary entries first, then ephemeral, then todos, then low-importance events. Durable permanent decisions and preferences are excluded from the default scope.
37
+ - **Two-phase retirement in auto mode.** Surgeon starts with actionable candidates, widens to full pool only if budget remains after exhausting the actionable set.
38
+
3
39
  ## [0.13.4] - 2026-03-23
4
40
 
5
41
  ### Surgeon
package/README.md CHANGED
@@ -34,7 +34,7 @@ That's it. The interactive wizard handles everything: auth setup, platform detec
34
34
  - **Extract** - An LLM reads your transcripts and pulls out structured entries. Smart filtering removes noise (tool calls, file contents, boilerplate - about 80% of a typical session) before the LLM sees it. Hedged or unverified agent claims are capped at importance 5 with an `unverified` tag.
35
35
  - **Store** - Entries get embedded and compared against existing knowledge. Near-duplicates reinforce existing entries. New information gets inserted. Online dedup catches copies in real-time.
36
36
  - **Recall** - Semantic search plus memory-aware ranking. Entries you recall often score higher. Stale entries decay. Contradicted entries get penalized.
37
- - **Surgeon** - Corpus health maintenance: deterministic cleanup, quality evolution, vector integrity checks, and LLM-assisted retirement, dedup, and contradiction resolution.
37
+ - **Surgeon** - Corpus health maintenance: deterministic cleanup, quality evolution, and LLM-assisted retirement, dedup, and contradiction resolution.
38
38
 
39
39
  ```text
40
40
  Transcript -> Filter -> Extract -> Store -> Recall
@@ -262,13 +262,13 @@ This exposes five MCP tools: `agenr_recall`, `agenr_store`, `agenr_extract`, `ag
262
262
  | `agenr watcher install` | Install background watch daemon (macOS launchd) |
263
263
  | `agenr watcher status` | Show daemon status (running/stopped, pid, watched file, recent logs) |
264
264
  | `agenr watcher logs` | Stream or show recent daemon logs |
265
- | `agenr surgeon run` | Run corpus-health maintenance: cleanup, quality evolution, vector checks, and retirement/dedup/contradictions |
265
+ | `agenr surgeon run` | Run corpus-health maintenance: cleanup, quality evolution, and retirement/dedup/contradictions |
266
266
  | `agenr benchmark` | Run extraction against benchmark fixtures and score results |
267
267
  | `agenr context` | Generate context file for AI tool integration |
268
- | `agenr health` | Show database health and forgetting candidates |
268
+ | `agenr health` | Show database health and cleanup status |
269
269
  | `agenr mcp` | Start MCP server (stdio) |
270
270
  | `agenr todo <subcommand>` | Manage todos in the knowledge base |
271
- | `agenr db <cmd>` | Database management (stats, version, export, reset, path, check, rebuild-index) |
271
+ | `agenr db <cmd>` | Database management (stats, version, export, reset, path, check, vector-check, rebuild-index) |
272
272
 
273
273
  Full reference: [docs/CLI.md](./docs/CLI.md) | [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
274
274
 
@@ -306,7 +306,7 @@ For the product thesis, see [docs/vision.md](./docs/vision.md).
306
306
  | Plugin install fails during wizard | Run `openclaw plugins install agenr` manually, then `openclaw gateway restart` |
307
307
  | Embeddings fail | Set `OPENAI_API_KEY` env var or `agenr config set-key openai <key>` |
308
308
  | Database locked | Wait for the active write-heavy command to finish (`agenr ingest --bulk`, `agenr surgeon run --apply`, etc.) and retry |
309
- | Recall returns nothing after force-kill | `agenr db rebuild-index` (vector index corruption) |
309
+ | Recall returns nothing after force-kill | `agenr db vector-check --rebuild` (vector shadow-table drift) |
310
310
  | Extraction fails mid-file | Retry - dedup skips already-stored entries |
311
311
  | Stale handoff entries persist | Run `agenr recall --browse --since 1d` to check, then `agenr retire --id <id>` |
312
312
  | Gateway doesn't pick up plugin | Run `openclaw gateway restart` after plugin install |
@@ -1,3 +1,7 @@
1
+ import {
2
+ toNumber,
3
+ toStringValue
4
+ } from "./chunk-QDGOYFN7.js";
1
5
  import {
2
6
  authMethodToProvider,
3
7
  normalizeProvider,
@@ -5,7 +9,7 @@ import {
5
9
  resolveModel,
6
10
  resolveModelForTask,
7
11
  resolveUserPath
8
- } from "./chunk-QI23WU2O.js";
12
+ } from "./chunk-SJN63MTD.js";
9
13
  import {
10
14
  toErrorMessage
11
15
  } from "./chunk-UUUFPTSM.js";
@@ -15,6 +19,42 @@ function sleep(ms) {
15
19
  return new Promise((resolve) => setTimeout(resolve, ms));
16
20
  }
17
21
 
22
+ // src/shared/domain/types.ts
23
+ var KNOWLEDGE_TYPES = [
24
+ "fact",
25
+ "decision",
26
+ "preference",
27
+ "todo",
28
+ "relationship",
29
+ "event",
30
+ "lesson",
31
+ "reflection"
32
+ ];
33
+ var ENTRY_TYPES = KNOWLEDGE_TYPES;
34
+ var IMPORTANCE_MIN = 1;
35
+ var IMPORTANCE_MAX = 10;
36
+ var EXPIRY_LEVELS = ["core", "permanent", "temporary"];
37
+ var SCOPE_LEVELS = ["private", "personal", "public"];
38
+ var KNOWLEDGE_PLATFORMS = ["openclaw", "claude-code", "codex", "plaud"];
39
+ var ENTRY_KINDS = ["directive", "state", "identity", "episode", "reference"];
40
+ var TEMPORAL_CLASSES = ["ephemeral", "durable"];
41
+ var REJECTED_CONFLICT_ENTRY_ID = "rejected";
42
+
43
+ // src/shared/domain/subject-key.ts
44
+ var ENTRY_TYPE_ATTRIBUTES = new Set(KNOWLEDGE_TYPES);
45
+ function isGenericSubjectKey(subjectKey, entryType) {
46
+ const normalizedSubjectKey = subjectKey?.trim().toLowerCase();
47
+ const normalizedEntryType = entryType.trim().toLowerCase();
48
+ if (!normalizedSubjectKey || !normalizedEntryType || !ENTRY_TYPE_ATTRIBUTES.has(normalizedEntryType)) {
49
+ return false;
50
+ }
51
+ const slashIndex = normalizedSubjectKey.lastIndexOf("/");
52
+ if (slashIndex < 0 || slashIndex === normalizedSubjectKey.length - 1) {
53
+ return false;
54
+ }
55
+ return normalizedSubjectKey.slice(slashIndex + 1) === normalizedEntryType;
56
+ }
57
+
18
58
  // src/modules/store/domain/structured-claim.ts
19
59
  var STRUCTURED_CLAIM_IDENTITY_TYPES = /* @__PURE__ */ new Set(["fact", "preference", "decision"]);
20
60
  var STRUCTURED_CLAIM_CONFIDENCE_FLOOR = 0.8;
@@ -109,6 +149,48 @@ var TRANSITION_MULTI_STEP_MARKERS = [
109
149
  /\bafter that\b/i,
110
150
  /\bthen\b.{0,24}\b(?:move|moved|switch|switched|replace|replaced|migrate|migrated|transition|transitioned)\b/i
111
151
  ];
152
+ var TOPIC_SLUG_STOP_WORDS = /* @__PURE__ */ new Set([
153
+ "the",
154
+ "a",
155
+ "an",
156
+ "for",
157
+ "and",
158
+ "or",
159
+ "to",
160
+ "in",
161
+ "of",
162
+ "on",
163
+ "at",
164
+ "by",
165
+ "with",
166
+ "from",
167
+ "that",
168
+ "this",
169
+ "is",
170
+ "was",
171
+ "are",
172
+ "were",
173
+ "be",
174
+ "been",
175
+ "has",
176
+ "have",
177
+ "had",
178
+ "it",
179
+ "its",
180
+ "create",
181
+ "creating",
182
+ "document",
183
+ "documenting",
184
+ "turn",
185
+ "turning",
186
+ "into",
187
+ "concrete",
188
+ "investigate",
189
+ "investigating"
190
+ ]);
191
+ var TOPIC_SLUG_MAX_TOKENS = 6;
192
+ var TOPIC_SLUG_MAX_LENGTH = 60;
193
+ var TOPIC_SLUG_MIN_LENGTH = 3;
112
194
  function normalizeLower(value) {
113
195
  return value?.trim().toLowerCase() ?? "";
114
196
  }
@@ -325,6 +407,34 @@ function buildSubjectKey(subjectEntity, subjectAttribute, fallback) {
325
407
  }
326
408
  return fallback;
327
409
  }
410
+ function extractTopicSlug(text) {
411
+ if (!text || text.trim().length === 0) {
412
+ return null;
413
+ }
414
+ const tokens = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !TOPIC_SLUG_STOP_WORDS.has(token));
415
+ const slug = tokens.slice(0, TOPIC_SLUG_MAX_TOKENS).join("_").slice(0, TOPIC_SLUG_MAX_LENGTH);
416
+ return slug.length >= TOPIC_SLUG_MIN_LENGTH ? slug : null;
417
+ }
418
+ function deriveTopicAttribute(entry, fallback) {
419
+ const normalizedFallback = normalizeLower(fallback);
420
+ const normalizedType = normalizeLower(entry.type);
421
+ const objectSlug = extractTopicSlug(entry.claimObject);
422
+ if (objectSlug && objectSlug !== normalizedFallback && objectSlug !== normalizedType) {
423
+ return objectSlug;
424
+ }
425
+ const subjectSlug = extractTopicSlug(entry.subject);
426
+ if (subjectSlug && subjectSlug !== normalizedFallback && subjectSlug !== normalizedType) {
427
+ return subjectSlug;
428
+ }
429
+ return fallback;
430
+ }
431
+ function resolveSubjectAttribute(entry, subjectEntity, subjectAttribute, fallbackSubjectKey) {
432
+ const subjectKey = buildSubjectKey(subjectEntity, subjectAttribute, fallbackSubjectKey);
433
+ if (!isGenericSubjectKey(subjectKey, entry.type)) {
434
+ return subjectAttribute;
435
+ }
436
+ return deriveTopicAttribute(entry, subjectAttribute);
437
+ }
328
438
  function normalizeStructuredClaimFields(entry) {
329
439
  const normalizedSubjectEntity = normalizeLower(entry.subjectEntity);
330
440
  const normalizedSubjectKey = normalizeLower(entry.subjectKey);
@@ -341,6 +451,16 @@ function normalizeStructuredClaimFields(entry) {
341
451
  claimRole: entry.claimRole
342
452
  });
343
453
  const normalizedAttribute = detectedState.normalizedAttribute ?? subjectAttribute;
454
+ const resolvedAttribute = resolveSubjectAttribute(
455
+ {
456
+ type: entry.type,
457
+ subject: entry.subject,
458
+ claimObject: entry.claimObject
459
+ },
460
+ subjectEntity,
461
+ normalizedAttribute,
462
+ normalizedSubjectKey
463
+ );
344
464
  const transitionClaim = detectedState.role ? void 0 : detectTransitionClaim({
345
465
  content: entry.content,
346
466
  claimObject: entry.claimObject,
@@ -349,11 +469,11 @@ function normalizeStructuredClaimFields(entry) {
349
469
  if (subjectEntity) {
350
470
  entry.subjectEntity = subjectEntity;
351
471
  }
352
- if (normalizedAttribute) {
353
- entry.subjectAttribute = normalizedAttribute;
472
+ if (resolvedAttribute) {
473
+ entry.subjectAttribute = resolvedAttribute;
354
474
  }
355
- if (subjectEntity || normalizedAttribute) {
356
- entry.subjectKey = buildSubjectKey(subjectEntity, normalizedAttribute, normalizedSubjectKey);
475
+ if (subjectEntity || resolvedAttribute) {
476
+ entry.subjectKey = buildSubjectKey(subjectEntity, resolvedAttribute, normalizedSubjectKey);
357
477
  }
358
478
  if (transitionClaim) {
359
479
  entry.claimPredicate = STATE_TRANSITION_PREDICATE;
@@ -1382,7 +1502,7 @@ var SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL = `
1382
1502
  `;
1383
1503
  var TEMP_REPAIR_TABLE = "_vec_repair";
1384
1504
  var REPAIR_CHUNK_SIZE = 100;
1385
- function toNumber(value) {
1505
+ function toNumber2(value) {
1386
1506
  if (typeof value === "number") {
1387
1507
  return value;
1388
1508
  }
@@ -1395,14 +1515,14 @@ function toNumber(value) {
1395
1515
  return Number.NaN;
1396
1516
  }
1397
1517
  function toFiniteCount(value) {
1398
- const numeric = toNumber(value);
1518
+ const numeric = toNumber2(value);
1399
1519
  if (!Number.isFinite(numeric)) {
1400
1520
  return 0;
1401
1521
  }
1402
1522
  return Math.max(0, Math.trunc(numeric));
1403
1523
  }
1404
1524
  function normalizeRowid(value) {
1405
- const numeric = toNumber(value);
1525
+ const numeric = toNumber2(value);
1406
1526
  if (!Number.isFinite(numeric)) {
1407
1527
  return null;
1408
1528
  }
@@ -1416,6 +1536,9 @@ function formatShadowMismatch(stats) {
1416
1536
  }
1417
1537
  return details.join(", ");
1418
1538
  }
1539
+ function formatIntegrityDetail(embeddingCount, shadowCount, missingRowids) {
1540
+ return `active=${embeddingCount}, shadow=${shadowCount}, missing=${missingRowids}`;
1541
+ }
1419
1542
  async function rollbackQuietly(db) {
1420
1543
  try {
1421
1544
  await db.execute("ROLLBACK");
@@ -1508,6 +1631,18 @@ async function getVectorIndexShadowStats(db) {
1508
1631
  missingRowids
1509
1632
  };
1510
1633
  }
1634
+ async function checkVectorIntegrity(db) {
1635
+ const stats = await getVectorIndexShadowStats(db);
1636
+ const missingRowids = stats.missingRowids.length;
1637
+ return {
1638
+ drifted: stats.embeddingCount !== stats.shadowCount || missingRowids > 0,
1639
+ detail: formatIntegrityDetail(stats.embeddingCount, stats.shadowCount, missingRowids),
1640
+ activeEntries: stats.embeddingCount,
1641
+ shadowRows: stats.shadowCount,
1642
+ missingRowids,
1643
+ missingRowidValues: stats.missingRowids
1644
+ };
1645
+ }
1511
1646
  async function rebuildVectorIndex(db, options) {
1512
1647
  const start = Date.now();
1513
1648
  const onLog = options?.onLog ?? (() => void 0);
@@ -1645,6 +1780,221 @@ async function getBulkIngestMeta(db) {
1645
1780
  }
1646
1781
  }
1647
1782
 
1783
+ // src/shared/infrastructure/db/co-recall.ts
1784
+ var DEFAULT_EDGE_INCREMENT = 0.1;
1785
+ var MAX_USED_ENTRIES = 20;
1786
+ var CO_RECALL_EDGE_TYPE = "co_recalled";
1787
+ var CO_INGEST_EDGE_TYPE = "co_ingested";
1788
+ var ALL_EDGE_TYPES = [CO_RECALL_EDGE_TYPE, CO_INGEST_EDGE_TYPE];
1789
+ var ALL_EDGE_TYPES_PLACEHOLDERS = ALL_EDGE_TYPES.map(() => "?").join(", ");
1790
+ function normalizePair(a, b) {
1791
+ return a < b ? [a, b] : [b, a];
1792
+ }
1793
+ async function strengthenEdges(db, usedEntryIds, timestamp2, edgeType) {
1794
+ const uniqueIds = Array.from(
1795
+ new Set(
1796
+ usedEntryIds.map((id) => id.trim()).filter((id) => id.length > 0)
1797
+ )
1798
+ ).slice(0, MAX_USED_ENTRIES);
1799
+ if (uniqueIds.length < 2) {
1800
+ return;
1801
+ }
1802
+ const now = timestamp2 || (/* @__PURE__ */ new Date()).toISOString();
1803
+ const pairs = [];
1804
+ for (let i = 0; i < uniqueIds.length; i += 1) {
1805
+ const a = uniqueIds[i];
1806
+ if (!a) {
1807
+ continue;
1808
+ }
1809
+ for (let j = i + 1; j < uniqueIds.length; j += 1) {
1810
+ const b = uniqueIds[j];
1811
+ if (!b || a === b) {
1812
+ continue;
1813
+ }
1814
+ pairs.push(normalizePair(a, b));
1815
+ }
1816
+ }
1817
+ if (pairs.length === 0) {
1818
+ return;
1819
+ }
1820
+ await db.execute("BEGIN");
1821
+ try {
1822
+ for (const [entryA, entryB] of pairs) {
1823
+ await db.execute({
1824
+ sql: `
1825
+ INSERT INTO co_recall_edges (
1826
+ entry_a, entry_b, edge_type, weight, session_count, last_co_recalled, created_at
1827
+ )
1828
+ VALUES (?, ?, ?, ?, 1, ?, ?)
1829
+ ON CONFLICT
1830
+ DO UPDATE SET
1831
+ weight = MIN(co_recall_edges.weight + excluded.weight, 1.0),
1832
+ session_count = co_recall_edges.session_count + 1,
1833
+ last_co_recalled = excluded.last_co_recalled
1834
+ `,
1835
+ args: [
1836
+ entryA,
1837
+ entryB,
1838
+ edgeType,
1839
+ DEFAULT_EDGE_INCREMENT,
1840
+ now,
1841
+ now
1842
+ ]
1843
+ });
1844
+ }
1845
+ await db.execute("COMMIT");
1846
+ } catch (error) {
1847
+ try {
1848
+ await db.execute("ROLLBACK");
1849
+ } catch {
1850
+ }
1851
+ throw error;
1852
+ }
1853
+ }
1854
+ async function strengthenCoRecallEdges(db, usedEntryIds, timestamp2) {
1855
+ await strengthenEdges(db, usedEntryIds, timestamp2, CO_RECALL_EDGE_TYPE);
1856
+ }
1857
+ async function strengthenCoIngestEdges(db, usedEntryIds, timestamp2) {
1858
+ await strengthenEdges(db, usedEntryIds, timestamp2, CO_INGEST_EDGE_TYPE);
1859
+ }
1860
+ async function getCoRecallNeighbors(db, entryId, minWeight = 0.1, limit = 10, edgeType = CO_RECALL_EDGE_TYPE) {
1861
+ const normalizedId = entryId.trim();
1862
+ if (!normalizedId) {
1863
+ return [];
1864
+ }
1865
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
1866
+ const safeMinWeight = Number.isFinite(minWeight) ? Math.max(minWeight, 0) : 0.1;
1867
+ const result = await db.execute({
1868
+ sql: `
1869
+ SELECT
1870
+ CASE WHEN entry_a = ? THEN entry_b ELSE entry_a END AS neighbor_id,
1871
+ weight,
1872
+ session_count,
1873
+ last_co_recalled
1874
+ FROM co_recall_edges
1875
+ WHERE edge_type = ?
1876
+ AND (entry_a = ? OR entry_b = ?)
1877
+ AND weight >= ?
1878
+ ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
1879
+ LIMIT ?
1880
+ `,
1881
+ args: [normalizedId, edgeType, normalizedId, normalizedId, safeMinWeight, safeLimit]
1882
+ });
1883
+ return result.rows.map((row) => ({
1884
+ entryId: toStringValue(row.neighbor_id),
1885
+ weight: toNumber(row.weight),
1886
+ sessionCount: toNumber(row.session_count),
1887
+ lastCoRecalled: toStringValue(row.last_co_recalled)
1888
+ }));
1889
+ }
1890
+ async function getCoRecallEdgeCounts(db, edgeType = CO_RECALL_EDGE_TYPE) {
1891
+ const result = await db.execute({
1892
+ sql: `
1893
+ SELECT entry_id, COUNT(*) AS edge_count
1894
+ FROM (
1895
+ SELECT entry_a AS entry_id
1896
+ FROM co_recall_edges
1897
+ WHERE edge_type = ?
1898
+ UNION ALL
1899
+ SELECT entry_b AS entry_id
1900
+ FROM co_recall_edges
1901
+ WHERE edge_type = ?
1902
+ )
1903
+ GROUP BY entry_id
1904
+ `,
1905
+ args: [edgeType, edgeType]
1906
+ });
1907
+ const counts = /* @__PURE__ */ new Map();
1908
+ for (const row of result.rows) {
1909
+ const entryId = toStringValue(row.entry_id).trim();
1910
+ if (!entryId) {
1911
+ continue;
1912
+ }
1913
+ counts.set(entryId, Math.max(0, toNumber(row.edge_count)));
1914
+ }
1915
+ return counts;
1916
+ }
1917
+ async function getTopCoRecallEdges(db, limit = 20, edgeType = CO_RECALL_EDGE_TYPE) {
1918
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
1919
+ const result = await db.execute({
1920
+ sql: `
1921
+ SELECT entry_a, entry_b, weight, session_count, last_co_recalled
1922
+ FROM co_recall_edges
1923
+ WHERE edge_type = ?
1924
+ ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
1925
+ LIMIT ?
1926
+ `,
1927
+ args: [edgeType, safeLimit]
1928
+ });
1929
+ return result.rows.map((row) => ({
1930
+ entryA: toStringValue(row.entry_a),
1931
+ entryB: toStringValue(row.entry_b),
1932
+ weight: toNumber(row.weight),
1933
+ sessionCount: toNumber(row.session_count),
1934
+ lastCoRecalled: toStringValue(row.last_co_recalled)
1935
+ }));
1936
+ }
1937
+
1938
+ // src/shared/infrastructure/db/meta.ts
1939
+ var LAST_CORPUS_REBUILD_AT_META_KEY = "last_corpus_rebuild_at";
1940
+ function normalizeIsoTimestamp(value) {
1941
+ if (value instanceof Date) {
1942
+ return value.toISOString();
1943
+ }
1944
+ const parsed = Date.parse(value);
1945
+ if (!Number.isFinite(parsed)) {
1946
+ throw new Error(`Invalid ISO timestamp: ${value}`);
1947
+ }
1948
+ return new Date(parsed).toISOString();
1949
+ }
1950
+ async function getMetaValue(db, key) {
1951
+ const result = await db.execute({
1952
+ sql: "SELECT value FROM _meta WHERE key = ? LIMIT 1",
1953
+ args: [key]
1954
+ });
1955
+ const raw = result.rows[0];
1956
+ const value = raw?.value ?? (raw ? Object.values(raw)[0] : void 0);
1957
+ if (typeof value !== "string") {
1958
+ return null;
1959
+ }
1960
+ const trimmed = value.trim();
1961
+ return trimmed.length > 0 ? trimmed : null;
1962
+ }
1963
+ async function setMetaValue(db, input) {
1964
+ const updatedAt = normalizeIsoTimestamp(input.updatedAt ?? /* @__PURE__ */ new Date());
1965
+ await db.execute({
1966
+ sql: `
1967
+ INSERT INTO _meta (key, value, updated_at)
1968
+ VALUES (?, ?, ?)
1969
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
1970
+ `,
1971
+ args: [input.key, input.value, updatedAt]
1972
+ });
1973
+ }
1974
+ async function getLastCorpusRebuildAt(db) {
1975
+ return getMetaValue(db, LAST_CORPUS_REBUILD_AT_META_KEY);
1976
+ }
1977
+ async function setLastCorpusRebuildAt(db, value) {
1978
+ const normalized = normalizeIsoTimestamp(value);
1979
+ await setMetaValue(db, {
1980
+ key: LAST_CORPUS_REBUILD_AT_META_KEY,
1981
+ value: normalized,
1982
+ updatedAt: normalized
1983
+ });
1984
+ }
1985
+ async function initializeLastCorpusRebuildAtForEmptyCorpus(db, at = /* @__PURE__ */ new Date()) {
1986
+ const existing = await getLastCorpusRebuildAt(db);
1987
+ if (existing) {
1988
+ return;
1989
+ }
1990
+ const result = await db.execute("SELECT COUNT(*) AS count FROM entries");
1991
+ const count = Number(result.rows[0]?.count ?? 0);
1992
+ if (count > 0) {
1993
+ return;
1994
+ }
1995
+ await setLastCorpusRebuildAt(db, at);
1996
+ }
1997
+
1648
1998
  // src/shared/infrastructure/db/migrations/reflection-importance-cap.ts
1649
1999
  var REFLECTION_IMPORTANCE_CAP_META_KEY = "reflection_importance_cap_v1";
1650
2000
  async function applyReflectionImportanceCapMigration(db) {
@@ -2014,6 +2364,7 @@ async function migrateRecallEvents(db) {
2014
2364
 
2015
2365
  // src/shared/infrastructure/db/schema/init.ts
2016
2366
  var LEGACY_IMPORTANCE_BACKFILL_META_KEY = "legacy_importance_backfill_from_confidence_v1";
2367
+ var CO_INGEST_EDGE_RETYPE_META_KEY = "co_recall_edges_retyped_to_co_ingested_v1";
2017
2368
  function readColumnNames(rows) {
2018
2369
  return new Set(rows.map((row) => String(row.name ?? "")));
2019
2370
  }
@@ -2134,6 +2485,36 @@ async function backfillLegacyImportanceFromConfidence(client) {
2134
2485
  } catch {
2135
2486
  }
2136
2487
  }
2488
+ async function retypeLegacyCoRecallEdges(client) {
2489
+ const edgeInfo = await client.execute("PRAGMA table_info(co_recall_edges)");
2490
+ const edgeColumns = readColumnNames(edgeInfo.rows);
2491
+ if (!edgeColumns.has("edge_type")) {
2492
+ return;
2493
+ }
2494
+ const sentinel = await client.execute({
2495
+ sql: "SELECT 1 AS found FROM _meta WHERE key = ? LIMIT 1",
2496
+ args: [CO_INGEST_EDGE_RETYPE_META_KEY]
2497
+ });
2498
+ if (sentinel.rows.length > 0) {
2499
+ return;
2500
+ }
2501
+ await client.execute({
2502
+ sql: `
2503
+ UPDATE co_recall_edges
2504
+ SET edge_type = ?
2505
+ WHERE edge_type = ?
2506
+ `,
2507
+ args: [CO_INGEST_EDGE_TYPE, CO_RECALL_EDGE_TYPE]
2508
+ });
2509
+ await client.execute({
2510
+ sql: `
2511
+ INSERT INTO _meta (key, value, updated_at)
2512
+ VALUES (?, datetime('now'), datetime('now'))
2513
+ ON CONFLICT(key) DO NOTHING
2514
+ `,
2515
+ args: [CO_INGEST_EDGE_RETYPE_META_KEY]
2516
+ });
2517
+ }
2137
2518
  async function stampSchemaMetadata(client) {
2138
2519
  await client.execute({
2139
2520
  sql: `
@@ -2190,11 +2571,13 @@ async function initSchema(client) {
2190
2571
  await applyReflectionImportanceCapMigration(client);
2191
2572
  await applyReflectionRemovalMigration(client);
2192
2573
  await backfillLegacyImportanceFromConfidence(client);
2574
+ await retypeLegacyCoRecallEdges(client);
2193
2575
  for (const statement of CREATE_INDEX_STATEMENTS) {
2194
2576
  await client.execute(statement);
2195
2577
  }
2196
2578
  await repairVectorIndexIfNeeded(client);
2197
2579
  await stampSchemaMetadata(client);
2580
+ await initializeLastCorpusRebuildAtForEmptyCorpus(client);
2198
2581
  }
2199
2582
 
2200
2583
  // src/shared/infrastructure/db/schema/reset.ts
@@ -2940,6 +3323,17 @@ function resolveDefaultAppRuntimeDeps() {
2940
3323
  }
2941
3324
 
2942
3325
  export {
3326
+ KNOWLEDGE_TYPES,
3327
+ ENTRY_TYPES,
3328
+ IMPORTANCE_MIN,
3329
+ IMPORTANCE_MAX,
3330
+ EXPIRY_LEVELS,
3331
+ SCOPE_LEVELS,
3332
+ KNOWLEDGE_PLATFORMS,
3333
+ ENTRY_KINDS,
3334
+ TEMPORAL_CLASSES,
3335
+ REJECTED_CONFLICT_ENTRY_ID,
3336
+ isGenericSubjectKey,
2943
3337
  STRUCTURED_CLAIM_IDENTITY_TYPES,
2944
3338
  STATE_ANCHOR_PREDICATE,
2945
3339
  STATE_TRANSITION_PREDICATE,
@@ -2958,13 +3352,23 @@ export {
2958
3352
  createLogger,
2959
3353
  OPENCLAW_PLUGIN_SCHEMA_STATEMENTS,
2960
3354
  OPENCLAW_PLUGIN_COLUMN_MIGRATIONS,
2961
- getVectorIndexShadowStats,
3355
+ checkVectorIntegrity,
2962
3356
  rebuildVectorIndex,
2963
3357
  dropFtsTriggersAndIndex,
2964
3358
  rebuildFtsAndTriggers,
2965
3359
  rebuildVectorIndex2,
2966
3360
  setBulkIngestMeta,
2967
3361
  clearBulkIngestMeta,
3362
+ MAX_USED_ENTRIES,
3363
+ CO_RECALL_EDGE_TYPE,
3364
+ CO_INGEST_EDGE_TYPE,
3365
+ strengthenCoRecallEdges,
3366
+ strengthenCoIngestEdges,
3367
+ getCoRecallNeighbors,
3368
+ getCoRecallEdgeCounts,
3369
+ getTopCoRecallEdges,
3370
+ getLastCorpusRebuildAt,
3371
+ setLastCorpusRebuildAt,
2968
3372
  resetDb,
2969
3373
  DEFAULT_DB_PATH,
2970
3374
  buildBackupPath,
@@ -4,7 +4,7 @@ import {
4
4
  getPendingReviews,
5
5
  rehabilitateEntry,
6
6
  resolveReview
7
- } from "./chunk-BIND34BY.js";
7
+ } from "./chunk-LLHGEYS7.js";
8
8
  import {
9
9
  classifyStructuredConflictPair,
10
10
  deleteCoRecallEdgesForEntryIds,
@@ -12,24 +12,23 @@ import {
12
12
  getConflictStats,
13
13
  resolveConflictLog,
14
14
  retireEntries
15
- } from "./chunk-NGV264GF.js";
16
- import {
17
- getCoRecallNeighbors,
18
- getTopCoRecallEdges
19
- } from "./chunk-GOV2RGHZ.js";
15
+ } from "./chunk-FUOKOMSN.js";
20
16
  import {
21
17
  DEFAULT_DB_PATH,
22
18
  backupDb,
23
19
  buildBackupPath,
20
+ checkVectorIntegrity,
24
21
  closeDb,
25
22
  createLogger,
23
+ getCoRecallNeighbors,
26
24
  getDb,
25
+ getTopCoRecallEdges,
27
26
  initDb,
28
27
  rebuildVectorIndex,
29
28
  resetDb,
30
29
  resolveDefaultAppRuntimeDeps,
31
30
  walCheckpoint
32
- } from "./chunk-WB6OH7LM.js";
31
+ } from "./chunk-2JI5DOZ2.js";
33
32
  import {
34
33
  toNumber,
35
34
  toStringValue
@@ -37,7 +36,7 @@ import {
37
36
  import {
38
37
  readConfig,
39
38
  resolveConfigDir
40
- } from "./chunk-QI23WU2O.js";
39
+ } from "./chunk-SJN63MTD.js";
41
40
 
42
41
  // src/runtime/operations.ts
43
42
  import fs from "fs/promises";
@@ -525,6 +524,7 @@ function resolveDbCommandDefaults() {
525
524
  initDb: shared.initDbFn,
526
525
  closeDb: shared.closeDbFn,
527
526
  backupDb,
527
+ checkVectorIntegrity,
528
528
  getConflictShadowSummary,
529
529
  resetDb,
530
530
  walCheckpoint,
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  resolveDbCommandDefaults
3
- } from "./chunk-EAIHUY6F.js";
3
+ } from "./chunk-4JHTJXYY.js";
4
4
  import {
5
5
  resolveConfigDir,
6
6
  resolveDefaultKnowledgeDbPath,
7
7
  resolveUserPath
8
- } from "./chunk-QI23WU2O.js";
8
+ } from "./chunk-SJN63MTD.js";
9
9
 
10
10
  // src/shared/operations/session-store-metrics/workflow.ts
11
11
  import fs from "fs/promises";