agenr 0.7.1 → 0.7.3

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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.3] - 2026-02-20
4
+
5
+ ### Added
6
+ - feat(plugin): bundled OpenClaw skill (skills/SKILL.md) - teaches agents when to call agenr_store and agenr_recall as MCP tools; automatically available when plugin is installed
7
+ - feat(plugin): complete configSchema in openclaw.plugin.json (signalMinImportance, signalMaxPerSignal, signalsEnabled, dbPath)
8
+
9
+ ### Changed
10
+ - fix(init): removed AGENTS.md auto-detection heuristic for openclaw platform - openclaw must be specified explicitly via --platform openclaw (AGENTS.md is also used by Codex; the heuristic was unreliable)
11
+ - fix(init): agenr init --platform openclaw no longer writes to AGENTS.md - the OpenClaw plugin handles memory injection via prependContext; AGENTS.md write was redundant
12
+
13
+ ### Internal
14
+ - chore(plugin): bump openclaw.plugin.json version to 0.7.3
15
+
16
+ ## [0.7.2] - 2026-02-20
17
+
18
+ ### Fixed
19
+ - 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)
20
+ - 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
21
+ - fix(mcp): append-only MCP access log at ~/.agenr/mcp-access.log for observability of agenr_recall and agenr_store tool calls
22
+
3
23
  ## [0.7.1] - 2026-02-20
4
24
 
5
25
  ### 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) {
