agenr 0.7.8 → 0.7.10

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.10] - 2026-02-20
4
+
5
+ ### Fixed
6
+ - fix(init): codex platform now writes MCP entry directly to ~/.codex/config.toml instead of .mcp.json (which Codex does not read)
7
+ - fix(init): openclaw platform no longer writes a .mcp.json file (OpenClaw native plugin handles MCP registration via openclaw plugins install agenr)
8
+ - fix(init): agenr binary path is now resolved at init time via which or PNPM_HOME fallback -- GUI clients that launch with a restricted PATH will now find the correct binary
9
+ - fix(init): codex config.toml write is idempotent -- re-running init replaces the agenr line without duplicating it
10
+ - docs: remove redundant Memory (agenr) AGENTS.md block from OPENCLAW.md -- OpenClaw plugin handles agent instruction injection automatically via the built-in skill
11
+
12
+ ## [0.7.9] - 2026-02-20
13
+
14
+ ### Fixed
15
+ - fix(openclaw-plugin): moved session-start recall injection from before_agent_start to before_prompt_build -- recall now fires exactly once per session instead of twice due to OpenClaw calling before_agent_start twice (once for model-resolve where prependContext is discarded, once for prompt-build where it is used)
16
+
17
+ ## [0.7.8] - 2026-02-20
18
+
19
+ ### Fixed
20
+ - fix(openclaw-plugin): session-start recall dedup now keys on sessionId instead of a shared seen-Set -- each new session (including after /new) correctly receives injected context instead of being silently skipped on the second run
21
+ - fix(extractor): normalizeImportance now defaults to 7 instead of 5 -- aligns runtime default with schema declaration and coaching guidance
22
+
23
+
3
24
  ## [0.7.7] - 2026-02-20
4
25
 
5
26
  ### Fixed
