agenr 0.6.8 → 0.6.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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.10] - 2026-02-19
4
+
5
+ ### Fixed
6
+ - OpenClaw plugin: AGENR.md now writes a compact summary (subjects only + entry count + recall instructions) instead of full content, preventing double-injection of full context if loaded into Project Context
7
+ - Note: version 0.6.9 was published with a stale build and unpublished; 0.6.10 is the correct release of these changes
8
+
9
+ ## [0.6.9] - 2026-02-19
10
+
11
+ ### Fixed
12
+ - OpenClaw plugin: session-seen guard prevents recall firing on every turn (fires once per session)
13
+ - OpenClaw plugin: sessionKey now read from ctx (second handler arg) instead of event
14
+ - OpenClaw plugin: DEFAULT_AGENR_PATH uses correct 2-level relative path to dist/cli.js
15
+ - OpenClaw plugin: spawn strategy detects .js vs executable binary
16
+
17
+ ### Added
18
+ - OpenClaw plugin: writes AGENR.md to ctx.workspaceDir after successful recall (fire-and-forget)
19
+
3
20
  ## [0.6.8] - 2026-02-19
4
21
 
5
22
  ### Fixed
@@ -1,6 +1,11 @@
1
1
  type BeforeAgentStartEvent = {
2
- sessionKey?: string;
3
2
  prompt?: string;
3
+ messages?: unknown[];
4
+ [key: string]: unknown;
5
+ };
6
+ type PluginHookAgentContext = {
7
+ sessionKey?: string;
8
+ workspaceDir?: string;
4
9
  [key: string]: unknown;
5
10
  };
