agenr 0.9.24 → 0.9.25

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.25 (2026-02-28)
4
+
5
+ ### Bug Fixes
6
+ - Health command now reads conflict data from conflict_log table instead of
7
+ the never-incremented entries.contradictions column. Shows total conflicts,
8
+ breakdown by relation type, pending count, and auto-resolved count. (#334)
9
+ - High-confidence coexists conflicts (>0.8) now auto-resolve instead of being
10
+ flagged for review. Previously, any coexists involving decision or lesson
11
+ types was flagged regardless of confidence, creating a false backlog. (#335)
12
+ - OpenClaw plugin: system messages (e.g. subagent completions) now classify
13
+ as trivial for mid-session recall, preventing wasted embedding API calls
14
+ on garbage queries containing session IDs and boilerplate text.
15
+
3
16
  ## 0.9.24 (2026-02-28)
4
17
 
5
18
  ### Bug Fixes
package/dist/cli-main.js CHANGED
@@ -5486,6 +5486,38 @@ async function getConflictStats(db) {
5486
5486
  userResolved: Number.isFinite(userResolvedRaw) ? userResolvedRaw : 0
5487
5487
  };
5488
5488
  }
5489
+ async function getConflictLogSummary(db) {
5490
+ const result = await db.execute({
5491
+ sql: `
5492
+ SELECT relation, resolution, COUNT(*) as cnt
5493
+ FROM conflict_log
5494
+ GROUP BY relation, resolution
5495
+ `
5496
+ });
5497
+ const byRelation = {};
5498
+ const byResolution = {};
5499
+ let total = 0;
5500
+ for (const row of result.rows) {
5501
+ const record = row;
5502
+ const relation = toStringValue(record.relation);
5503
+ const resolution = toStringValue(record.resolution);
5504
+ const countRaw = toNumber(record.cnt);
5505
+ const count = Number.isFinite(countRaw) ? countRaw : 0;
5506
+ total += count;
5507
+ if (relation) {
5508
+ byRelation[relation] = (byRelation[relation] ?? 0) + count;
5509
+ }
5510
+ if (resolution) {
5511
+ byResolution[resolution] = (byResolution[resolution] ?? 0) + count;
5512
+ }
5513
+ }
5514
+ return {
5515
+ total,
5516
+ byRelation,
5517
+ byResolution,
5518
+ pending: byResolution.pending ?? 0
5519
+ };
5520
+ }
5489
5521
 
5490
5522
  // src/db/contradiction.ts
5491
5523
  var CONTRADICTION_JUDGE_SYSTEM_PROMPT = [
@@ -5805,6 +5837,20 @@ async function resolveConflict(db, newEntryId, newEntry, conflict, subjectIndex,
5805
5837
  reason: "temporal type with high confidence supersession"
5806
5838
  };
5807
5839
  }
5840
+ if (conflict.result.relation === "coexists" && conflict.result.confidence > 0.8) {
5841
+ await logConflict(
5842
+ db,
5843
+ newEntryId,
5844
+ conflict.existingEntryId,
5845
+ "coexists",
5846
+ conflict.result.confidence,
5847
+ "coexist"
5848
+ );
5849
+ console.error(
5850
+ `[contradiction] resolution: coexist (high-confidence) entry=${conflict.existingEntryId.slice(0, 8)} (confidence=${conflict.result.confidence.toFixed(2)})`
5851
+ );
5852
+ return { action: "coexist", reason: "high-confidence coexistence" };
5853
+ }
5808
5854
  if (conflict.result.relation === "contradicts" || conflict.result.confidence <= 0.75 || conflict.existingType === "decision" || conflict.existingType === "lesson") {
5809
5855
  await logConflict(
5810
5856
  db,
@@ -17711,7 +17757,6 @@ async function collectHealthStats(db, now, fileSizeBytes, forgettingThreshold, f
17711
17757
  let recalledFivePlus = 0;
17712
17758
  let forgettingCandidates = 0;
17713
17759
  let forgettingProtected = 0;
17714
- let contradictionFlags = 0;
17715
17760
  let staleTodos = 0;
17716
17761
  let qualityHigh = 0;
17717
17762
  let qualityMedium = 0;
@@ -17749,9 +17794,6 @@ async function collectHealthStats(db, now, fileSizeBytes, forgettingThreshold, f
17749
17794
  } else {
17750
17795
  recalledFivePlus += 1;
17751
17796
  }
17752
- if (entry.contradictions > 0) {
17753
- contradictionFlags += 1;
17754
- }
17755
17797
  if (entry.type === "todo" && ageDays2 > 30 && entry.recall_count <= 0) {
17756
17798
  staleTodos += 1;
17757
17799
  }
@@ -17810,6 +17852,7 @@ async function collectHealthStats(db, now, fileSizeBytes, forgettingThreshold, f
17810
17852
  const totalPending = pendingByReason.reduce((sum, item) => sum + item.count, 0);
17811
17853
  const oldestPendingCreatedAt = await getOldestPendingReviewCreatedAt(db);
17812
17854
  const oldestPendingAgeDays = oldestPendingCreatedAt && totalPending > 0 ? parseDaysBetween(now, oldestPendingCreatedAt) : null;
17855
+ const conflictLog = await getConflictLogSummary(db);
17813
17856
  return {
17814
17857
  total,
17815
17858
  todos,
@@ -17834,10 +17877,8 @@ async function collectHealthStats(db, now, fileSizeBytes, forgettingThreshold, f
17834
17877
  estimatedFreedBytes: estimateFreedBytes2(forgettingCandidates, total, fileSizeBytes),
17835
17878
  threshold: forgettingThreshold
17836
17879
  },
17837
- consolidation: {
17838
- contradictionFlags,
17839
- staleTodos
17840
- },
17880
+ staleTodos,
17881
+ conflictLog,
17841
17882
  quality: {
17842
17883
  high: qualityHigh,
17843
17884
  medium: qualityMedium,
@@ -17871,6 +17912,12 @@ function formatAgeDays(ageDays2) {
17871
17912
  function renderHealthOutput(stats, now) {
17872
17913
  const overTarget = (stats.fileSizeBytes ?? 0) > 200 * MB2;
17873
17914
  const fileSizeNote = overTarget ? " (\u26A0 over 200MB target)" : "";
17915
+ const conflictByRelation = `coexists=${stats.conflictLog.byRelation.coexists ?? 0}, supersedes=${stats.conflictLog.byRelation.supersedes ?? 0}, contradicts=${stats.conflictLog.byRelation.contradicts ?? 0}`;
17916
+ const autoResolvedCoexist = stats.conflictLog.byResolution.coexist ?? 0;
17917
+ const autoResolvedSuperseded = stats.conflictLog.byResolution["auto-superseded"] ?? 0;
17918
+ const autoResolvedKeepBoth = stats.conflictLog.byResolution["keep-both"] ?? 0;
17919
+ const autoResolvedKeepNew = stats.conflictLog.byResolution["keep-new"] ?? 0;
17920
+ const autoResolvedTotal = autoResolvedCoexist + autoResolvedSuperseded + autoResolvedKeepBoth + autoResolvedKeepNew;
17874
17921
  const lines = [
17875
17922
  "DB Health \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
17876
17923
  `Entries: ${formatInt(stats.total)} total | ${formatInt(stats.todos)} todos | ${formatInt(stats.preferences)} preferences`,
@@ -17892,9 +17939,14 @@ function renderHealthOutput(stats, now) {
17892
17939
  `- score < ${stats.forgetting.threshold}: ${formatInt(stats.forgetting.candidates)} entries (would free ~${formatFileSize(stats.forgetting.estimatedFreedBytes)})`,
17893
17940
  `- Protected: ${formatInt(stats.forgetting.protected)} entries`,
17894
17941
  "",
17895
- "Consolidation Health",
17896
- `- Contradiction flags: ${formatInt(stats.consolidation.contradictionFlags)} entries`,
17897
- `- Stale todos (>30d old, not recalled): ${formatInt(stats.consolidation.staleTodos)}`,
17942
+ "Conflict Log",
17943
+ `- Total conflicts: ${formatInt(stats.conflictLog.total)}`,
17944
+ `- By relation: ${conflictByRelation}`,
17945
+ `- Pending resolution: ${formatInt(stats.conflictLog.pending)}`,
17946
+ `- Auto-resolved: ${formatInt(autoResolvedTotal)} (coexist=${autoResolvedCoexist}, auto-superseded=${autoResolvedSuperseded}, keep-both=${autoResolvedKeepBoth}, keep-new=${autoResolvedKeepNew})`,
17947
+ "",
17948
+ "Stale Todos",
17949
+ `- Over 30d old, not recalled: ${formatInt(stats.staleTodos)}`,
17898
17950
  "",
17899
17951
  "Quality Score Distribution",
17900
17952
  `- High (>= 0.7): ${formatInt(stats.quality.high)} entries`,
@@ -160,6 +160,9 @@ function normalizeBufferedMessage(text) {
160
160
  return "";
161
161
  }
162
162
  if (trimmed.length > MAX_BUFFERED_MESSAGE_CHARS) {
163
+ if (trimmed.startsWith("[System Message]")) {
164
+ return "trivial";
165
+ }
163
166
  return trimmed.slice(0, MAX_BUFFERED_MESSAGE_CHARS);
164
167
  }
165
168
  return trimmed;
@@ -230,6 +233,9 @@ function classifyMessage(text) {
230
233
  if (!trimmed) {
231
234
  return "trivial";
232
235
  }
236
+ if (trimmed.startsWith("[System Message]")) {
237
+ return "trivial";
238
+ }
233
239
  const words = trimmed.split(/\s+/).map((word) => word.replace(/^[.!?,;:]+|[.!?,;:]+$/g, "")).filter(Boolean);
234
240
  const wordCount = words.length;
235
241
  const lower = trimmed.toLowerCase();
@@ -272,6 +278,9 @@ function buildQuery(message) {
272
278
  return "";
273
279
  }
274
280
  return trimmed.slice(0, MAX_QUERY_CHARS);
281
+ if (trimmed.startsWith("[System Message]")) {
282
+ return "trivial";
283
+ }
275
284
  }
276
285
  function tokenizeForSimilarity(text) {
277
286
  const tokens = text.toLowerCase().split(/\s+/).map((token) => token.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "")).filter((token) => token.length > 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.9.24",
3
+ "version": "0.9.25",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"
@@ -11,6 +11,13 @@
11
11
  "bin": {
12
12
  "agenr": "dist/cli.js"
13
13
  },
14
+ "scripts": {
15
+ "build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
16
+ "dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "typecheck": "tsc --noEmit"
20
+ },
14
21
  "dependencies": {
15
22
  "@clack/prompts": "^1.0.1",
16
23
  "@libsql/client": "^0.17.0",
@@ -54,11 +61,9 @@
54
61
  "README.md"
55
62
  ],
56
63
  "author": "agenr-ai",
57
- "scripts": {
58
- "build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
59
- "dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
60
- "test": "vitest run",
61
- "test:watch": "vitest",
62
- "typecheck": "tsc --noEmit"
64
+ "pnpm": {
65
+ "overrides": {
66
+ "fast-xml-parser": "^5.3.6"
67
+ }
63
68
  }
64
- }
69
+ }