package/dist/cli-main.js CHANGED
@@ -13367,8 +13367,40 @@ async function runContextCommand(options, deps) {
13367
13367
 
13368
13368
  // src/commands/init.ts
13369
13369
  import fs29 from "fs/promises";
13370
+ import { accessSync, constants } from "fs";
13370
13371
  import os16 from "os";
13371
13372
  import path28 from "path";
13373
+ import { execFileSync } from "child_process";
13374
+ function escapeTomlString(value) {
13375
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
13376
+ }
13377
+ function isExecutableFile2(filePath) {
13378
+ try {
13379
+ if (process.platform === "win32") {
13380
+ accessSync(filePath, constants.F_OK);
13381
+ } else {
13382
+ accessSync(filePath, constants.X_OK);
13383
+ }
13384
+ return true;
13385
+ } catch {
13386
+ return false;
13387
+ }
13388
+ }
13389
+ function resolveAgenrBinary() {
13390
+ const lookupCommand = process.platform === "win32" ? "where.exe" : "which";
13391
+ try {
13392
+ return execFileSync(lookupCommand, ["agenr"], { encoding: "utf8" }).trim();
13393
+ } catch {
13394
+ const pnpmHome = process.env.PNPM_HOME;
13395
+ if (pnpmHome) {
13396
+ const candidate = path28.join(pnpmHome, "agenr");
13397
+ if (isExecutableFile2(candidate)) {
13398
+ return candidate;
13399
+ }
13400
+ }
13401
+ return "agenr";
13402
+ }
13403
+ }
13372
13404
  var AGENR_START_MARKER = "<!-- agenr:start -->";
13373
13405
  var AGENR_END_MARKER = "<!-- agenr:end -->";
13374
13406
  var PLATFORM_VALUES = ["claude-code", "cursor", "openclaw", "windsurf", "codex", "generic"];
@@ -13527,9 +13559,9 @@ async function writeAgenrConfig(projectDir, project, platform, dependencies) {
13527
13559
  await writeJsonFile(configPath, next);
13528
13560
  return { configPath, config: next };
13529
13561
  }
13530
- function buildMcpEntry(projectDir) {
13562
+ function buildMcpEntry(projectDir, command) {
13531
13563
  return {
13532
- command: "agenr",
13564
+ command: command ?? "agenr",
13533
13565
  args: ["mcp"],
13534
13566
  env: {
13535
13567
  AGENR_PROJECT_DIR: projectDir
@@ -13556,11 +13588,20 @@ function mergeMcpConfig(existing, entry) {
13556
13588
  };
13557
13589
  }
13558
13590
  async function writeMcpConfig(projectDir, platform) {
13591
+ if (platform === "codex") {
13592
+ const agenrBin2 = resolveAgenrBinary();
13593
+ const mcpPath2 = await writeCodexConfig(projectDir, agenrBin2);
13594
+ return { mcpPath: mcpPath2, skipped: false };
13595
+ }
13596
+ if (platform === "openclaw") {
13597
+ return { mcpPath: "", skipped: true };
13598
+ }
13559
13599
  const mcpPath = platform === "cursor" ? path28.join(projectDir, ".cursor", "mcp.json") : path28.join(projectDir, ".mcp.json");
13600
+ const agenrBin = resolveAgenrBinary();
13560
13601
  const existing = await readJsonRecord(mcpPath) ?? {};
13561
- const next = mergeMcpConfig(existing, buildMcpEntry(projectDir));
13602
+ const next = mergeMcpConfig(existing, buildMcpEntry(projectDir, agenrBin));
13562
13603
  await writeJsonFile(mcpPath, next);
13563
- return mcpPath;
13604
+ return { mcpPath, skipped: false };
13564
13605
  }
13565
13606
  function isPathInsideProject(projectDir, targetPath) {
13566
13607
  const relative = path28.relative(projectDir, targetPath);
@@ -13599,12 +13640,60 @@ function resolveProjectSlug(projectDir, explicitProject) {
13599
13640
  }
13600
13641
  return slug;
13601
13642
  }
13643
+ async function writeCodexConfig(projectDir, agenrBin) {
13644
+ const configPath = path28.join(os16.homedir(), ".codex", "config.toml");
13645
+ const escapedBin = escapeTomlString(agenrBin);
13646
+ const escapedProjectDir = escapeTomlString(projectDir);
13647
+ const newLine = `agenr = { command = "${escapedBin}", args = ["mcp"], env = { AGENR_PROJECT_DIR = "${escapedProjectDir}" } }`;
13648
+ let existing = "";
13649
+ try {
13650
+ existing = await fs29.readFile(configPath, "utf8");
13651
+ } catch (error) {
13652
+ const code = error.code;
13653
+ if (code !== "ENOENT") throw error;
13654
+ }
13655
+ let next;
13656
+ if (existing.trim().length === 0) {
13657
+ next = `[mcp]
13658
+ ${newLine}
13659
+ `;
13660
+ } else {
13661
+ const lines = existing.split("\n");
13662
+ const mcpSectionIdx = lines.findIndex((l) => l.trim() === "[mcp]");
13663
+ if (mcpSectionIdx !== -1) {
13664
+ let mcpSectionEnd = lines.length;
13665
+ for (let i = mcpSectionIdx + 1; i < lines.length; i += 1) {
13666
+ if (/^\s*\[/.test(lines[i] ?? "")) {
13667
+ mcpSectionEnd = i;
13668
+ break;
13669
+ }
13670
+ }
13671
+ const relativeAgenrIdx = lines.slice(mcpSectionIdx + 1, mcpSectionEnd).findIndex((l) => l.trimStart().startsWith("agenr ="));
13672
+ if (relativeAgenrIdx !== -1) {
13673
+ const agenrLineIdx = mcpSectionIdx + 1 + relativeAgenrIdx;
13674
+ lines[agenrLineIdx] = newLine;
13675
+ } else {
13676
+ lines.splice(mcpSectionIdx + 1, 0, newLine);
13677
+ }
13678
+ next = lines.join("\n");
13679
+ } else {
13680
+ const suffix = existing.endsWith("\n") ? "" : "\n";
13681
+ next = `${existing}${suffix}
13682
+ [mcp]
13683
+ ${newLine}
13684
+ `;
13685
+ }
13686
+ }
13687
+ await fs29.mkdir(path28.dirname(configPath), { recursive: true });
13688
+ await fs29.writeFile(configPath, next, "utf8");
13689
+ return configPath;
13690
+ }
13602
13691
  function formatInitSummary(result) {
13603
13692
  const dependencyLabel = result.dependencies.length > 0 ? `[${result.dependencies.join(",")}]` : "[]";
13604
13693
  const lines = [
13605
13694
  `agenr init: platform=${result.platform} project=${result.project} dependencies=${dependencyLabel}`,
13606
13695
  `- Wrote .agenr/config.json`,
13607
- `- Wrote MCP config to ${path28.relative(result.projectDir, result.mcpPath) || path28.basename(result.mcpPath)}`
13696
+ result.platform === "codex" ? "- Wrote MCP entry to ~/.codex/config.toml" : result.mcpSkipped ? "- MCP: handled by OpenClaw native plugin (openclaw plugins install agenr)" : `- Wrote MCP config to ${path28.relative(result.projectDir, result.mcpPath) || path28.basename(result.mcpPath)}`
13608
13697
  ];
13609
13698
  if (result.instructionsPath !== null) {
13610
13699
  lines.splice(2, 0, `- Wrote system prompt block to ${path28.basename(result.instructionsPath)}`);
@@ -13641,7 +13730,7 @@ async function runInitCommand(options) {
13641
13730
  if (instructionsPath !== null) {
13642
13731
  await upsertPromptBlock(instructionsPath, buildSystemPromptBlock(project));
13643
13732
  }
13644
- const mcpPath = await writeMcpConfig(projectDir, resolvedPlatform);
13733
+ const { mcpPath, skipped: mcpSkipped } = await writeMcpConfig(projectDir, resolvedPlatform);
13645
13734
  const gitignoreEntries = [".agenr/knowledge.db"];
13646
13735
  if (resolvedPlatform === "cursor") {
13647
13736
  gitignoreEntries.push(".cursor/rules/agenr.mdc");
@@ -13659,6 +13748,7 @@ async function runInitCommand(options) {
13659
13748
  configPath: configResult.configPath,
13660
13749
  instructionsPath,
13661
13750
  mcpPath,
13751
+ mcpSkipped,
13662
13752
  gitignoreUpdated
13663
13753
  };
13664
13754
  }
@@ -624,46 +624,8 @@ async function ensurePluginDb(config) {
624
624
  var plugin = {
625
625
  id: "agenr",
626
626
  name: "agenr memory context",
627
- description: "Injects agenr long-term memory into every agent session via before_agent_start",
627
+ description: "Injects agenr long-term memory into every agent session via before_prompt_build",
628
628
  register(api) {
629
- api.on(
630
- "before_agent_start",
631
- async (_event, ctx) => {
632
- try {
633
- const sessionKey = ctx.sessionKey ?? "";
634
- const dedupeKey = ctx.sessionId ?? sessionKey;
635
- if (shouldSkipSession(sessionKey)) {
636
- return;
637
- }
638
- if (dedupeKey && hasSeenSession(dedupeKey)) {
639
- return;
640
- }
641
- const config = api.pluginConfig;
642
- if (config?.enabled === false) {
643
- return;
644
- }
645
- if (dedupeKey) {
646
- markSessionSeen(dedupeKey);
647
- }
648
- const agenrPath = resolveAgenrPath(config);
649
- const budget = resolveBudget(config);
650
- const result = await runRecall(agenrPath, budget);
651
- if (!result) {
652
- return;
653
- }
654
- const markdown = formatRecallAsMarkdown(result);
655
- if (!markdown.trim()) {
656
- return;
657
- }
658
- return { prependContext: markdown };
659
- } catch (err) {
660
- api.logger.warn(
661
- `agenr plugin before_agent_start recall failed: ${err instanceof Error ? err.message : String(err)}`
662
- );
663
- return;
664
- }
665
- }
666
- );
667
629
  api.on(
668
630
  "before_prompt_build",
669
631
  async (_event, ctx) => {
@@ -679,26 +641,46 @@ var plugin = {
679
641
  if (config?.enabled === false) {
680
642
  return;
681
643
  }
682
- if (config?.signalsEnabled === false) {
683
- return;
644
+ const dedupeKey = ctx.sessionId ?? sessionKey;
645
+ let markdown;
646
+ const isFirstInSession = dedupeKey ? !hasSeenSession(dedupeKey) : true;
647
+ if (isFirstInSession) {
648
+ if (dedupeKey) {
649
+ markSessionSeen(dedupeKey);
650
+ }
651
+ const agenrPath = resolveAgenrPath(config);
652
+ const budget = resolveBudget(config);
653
+ const recallResult = await runRecall(agenrPath, budget);
654
+ if (recallResult) {
655
+ const formatted = formatRecallAsMarkdown(recallResult);
656
+ if (formatted.trim()) {
657
+ markdown = formatted;
658
+ }
659
+ }
684
660
  }
685
- const signalConfig = resolveSignalConfig(config);
686
- const db = await ensurePluginDb(config);
687
- const signal = await checkSignals(db, sessionKey, signalConfig);
688
- if (!signal) {
689
- return;
661
+ let signal;
662
+ if (config?.signalsEnabled !== false) {
663
+ const signalConfig = resolveSignalConfig(config);
664
+ const db = await ensurePluginDb(config);
665
+ const candidateSignal = await checkSignals(db, sessionKey, signalConfig);
666
+ if (candidateSignal) {
667
+ const state = sessionSignalState.get(sessionKey) ?? { lastSignalAt: 0, signalCount: 0 };
668
+ const inCooldown = signalConfig.cooldownMs > 0 && Date.now() - state.lastSignalAt < signalConfig.cooldownMs;
669
+ const overCap = signalConfig.maxPerSession > 0 && state.signalCount >= signalConfig.maxPerSession;
670
+ if (!inCooldown && !overCap) {
671
+ sessionSignalState.set(sessionKey, {
672
+ lastSignalAt: Date.now(),
673
+ signalCount: state.signalCount + 1
674
+ });
675
+ signal = candidateSignal;
676
+ }
677
+ }
690
678
  }
691
- const state = sessionSignalState.get(sessionKey) ?? { lastSignalAt: 0, signalCount: 0 };
692
- const inCooldown = signalConfig.cooldownMs > 0 && Date.now() - state.lastSignalAt < signalConfig.cooldownMs;
693
- const overCap = signalConfig.maxPerSession > 0 && state.signalCount >= signalConfig.maxPerSession;
694
- if (inCooldown || overCap) {
679
+ const prependContext = [markdown, signal].filter(Boolean).join("\n\n");
680
+ if (!prependContext) {
695
681
  return;
696
682
  }
697
- sessionSignalState.set(sessionKey, {
698
- lastSignalAt: Date.now(),
699
- signalCount: state.signalCount + 1
700
- });
701
- return { prependContext: signal };
683
+ return { prependContext };
702
684
  } catch (err) {
703
685
  api.logger.warn(
704
686
  `agenr plugin before_prompt_build signal check failed: ${err instanceof Error ? err.message : String(err)}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"