@@ -13226,9 +13312,6 @@ async function detectPlatform(projectDir) {
13226
13312
  if (await isDirectory(path28.join(projectDir, ".cursor"))) {
13227
13313
  return "cursor";
13228
13314
  }
13229
- if (await pathExists(path28.join(projectDir, "AGENTS.md"))) {
13230
- return "openclaw";
13231
- }
13232
13315
  if (await pathExists(path28.join(projectDir, ".windsurfrules"))) {
13233
13316
  return "windsurf";
13234
13317
  }
@@ -13251,6 +13334,9 @@ async function resolveInstructionsPath(projectDir, platform) {
13251
13334
  if (platform === "codex") {
13252
13335
  return path28.join(os16.homedir(), ".codex", "AGENTS.md");
13253
13336
  }
13337
+ if (platform === "openclaw") {
13338
+ return null;
13339
+ }
13254
13340
  return path28.join(projectDir, "AGENTS.md");
13255
13341
  }
13256
13342
  function withMarkers(promptBlock) {
@@ -13400,9 +13486,13 @@ function formatInitSummary(result) {
13400
13486
  const lines = [
13401
13487
  `agenr init: platform=${result.platform} project=${result.project} dependencies=${dependencyLabel}`,
13402
13488
  `- Wrote .agenr/config.json`,
13403
- `- Wrote system prompt block to ${path28.basename(result.instructionsPath)}`,
13404
13489
  `- Wrote MCP config to ${path28.relative(result.projectDir, result.mcpPath) || path28.basename(result.mcpPath)}`
13405
13490
  ];
13491
+ if (result.instructionsPath !== null) {
13492
+ lines.splice(2, 0, `- Wrote system prompt block to ${path28.basename(result.instructionsPath)}`);
13493
+ } else {
13494
+ lines.splice(2, 0, `- Memory injection: handled automatically by ${result.platform} plugin (no instructions file needed)`);
13495
+ }
13406
13496
  if (result.gitignoreUpdated) {
13407
13497
  lines.push("- Added .agenr/knowledge.db to .gitignore");
13408
13498
  }
@@ -13430,12 +13520,14 @@ async function runInitCommand(options) {
13430
13520
  const dependencies = options.dependsOn !== void 0 ? normalizeSlugList(options.dependsOn) : void 0;
13431
13521
  const configResult = await writeAgenrConfig(projectDir, project, resolvedPlatform, dependencies);
13432
13522
  const instructionsPath = await resolveInstructionsPath(projectDir, resolvedPlatform);
13433
- await upsertPromptBlock(instructionsPath, buildSystemPromptBlock(project));
13523
+ if (instructionsPath !== null) {
13524
+ await upsertPromptBlock(instructionsPath, buildSystemPromptBlock(project));
13525
+ }
13434
13526
  const mcpPath = await writeMcpConfig(projectDir, resolvedPlatform);
13435
13527
  const gitignoreEntries = [".agenr/knowledge.db"];
13436
13528
  if (resolvedPlatform === "cursor") {
13437
13529
  gitignoreEntries.push(".cursor/rules/agenr.mdc");
13438
- if (isPathInsideProject(projectDir, instructionsPath)) {
13530
+ if (instructionsPath !== null && isPathInsideProject(projectDir, instructionsPath)) {
13439
13531
  const relativeInstructionsPath = path28.relative(projectDir, instructionsPath).split(path28.sep).join("/");
13440
13532
  gitignoreEntries.push(relativeInstructionsPath);
13441
13533
  }
@@ -13462,6 +13554,14 @@ import os17 from "os";
13462
13554
  import path29 from "path";
13463
13555
  import * as readline from "readline";
13464
13556
  import { once } from "events";
13557
+ async function appendMcpLog(line) {
13558
+ try {
13559
+ const logPath = path29.join(os17.homedir(), ".agenr", "mcp-access.log");
13560
+ const entry = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...line }) + "\n";
13561
+ await fs30.appendFile(logPath, entry, "utf8");
13562
+ } catch {
13563
+ }
13564
+ }
13465
13565
  var MCP_PROTOCOL_VERSION = "2024-11-05";
13466
13566
  var JSON_RPC_PARSE_ERROR = -32700;
13467
13567
  var JSON_RPC_INVALID_REQUEST = -32600;
@@ -14277,13 +14377,31 @@ function createMcpServer(options = {}, deps = {}) {
14277
14377
  async function dispatchToolCall(params) {
14278
14378
  try {
14279
14379
  if (params.name === "agenr_recall") {
14380
+ const result = await callRecallTool(params.args);
14381
+ await appendMcpLog({
14382
+ tool: "agenr_recall",
14383
+ query: typeof params.args.query === "string" ? params.args.query.slice(0, 120) : void 0,
14384
+ context: typeof params.args.context === "string" ? params.args.context : void 0,
14385
+ project: typeof params.args.project === "string" ? params.args.project : void 0
14386
+ });
14280
14387
  return {
14281
- content: [{ type: "text", text: await callRecallTool(params.args) }]
14388
+ content: [{ type: "text", text: result }]
14282
14389
  };
14283
14390
  }
14284
14391
  if (params.name === "agenr_store") {
14392
+ const result = await callStoreTool(params.args);
14393
+ const storeArgsEntries = Array.isArray(params.args.entries) ? params.args.entries : [];
14394
+ const firstEntry = storeArgsEntries[0];
14395
+ await appendMcpLog({
14396
+ tool: "agenr_store",
14397
+ count: storeArgsEntries.length,
14398
+ firstType: typeof firstEntry?.type === "string" ? firstEntry.type : void 0,
14399
+ firstSubject: typeof firstEntry?.subject === "string" ? firstEntry.subject.slice(0, 80) : void 0,
14400
+ firstImportance: typeof firstEntry?.importance === "number" ? firstEntry.importance : void 0,
14401
+ project: typeof params.args.project === "string" ? params.args.project : void 0
14402
+ });
14285
14403
  return {
14286
- content: [{ type: "text", text: await callStoreTool(params.args) }]
14404
+ content: [{ type: "text", text: result }]
14287
14405
  };
14288
14406
  }
14289
14407
  if (params.name === "agenr_extract") {
@@ -2,7 +2,10 @@
2
2
  "id": "agenr",
3
3
  "name": "agenr Memory",
4
4
  "description": "Local memory layer - injects agenr context at session start",
5
- "version": "0.6.6",
5
+ "version": "0.7.3",
6
+ "skills": [
7
+ "skills"
8
+ ],
6
9
  "configSchema": {
7
10
  "type": "object",
8
11
  "additionalProperties": false,
@@ -18,6 +21,22 @@
18
21
  "enabled": {
19
22
  "type": "boolean",
20
23
  "description": "Set false to disable memory injection without uninstalling."
24
+ },
25
+ "signalMinImportance": {
26
+ "type": "number",
27
+ "description": "Minimum importance for mid-session signals (1-10, default: 7). Raise to reduce noise."
28
+ },
29
+ "signalMaxPerSignal": {
30
+ "type": "number",
31
+ "description": "Max entries per signal notification (default: 5)."
32
+ },
33
+ "signalsEnabled": {
34
+ "type": "boolean",
35
+ "description": "Set false to disable mid-session signals without uninstalling."
36
+ },
37
+ "dbPath": {
38
+ "type": "string",
39
+ "description": "Path to agenr DB. Defaults to AGENR_DB_PATH env or ~/.agenr/knowledge.db."
21
40
  }
22
41
  }
23
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"
@@ -47,6 +47,7 @@
47
47
  ],
48
48
  "files": [
49
49
  "dist",
50
+ "skills",
50
51
  "openclaw.plugin.json",
51
52
  "CHANGELOG.md",
52
53
  "LICENSE",
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: agenr
3
+ description: Use when storing new knowledge (decisions, preferences, lessons, todos) or recalling context mid-session. The agenr plugin auto-injects memory at session start - this skill covers proactive store and on-demand recall.
4
+ ---
5
+
6
+ Use `agenr_store` proactively. Call it immediately after any decision, user preference, lesson learned, important event, or fact worth remembering. Do not ask first.
7
+
8
+ Required fields: `type`, `content`, `importance`.
9
+ Types: `fact | decision | preference | todo | lesson | event`.
10
+ Importance: `1-10` (default `7`), use `9` for critical items, use `10` sparingly.
11
+
12
+ Do not store secrets/credentials, temporary state, or verbatim conversation.
13
+
14
+ Use `agenr_recall` mid-session when you need context you do not already have. Use a specific query so results stay relevant.
15
+
16
+ Session-start recall is already handled automatically by the OpenClaw plugin. Do not call `agenr_recall` at turn 1 unless you need extra context beyond the injected summary.