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 +17 -0
- package/dist/openclaw-plugin/index.d.ts +7 -2
- package/dist/openclaw-plugin/index.js +123 -20
- package/package.json +4 -2
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
|
|
8
|
-
|
|
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
|
-
|
|
34
|
-
[
|
|
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
|
|
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 (
|
|
215
|
+
async (_event, ctx) => {
|
|
125
216
|
try {
|
|
126
|
-
const 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.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"openclaw": {
|
|
5
|
-
"extensions": [
|
|
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",
|