agenr 0.7.1 → 0.7.2

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.2] - 2026-02-20
4
+
5
+ ### Fixed
6
+ - fix(store): within-batch deduplication - entries with the same subject+type+source file in a single storeEntries() call are now deduplicated before processing, preventing same-batch signal duplicates (entries from different source files with the same subject are kept as distinct)
7
+ - fix(store): re-extraction guard - entries with the same subject+type+source_file extracted within 24 hours now increment confirmations instead of adding a new entry
8
+ - fix(mcp): append-only MCP access log at ~/.agenr/mcp-access.log for observability of agenr_recall and agenr_store tool calls
9
+
3
10
  ## [0.7.1] - 2026-02-20
4
11
 
5
12
  ### Added
package/dist/cli-main.js CHANGED
@@ -4469,6 +4469,7 @@ var AUTO_SKIP_THRESHOLD = 0.95;
4469
4469
  var SMART_DEDUP_THRESHOLD = 0.88;
4470
4470
  var DEFAULT_DEDUP_THRESHOLD = 0.8;
4471
4471
  var DEFAULT_SIMILAR_LIMIT = 5;
4472
+ var RECENCY_DEDUP_HOURS = 24;
4472
4473
  var CANONICAL_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+){2,4}$/;
4473
4474
  var CROSS_TYPE_TODO_SUPERSEDE_TYPES = /* @__PURE__ */ new Set(["event", "fact", "decision"]);
4474
4475
  var TODO_COMPLETION_SIGNALS = ["done", "fixed", "resolved", "completed", "shipped", "closed", "merged"];
@@ -5066,6 +5067,47 @@ async function findEntryByTypeAndCanonicalKey(db, type, canonicalKey) {
5066
5067
  const tagsById = await getTagsForEntryIds2(db, [entryId]);
5067
5068
  return mapStoredEntry2(row, tagsById.get(entryId) ?? []);
5068
5069
  }
5070
+ async function findRecentEntryBySubjectTypeAndSourceFile(db, normalizedSubject, type, sourceFile, withinHours) {
5071
+ const result = await db.execute({
5072
+ sql: `
5073
+ SELECT
5074
+ id,
5075
+ type,
5076
+ subject,
5077
+ canonical_key,
5078
+ content,
5079
+ importance,
5080
+ expiry,
5081
+ source_file,
5082
+ source_context,
5083
+ embedding,
5084
+ created_at,
5085
+ updated_at,
5086
+ last_recalled_at,
5087
+ recall_count,
5088
+ confirmations,
5089
+ contradictions,
5090
+ superseded_by
5091
+ FROM entries e
5092
+ WHERE lower(trim(e.subject)) = ?
5093
+ AND e.type = ?
5094
+ AND e.source_file = ?
5095
+ AND e.retired = 0
5096
+ AND e.superseded_by IS NULL
5097
+ AND e.created_at > datetime('now', '-' || ? || ' hours')
5098
+ ORDER BY e.rowid DESC
5099
+ LIMIT 1
5100
+ `,
5101
+ args: [normalizedSubject, type, sourceFile, withinHours]
5102
+ });
5103
+ const row = result.rows[0];
5104
+ if (!row) {
5105
+ return null;
5106
+ }
5107
+ const entryId = toStringValue2(row.id);
5108
+ const tagsById = await getTagsForEntryIds2(db, [entryId]);
5109
+ return mapStoredEntry2(row, tagsById.get(entryId) ?? []);
5110
+ }
5069
5111
  async function findActiveTodoByCanonicalKey(db, canonicalKey) {
5070
5112
  return findEntryByTypeAndCanonicalKey(db, "todo", canonicalKey);
5071
5113
  }