6
11
  type BeforeAgentStartResult = {
@@ -18,7 +23,7 @@ type PluginApi = {
18
23
  version?: string;
19
24
  pluginConfig?: Record<string, unknown>;
20
25
  logger: PluginLogger;
21
- on: (hook: "before_agent_start", handler: (event: BeforeAgentStartEvent) => Promise<BeforeAgentStartResult | undefined> | BeforeAgentStartResult | undefined) => void;
26
+ on: (hook: "before_agent_start", handler: (event: BeforeAgentStartEvent, ctx: PluginHookAgentContext) => Promise<BeforeAgentStartResult | undefined> | BeforeAgentStartResult | undefined) => void;
22
27
  };
23
28
 
24
29
  declare const plugin: {
@@ -1,27 +1,30 @@
1
1
  // src/openclaw-plugin/recall.ts
2
2
  import { spawn } from "child_process";
3
+ import { writeFile } from "fs/promises";
3
4
  import path from "path";
4
5
  import { fileURLToPath } from "url";
5
6
  var RECALL_TIMEOUT_MS = 5e3;
6
7
  var DEFAULT_BUDGET = 2e3;
7
- var DEFAULT_AGENR_PATH = path.resolve(
8
- fileURLToPath(import.meta.url),
9
- "..",
10
- "..",
11
- "..",
12
- "dist",
13
- "cli.js"
14
- );
8
+ var MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
9
+ var PACKAGE_ROOT = path.resolve(MODULE_DIR, "..", "..");
10
+ var DEFAULT_AGENR_PATH = path.join(PACKAGE_ROOT, "dist", "cli.js");
15
11
  function resolveAgenrPath(config) {
16
12
  return config?.agenrPath?.trim() || process.env["AGENR_BIN"]?.trim() || DEFAULT_AGENR_PATH;
17
13
  }
18
14
  function resolveBudget(config) {
19
15
  return config?.budget ?? DEFAULT_BUDGET;
20
16
  }
17
+ function buildSpawnArgs(agenrPath) {
18
+ if (agenrPath.endsWith(".js")) {
19
+ return { cmd: process.execPath, args: [agenrPath] };
20
+ }
21
+ return { cmd: agenrPath, args: [] };
22
+ }
21
23
  async function runRecall(agenrPath, budget) {
22
24
  return await new Promise((resolve) => {
23
25
  let stdout = "";
24
26
  let settled = false;
27
+ const spawnArgs = buildSpawnArgs(agenrPath);
25
28
  function finish(value) {
26
29
  if (settled) {
27
30
  return;
@@ -30,8 +33,8 @@ async function runRecall(agenrPath, budget) {
30
33
  resolve(value);
31
34
  }
32
35
  const child = spawn(
33
- process.execPath,
34
- [agenrPath, "recall", "--context", "session-start", "--budget", String(budget), "--json"],
36
+ spawnArgs.cmd,
37
+ [...spawnArgs.args, "recall", "--context", "session-start", "--budget", String(budget), "--json"],
35
38
  { stdio: ["ignore", "pipe", "ignore"] }
36
39
  );
37
40
  const timer = setTimeout(() => {
@@ -61,26 +64,35 @@ function isRecallResult(value) {
61
64
  }
62
65
  var TODO_TYPES = /* @__PURE__ */ new Set(["todo"]);
63
66
  var PREFERENCE_TYPES = /* @__PURE__ */ new Set(["preference", "decision"]);
64
- function formatRecallAsMarkdown(result) {
67
+ function groupValidEntries(result) {
68
+ const grouped = {
69
+ todos: [],
70
+ preferences: [],
71
+ facts: []
72
+ };
65
73
  if (!result.results || result.results.length === 0) {
66
- return "";
74
+ return grouped;
67
75
  }
68
- const todos = [];
69
- const preferences = [];
70
- const facts = [];
71
76
  for (const item of result.results) {
72
77
  const entry = item.entry;
73
78
  if (!entry || typeof entry.type !== "string" || typeof entry.subject !== "string" || typeof entry.content !== "string") {
74
79
  continue;
75
80
  }
76
81
  if (TODO_TYPES.has(entry.type)) {
77
- todos.push(entry);
82
+ grouped.todos.push(entry);
78
83
  } else if (PREFERENCE_TYPES.has(entry.type)) {
79
- preferences.push(entry);
84
+ grouped.preferences.push(entry);
80
85
  } else {
81
- facts.push(entry);
86
+ grouped.facts.push(entry);
82
87
  }
83
88
  }
89
+ return grouped;
90
+ }
91
+ function formatRecallAsMarkdown(result) {
92
+ if (!result.results || result.results.length === 0) {
93
+ return "";
94
+ }
95
+ const { todos, preferences, facts } = groupValidEntries(result);
84
96
  if (todos.length === 0 && preferences.length === 0 && facts.length === 0) {
85
97
  return "";
86
98
  }
@@ -108,12 +120,91 @@ function formatRecallAsMarkdown(result) {
108
120
  }
109
121
  return lines.join("\n").trimEnd();
110
122
  }
123
+ function formatRecallAsSummary(result, timestamp) {
124
+ if (!result.results || result.results.length === 0) {
125
+ return "";
126
+ }
127
+ const { todos, preferences, facts } = groupValidEntries(result);
128
+ const totalEntries = todos.length + preferences.length + facts.length;
129
+ if (totalEntries === 0) {
130
+ return "";
131
+ }
132
+ const lines = [timestamp ? `## agenr Memory -- ${timestamp}` : "## agenr Memory", ""];
133
+ lines.push(
134
+ `${totalEntries} entries recalled. Full context injected into this session automatically.`,
135
+ "To pull specific memories: ask your agent, or run:",
136
+ ' mcporter call agenr.agenr_recall query="your topic" limit=5',
137
+ ""
138
+ );
139
+ if (todos.length > 0) {
140
+ lines.push(`### Active Todos (${todos.length})`, "");
141
+ for (const entry of todos) {
142
+ lines.push(`- ${entry.subject}`);
143
+ }
144
+ lines.push("");
145
+ }
146
+ if (preferences.length > 0) {
147
+ lines.push(`### Preferences and Decisions (${preferences.length})`, "");
148
+ for (const entry of preferences) {
149
+ lines.push(`- ${entry.subject}`);
150
+ }
151
+ lines.push("");
152
+ }
153
+ if (facts.length > 0) {
154
+ lines.push(`### Facts and Events (${facts.length})`, "");
155
+ for (const entry of facts) {
156
+ lines.push(`- ${entry.subject}`);
157
+ }
158
+ lines.push("");
159
+ }
160
+ return lines.join("\n").trimEnd();
161
+ }
162
+ async function writeAgenrMd(markdown, workspaceDir) {
163
+ try {
164
+ const outputPath = path.join(workspaceDir, "AGENR.md");
165
+ await writeFile(outputPath, markdown, "utf8");
166
+ } catch {
167
+ }
168
+ }
111
169
 
112
170
  // src/openclaw-plugin/index.ts
113
171
  var SKIP_SESSION_PATTERNS = [":subagent:", ":cron:"];
172
+ var DEFAULT_MAX_SEEN_SESSIONS = 1e3;
173
+ var seenSessions = /* @__PURE__ */ new Map();
174
+ function resolveMaxSeenSessions() {
175
+ const raw = process.env.AGENR_OPENCLAW_MAX_SEEN_SESSIONS;
176
+ if (!raw) {
177
+ return DEFAULT_MAX_SEEN_SESSIONS;
178
+ }
179
+ const parsed = Number.parseInt(raw, 10);
180
+ if (Number.isFinite(parsed) && parsed > 0) {
181
+ return parsed;
182
+ }
183
+ return DEFAULT_MAX_SEEN_SESSIONS;
184
+ }
185
+ var maxSeenSessions = resolveMaxSeenSessions();
114
186
  function shouldSkipSession(sessionKey) {
115
187
  return SKIP_SESSION_PATTERNS.some((pattern) => sessionKey.includes(pattern));
116
188
  }
189
+ function hasSeenSession(sessionKey) {
190
+ const seen = seenSessions.has(sessionKey);
191
+ if (!seen) {
192
+ return false;
193
+ }
194
+ seenSessions.delete(sessionKey);
195
+ seenSessions.set(sessionKey, true);
196
+ return true;
197
+ }
198
+ function markSessionSeen(sessionKey) {
199
+ seenSessions.set(sessionKey, true);
200
+ while (seenSessions.size > maxSeenSessions) {
201
+ const oldestKey = seenSessions.keys().next().value;
202
+ if (!oldestKey) {
203
+ break;
204
+ }
205
+ seenSessions.delete(oldestKey);
206
+ }
207
+ }
117
208
  var plugin = {
118
209
  id: "agenr",
119
210
  name: "agenr memory context",
@@ -121,16 +212,22 @@ var plugin = {
121
212
  register(api) {
122
213
  api.on(
123
214
  "before_agent_start",
124
- async (event) => {
215
+ async (_event, ctx) => {
125
216
  try {
126
- const sessionKey = event.sessionKey ?? "";
217
+ const sessionKey = ctx.sessionKey ?? "";
127
218
  if (shouldSkipSession(sessionKey)) {
128
219
  return;
129
220
  }
221
+ if (sessionKey && hasSeenSession(sessionKey)) {
222
+ return;
223
+ }
130
224
  const config = api.pluginConfig;
131
225
  if (config?.enabled === false) {
132
226
  return;
133
227
  }
228
+ if (sessionKey) {
229
+ markSessionSeen(sessionKey);
230
+ }
134
231
  const agenrPath = resolveAgenrPath(config);
135
232
  const budget = resolveBudget(config);
136
233
  const result = await runRecall(agenrPath, budget);
@@ -141,6 +238,12 @@ var plugin = {
141
238
  if (!markdown.trim()) {
142
239
  return;
143
240
  }
241
+ const workspaceDir = typeof ctx.workspaceDir === "string" ? ctx.workspaceDir.trim() : "";
242
+ if (workspaceDir) {
243
+ const now = /* @__PURE__ */ new Date();
244
+ const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
245
+ void writeAgenrMd(formatRecallAsSummary(result, timestamp), workspaceDir);
246
+ }
144
247
  return { prependContext: markdown };
145
248
  } catch (err) {
146
249
  api.logger.warn(
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "openclaw": {
5
- "extensions": ["dist/openclaw-plugin/index.js"]
5
+ "extensions": [
6
+ "dist/openclaw-plugin/index.js"
7
+ ]
6
8
  },
7
9
  "description": "AGENt memoRy -- Memory infrastructure for AI agents",
8
10
  "type": "module",