agenr 0.8.19 → 0.8.21
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/dist/openclaw-plugin/index.js +133 -75
- package/package.json +13 -8
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
} from "../chunk-L7VVZDWF.js";
|
|
8
8
|
|
|
9
9
|
// src/openclaw-plugin/index.ts
|
|
10
|
-
import os from "os";
|
|
11
|
-
import path3 from "path";
|
|
12
10
|
import { Type } from "@sinclair/typebox";
|
|
13
11
|
|
|
14
12
|
// src/openclaw-plugin/recall.ts
|
|
@@ -33,7 +31,7 @@ function buildSpawnArgs(agenrPath) {
|
|
|
33
31
|
}
|
|
34
32
|
return { cmd: agenrPath, args: [] };
|
|
35
33
|
}
|
|
36
|
-
async function runRecall(agenrPath, budget, project, query) {
|
|
34
|
+
async function runRecall(agenrPath, budget, project, query, options) {
|
|
37
35
|
return await new Promise((resolve) => {
|
|
38
36
|
let stdout = "";
|
|
39
37
|
let settled = false;
|
|
@@ -45,14 +43,17 @@ async function runRecall(agenrPath, budget, project, query) {
|
|
|
45
43
|
settled = true;
|
|
46
44
|
resolve(value);
|
|
47
45
|
}
|
|
48
|
-
const
|
|
46
|
+
const isBrowse = options?.context === "browse";
|
|
47
|
+
const args = isBrowse ? ["recall", "--browse", "--since", options?.since ?? "1d", "--json"] : ["recall", "--context", "session-start", "--budget", String(budget), "--json"];
|
|
49
48
|
if (project) {
|
|
50
49
|
args.push("--project", project);
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
if (!isBrowse) {
|
|
52
|
+
const trimmedQuery = query?.trim() ?? "";
|
|
53
|
+
const truncatedQuery = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
|
|
54
|
+
if (truncatedQuery) {
|
|
55
|
+
args.push(truncatedQuery);
|
|
56
|
+
}
|
|
56
57
|
}
|
|
57
58
|
const child = spawn(spawnArgs.cmd, [...spawnArgs.args, ...args], {
|
|
58
59
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -148,6 +149,8 @@ var SESSION_TOPIC_TTL_MS = 60 * 60 * 1e3;
|
|
|
148
149
|
var SESSION_TOPIC_MIN_LENGTH = 40;
|
|
149
150
|
var ARCHIVED_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
150
151
|
var SESSION_QUERY_LOOKBACK = 3;
|
|
152
|
+
var EXCHANGE_TEXT_MAX_CHARS = 200;
|
|
153
|
+
var EXCHANGE_USER_TURN_LIMIT = 5;
|
|
151
154
|
var sessionTopicStash = /* @__PURE__ */ new Map();
|
|
152
155
|
function isRecord(value) {
|
|
153
156
|
return typeof value === "object" && value !== null;
|
|
@@ -179,9 +182,40 @@ function extractTextFromUserMessage(message) {
|
|
|
179
182
|
}
|
|
180
183
|
return textParts.join(" ").trim();
|
|
181
184
|
}
|
|
185
|
+
function extractTextFromAssistantMessage(message) {
|
|
186
|
+
if (!isRecord(message) || message["role"] !== "assistant") {
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
const content = message["content"];
|
|
190
|
+
if (typeof content === "string") {
|
|
191
|
+
return content.trim();
|
|
192
|
+
}
|
|
193
|
+
if (!Array.isArray(content)) {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
const textParts = [];
|
|
197
|
+
for (const part of content) {
|
|
198
|
+
if (!isRecord(part) || part["type"] !== "text") {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const partText = part["text"];
|
|
202
|
+
if (typeof partText !== "string") {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const trimmed = partText.trim();
|
|
206
|
+
if (trimmed) {
|
|
207
|
+
textParts.push(trimmed);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return textParts.join(" ").trim();
|
|
211
|
+
}
|
|
212
|
+
function truncateMessageText(text) {
|
|
213
|
+
return text.length > EXCHANGE_TEXT_MAX_CHARS ? text.slice(0, EXCHANGE_TEXT_MAX_CHARS) : text;
|
|
214
|
+
}
|
|
215
|
+
var OPENCLAW_BARE_RESET_PREFIX = "a new session was started via /new";
|
|
182
216
|
function isThinPrompt(prompt) {
|
|
183
217
|
const trimmed = prompt.trim().toLowerCase();
|
|
184
|
-
return trimmed === "" || trimmed === "/new" || trimmed === "/reset";
|
|
218
|
+
return trimmed === "" || trimmed === "/new" || trimmed === "/reset" || trimmed.startsWith(OPENCLAW_BARE_RESET_PREFIX);
|
|
185
219
|
}
|
|
186
220
|
function stripPromptMetadata(raw) {
|
|
187
221
|
if (!raw) {
|
|
@@ -221,56 +255,41 @@ function extractLastUserText(messages) {
|
|
|
221
255
|
return "";
|
|
222
256
|
}
|
|
223
257
|
}
|
|
224
|
-
|
|
258
|
+
function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT) {
|
|
225
259
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
229
|
-
for (const entry of entries) {
|
|
230
|
-
if (!entry.isFile() || !entry.name.includes(".reset.")) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const filePath = path2.join(sessionsDir, entry.name);
|
|
234
|
-
const fileStats = await stat(filePath);
|
|
235
|
-
if (now - fileStats.mtimeMs > maxAgeMs) {
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
archivedFiles.push({ filePath, mtimeMs: fileStats.mtimeMs });
|
|
260
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
261
|
+
return "";
|
|
239
262
|
}
|
|
240
|
-
if (
|
|
241
|
-
return
|
|
263
|
+
if (maxTurns <= 0) {
|
|
264
|
+
return "";
|
|
242
265
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
parsed = JSON.parse(trimmed);
|
|
255
|
-
} catch {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (!isRecord(parsed) || parsed["type"] !== "message") {
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
const text = extractTextFromUserMessage(parsed["message"]);
|
|
262
|
-
if (!text) {
|
|
263
|
-
continue;
|
|
266
|
+
const collected = [];
|
|
267
|
+
let collectedUserTurns = 0;
|
|
268
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
269
|
+
const message = messages[index];
|
|
270
|
+
const userText = extractTextFromUserMessage(message);
|
|
271
|
+
if (userText) {
|
|
272
|
+
collected.push({ role: "user", text: truncateMessageText(userText) });
|
|
273
|
+
collectedUserTurns += 1;
|
|
274
|
+
if (collectedUserTurns >= maxTurns) {
|
|
275
|
+
break;
|
|
264
276
|
}
|
|
265
|
-
|
|
277
|
+
continue;
|
|
266
278
|
}
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
const assistantText = extractTextFromAssistantMessage(message);
|
|
280
|
+
if (assistantText) {
|
|
281
|
+
collected.push({
|
|
282
|
+
role: "assistant",
|
|
283
|
+
text: truncateMessageText(assistantText)
|
|
284
|
+
});
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
|
-
|
|
287
|
+
if (collected.length === 0) {
|
|
288
|
+
return "";
|
|
289
|
+
}
|
|
290
|
+
return collected.reverse().map((turn) => `${turn.role === "user" ? "U" : "A"}: ${turn.text}`).join(" | ");
|
|
272
291
|
} catch {
|
|
273
|
-
return
|
|
292
|
+
return "";
|
|
274
293
|
}
|
|
275
294
|
}
|
|
276
295
|
function shouldStashTopic(text) {
|
|
@@ -923,6 +942,7 @@ var plugin = {
|
|
|
923
942
|
name: "agenr memory context",
|
|
924
943
|
description: "Injects agenr long-term memory into every agent session via before_prompt_build",
|
|
925
944
|
register(api) {
|
|
945
|
+
const config = api.pluginConfig;
|
|
926
946
|
api.on(
|
|
927
947
|
"before_prompt_build",
|
|
928
948
|
async (event, ctx) => {
|
|
@@ -934,7 +954,6 @@ var plugin = {
|
|
|
934
954
|
if (shouldSkipSession(sessionKey)) {
|
|
935
955
|
return;
|
|
936
956
|
}
|
|
937
|
-
const config = api.pluginConfig;
|
|
938
957
|
if (config?.enabled === false) {
|
|
939
958
|
return;
|
|
940
959
|
}
|
|
@@ -950,33 +969,48 @@ var plugin = {
|
|
|
950
969
|
const project = config?.project?.trim() || void 0;
|
|
951
970
|
const userPrompt = stripPromptMetadata(event.prompt ?? "");
|
|
952
971
|
const queryText = resolveSessionQuery(userPrompt, ctx.sessionKey);
|
|
953
|
-
let
|
|
954
|
-
|
|
955
|
-
|
|
972
|
+
let recallResult;
|
|
973
|
+
const isColdStart = isFirstInSession && queryText === void 0;
|
|
974
|
+
if (isColdStart) {
|
|
956
975
|
if (!ctx.agentId?.trim()) {
|
|
957
|
-
api.logger.debug?.("[agenr]
|
|
958
|
-
}
|
|
959
|
-
const sessionsDir = path3.join(
|
|
960
|
-
os.homedir(),
|
|
961
|
-
".openclaw",
|
|
962
|
-
"agents",
|
|
963
|
-
agentId,
|
|
964
|
-
"sessions"
|
|
965
|
-
);
|
|
966
|
-
const archivedMessages = await readLatestArchivedUserMessages(sessionsDir);
|
|
967
|
-
if (archivedMessages.length > 0) {
|
|
968
|
-
recallQueryText = `${archivedMessages.join(" ")} ${userPrompt}`;
|
|
969
|
-
api.logger.info?.(
|
|
970
|
-
`[agenr] session-start: archived session fallback applied, ${archivedMessages.length} messages`
|
|
971
|
-
);
|
|
976
|
+
api.logger.debug?.("[agenr] cold-start: agentId not in ctx, defaulting to 'main'");
|
|
972
977
|
}
|
|
978
|
+
recallResult = await runRecall(agenrPath, budget, project, void 0, {
|
|
979
|
+
context: "browse",
|
|
980
|
+
since: "1d"
|
|
981
|
+
});
|
|
982
|
+
} else {
|
|
983
|
+
recallResult = await runRecall(agenrPath, budget, project, queryText);
|
|
973
984
|
}
|
|
974
|
-
const recallResult = await runRecall(agenrPath, budget, project, recallQueryText);
|
|
975
985
|
if (recallResult) {
|
|
976
986
|
const formatted = formatRecallAsMarkdown(recallResult);
|
|
977
987
|
if (formatted.trim()) {
|
|
978
988
|
markdown = formatted;
|
|
979
989
|
}
|
|
990
|
+
if (isColdStart) {
|
|
991
|
+
for (const item of recallResult.results) {
|
|
992
|
+
const entry = item.entry;
|
|
993
|
+
const subject = typeof entry.subject === "string" ? entry.subject : "";
|
|
994
|
+
if (!subject.toLowerCase().startsWith("session handoff")) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const entryId = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
|
|
998
|
+
if (entryId) {
|
|
999
|
+
await runRetireTool(agenrPath, {
|
|
1000
|
+
entry_id: entryId,
|
|
1001
|
+
reason: "consumed at session start"
|
|
1002
|
+
}).catch((err) => {
|
|
1003
|
+
api.logger.debug?.(
|
|
1004
|
+
`[agenr] session-start: retire handoff ${entryId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1005
|
+
);
|
|
1006
|
+
});
|
|
1007
|
+
} else {
|
|
1008
|
+
api.logger.debug?.(
|
|
1009
|
+
"[agenr] session-start: handoff entry missing id, skipping retire"
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
980
1014
|
}
|
|
981
1015
|
}
|
|
982
1016
|
let signal;
|
|
@@ -1021,10 +1055,35 @@ var plugin = {
|
|
|
1021
1055
|
return;
|
|
1022
1056
|
}
|
|
1023
1057
|
const lastUserText = extractLastUserText(messages);
|
|
1024
|
-
if (
|
|
1025
|
-
|
|
1058
|
+
if (shouldStashTopic(lastUserText)) {
|
|
1059
|
+
stashSessionTopic(sessionKey, lastUserText);
|
|
1060
|
+
}
|
|
1061
|
+
const handoffText = extractLastExchangeText(messages);
|
|
1062
|
+
if (handoffText) {
|
|
1063
|
+
const agenrPath = resolveAgenrPath(config);
|
|
1064
|
+
const defaultProject = config?.project?.trim() || void 0;
|
|
1065
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
1066
|
+
const handoffEntry = {
|
|
1067
|
+
entries: [
|
|
1068
|
+
{
|
|
1069
|
+
type: "event",
|
|
1070
|
+
importance: 10,
|
|
1071
|
+
subject: `session handoff ${timestamp}`,
|
|
1072
|
+
content: handoffText,
|
|
1073
|
+
tags: ["handoff", "session"]
|
|
1074
|
+
}
|
|
1075
|
+
]
|
|
1076
|
+
};
|
|
1077
|
+
const storeConfig = {
|
|
1078
|
+
...config,
|
|
1079
|
+
logger: api.logger
|
|
1080
|
+
};
|
|
1081
|
+
runStoreTool(agenrPath, handoffEntry, storeConfig, defaultProject).catch((err) => {
|
|
1082
|
+
api.logger.debug?.(
|
|
1083
|
+
`[agenr] before_reset: handoff store failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
1026
1086
|
}
|
|
1027
|
-
stashSessionTopic(sessionKey, lastUserText);
|
|
1028
1087
|
} catch (err) {
|
|
1029
1088
|
api.logger.warn(
|
|
1030
1089
|
`agenr plugin before_reset stash failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1032,7 +1091,6 @@ var plugin = {
|
|
|
1032
1091
|
}
|
|
1033
1092
|
});
|
|
1034
1093
|
if (api.registerTool) {
|
|
1035
|
-
const config = api.pluginConfig;
|
|
1036
1094
|
if (config?.enabled === false) {
|
|
1037
1095
|
return;
|
|
1038
1096
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenr",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.21",
|
|
4
4
|
"openclaw": {
|
|
5
5
|
"extensions": [
|
|
6
6
|
"dist/openclaw-plugin/index.js"
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"agenr": "dist/cli.js"
|
|
13
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
|
|
16
|
+
"dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
14
21
|
"dependencies": {
|
|
15
22
|
"@clack/prompts": "^1.0.1",
|
|
16
23
|
"@libsql/client": "^0.17.0",
|
|
@@ -54,11 +61,9 @@
|
|
|
54
61
|
"README.md"
|
|
55
62
|
],
|
|
56
63
|
"author": "agenr-ai",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"test:watch": "vitest",
|
|
62
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
+
"pnpm": {
|
|
65
|
+
"overrides": {
|
|
66
|
+
"fast-xml-parser": "^5.3.6"
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
|
-
}
|
|
69
|
+
}
|