@@ -5474,6 +5516,28 @@ async function storeEntries(db, entries, apiKey, options = {}) {
5474
5516
  let superseded = 0;
5475
5517
  let relationsCreated = 0;
5476
5518
  let llmDedupCalls = 0;
5519
+ if (!options.force && !onlineDedup) {
5520
+ const seen = /* @__PURE__ */ new Map();
5521
+ for (let i = 0; i < entries.length; i += 1) {
5522
+ const key = `${entries[i].subject.trim().toLowerCase()}:${entries[i].type}:${entries[i].source.file}`;
5523
+ seen.set(key, i);
5524
+ }
5525
+ for (let i = 0; i < entries.length; i += 1) {
5526
+ const key = `${entries[i].subject.trim().toLowerCase()}:${entries[i].type}:${entries[i].source.file}`;
5527
+ if (seen.get(key) !== i) {
5528
+ skipped += 1;
5529
+ options.onDecision?.({
5530
+ entry: entries[i],
5531
+ action: "skipped",
5532
+ reason: "within-batch duplicate (same subject+type+source.file)"
5533
+ });
5534
+ }
5535
+ }
5536
+ entries = entries.filter((entry, i) => {
5537
+ const key = `${entry.subject.trim().toLowerCase()}:${entry.type}:${entry.source.file}`;
5538
+ return seen.get(key) === i;
5539
+ });
5540
+ }
5477
5541
  const processOne = async (entry) => {
5478
5542
  const normalizedEntry = {
5479
5543
  ...entry,
@@ -5489,6 +5553,28 @@ async function storeEntries(db, entries, apiKey, options = {}) {
5489
5553
  });
5490
5554
  return;
5491
5555
  }
5556
+ if (!options.force && !onlineDedup && normalizedEntry.source.file) {
5557
+ const recentMatch = await findRecentEntryBySubjectTypeAndSourceFile(
5558
+ db,
5559
+ normalizedEntry.subject.trim().toLowerCase(),
5560
+ normalizedEntry.type,
5561
+ normalizedEntry.source.file,
5562
+ RECENCY_DEDUP_HOURS
5563
+ );
5564
+ if (recentMatch) {
5565
+ await incrementConfirmations(db, recentMatch.id, contentHash);
5566
+ updated += 1;
5567
+ options.onDecision?.({
5568
+ entry: normalizedEntry,
5569
+ action: "updated",
5570
+ reason: "re-extraction guard: same subject+type+source within 24h",
5571
+ matchedEntryId: recentMatch.id,
5572
+ matchedEntry: recentMatch,
5573
+ sameSubject: true
5574
+ });
5575
+ return;
5576
+ }
5577
+ }
5492
5578
  if (!options.force && normalizedEntry.canonical_key) {
5493
5579
  const canonicalMatch = await findEntryByTypeAndCanonicalKey(db, normalizedEntry.type, normalizedEntry.canonical_key);
5494
5580
  if (canonicalMatch) {
@@ -13462,6 +13548,14 @@ import os17 from "os";
13462
13548
  import path29 from "path";
13463
13549
  import * as readline from "readline";
13464
13550
  import { once } from "events";
13551
+ async function appendMcpLog(line) {
13552
+ try {
13553
+ const logPath = path29.join(os17.homedir(), ".agenr", "mcp-access.log");
13554
+ const entry = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...line }) + "\n";
13555
+ await fs30.appendFile(logPath, entry, "utf8");
13556
+ } catch {
13557
+ }
13558
+ }
13465
13559
  var MCP_PROTOCOL_VERSION = "2024-11-05";
13466
13560
  var JSON_RPC_PARSE_ERROR = -32700;
13467
13561
  var JSON_RPC_INVALID_REQUEST = -32600;
@@ -14277,13 +14371,31 @@ function createMcpServer(options = {}, deps = {}) {
14277
14371
  async function dispatchToolCall(params) {
14278
14372
  try {
14279
14373
  if (params.name === "agenr_recall") {
14374
+ const result = await callRecallTool(params.args);
14375
+ await appendMcpLog({
14376
+ tool: "agenr_recall",
14377
+ query: typeof params.args.query === "string" ? params.args.query.slice(0, 120) : void 0,
14378
+ context: typeof params.args.context === "string" ? params.args.context : void 0,
14379
+ project: typeof params.args.project === "string" ? params.args.project : void 0
14380
+ });
14280
14381
  return {
14281
- content: [{ type: "text", text: await callRecallTool(params.args) }]
14382
+ content: [{ type: "text", text: result }]
14282
14383
  };
14283
14384
  }
14284
14385
  if (params.name === "agenr_store") {
14386
+ const result = await callStoreTool(params.args);
14387
+ const storeArgsEntries = Array.isArray(params.args.entries) ? params.args.entries : [];
14388
+ const firstEntry = storeArgsEntries[0];
14389
+ await appendMcpLog({
14390
+ tool: "agenr_store",
14391
+ count: storeArgsEntries.length,
14392
+ firstType: typeof firstEntry?.type === "string" ? firstEntry.type : void 0,
14393
+ firstSubject: typeof firstEntry?.subject === "string" ? firstEntry.subject.slice(0, 80) : void 0,
14394
+ firstImportance: typeof firstEntry?.importance === "number" ? firstEntry.importance : void 0,
14395
+ project: typeof params.args.project === "string" ? params.args.project : void 0
14396
+ });
14285
14397
  return {
14286
- content: [{ type: "text", text: await callStoreTool(params.args) }]
14398
+ content: [{ type: "text", text: result }]
14287
14399
  };
14288
14400
  }
14289
14401
  if (params.name === "agenr_extract") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"