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 +7 -0
- package/dist/cli-main.js +114 -2
- package/package.json +1 -1
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:
|
|
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:
|
|
14398
|
+
content: [{ type: "text", text: result }]
|
|
14287
14399
|
};
|
|
14288
14400
|
}
|
|
14289
14401
|
if (params.name === "agenr_extract") {
|