agenr 0.8.22 → 0.8.24
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.d.ts +0 -1
- package/dist/openclaw-plugin/index.js +195 -131
- package/package.json +1 -1
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/openclaw-plugin/index.ts
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path3 from "path";
|
|
11
13
|
|
|
12
14
|
// src/openclaw-plugin/recall.ts
|
|
13
15
|
import { spawn } from "child_process";
|
|
@@ -45,6 +47,9 @@ async function runRecall(agenrPath, budget, project, query, options) {
|
|
|
45
47
|
}
|
|
46
48
|
const isBrowse = options?.context === "browse";
|
|
47
49
|
const args = isBrowse ? ["recall", "--browse", "--since", options?.since ?? "1d", "--json"] : ["recall", "--context", "session-start", "--budget", String(budget), "--json"];
|
|
50
|
+
if (isBrowse && options?.limit !== void 0) {
|
|
51
|
+
args.push("--limit", String(options.limit));
|
|
52
|
+
}
|
|
48
53
|
if (project) {
|
|
49
54
|
args.push("--project", project);
|
|
50
55
|
}
|
|
@@ -143,15 +148,15 @@ function formatRecallAsMarkdown(result) {
|
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
// src/openclaw-plugin/session-query.ts
|
|
146
|
-
import {
|
|
151
|
+
import { createReadStream } from "fs";
|
|
152
|
+
import { readdir, stat } from "fs/promises";
|
|
147
153
|
import path2 from "path";
|
|
148
|
-
|
|
149
|
-
var SESSION_TOPIC_MIN_LENGTH = 40;
|
|
150
|
-
var ARCHIVED_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
151
|
-
var SESSION_QUERY_LOOKBACK = 3;
|
|
154
|
+
import { createInterface } from "readline";
|
|
152
155
|
var EXCHANGE_TEXT_MAX_CHARS = 200;
|
|
153
156
|
var EXCHANGE_USER_TURN_LIMIT = 5;
|
|
154
|
-
var
|
|
157
|
+
var RECENT_TURN_MAX_CHARS = 150;
|
|
158
|
+
var SEMANTIC_SEED_PREVIOUS_TURNS_MAX_CHARS = 400;
|
|
159
|
+
var DEFAULT_RECENT_TURN_LIMIT = 7;
|
|
155
160
|
function isRecord(value) {
|
|
156
161
|
return typeof value === "object" && value !== null;
|
|
157
162
|
}
|
|
@@ -209,13 +214,8 @@ function extractTextFromAssistantMessage(message) {
|
|
|
209
214
|
}
|
|
210
215
|
return textParts.join(" ").trim();
|
|
211
216
|
}
|
|
212
|
-
function truncateMessageText(text) {
|
|
213
|
-
return text.length >
|
|
214
|
-
}
|
|
215
|
-
var OPENCLAW_BARE_RESET_PREFIX = "a new session was started via /new";
|
|
216
|
-
function isThinPrompt(prompt) {
|
|
217
|
-
const trimmed = prompt.trim().toLowerCase();
|
|
218
|
-
return trimmed === "" || trimmed === "/new" || trimmed === "/reset" || trimmed.startsWith(OPENCLAW_BARE_RESET_PREFIX);
|
|
217
|
+
function truncateMessageText(text, maxChars = EXCHANGE_TEXT_MAX_CHARS) {
|
|
218
|
+
return text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
219
219
|
}
|
|
220
220
|
function stripPromptMetadata(raw) {
|
|
221
221
|
if (!raw) {
|
|
@@ -236,25 +236,6 @@ function stripPromptMetadata(raw) {
|
|
|
236
236
|
return "";
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
function extractLastUserText(messages) {
|
|
240
|
-
try {
|
|
241
|
-
const collected = [];
|
|
242
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
243
|
-
const extracted = extractTextFromUserMessage(messages[index]);
|
|
244
|
-
if (!extracted) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
collected.push(extracted);
|
|
248
|
-
if (collected.length >= SESSION_QUERY_LOOKBACK) {
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const joined = collected.reverse().join(" ").trim();
|
|
253
|
-
return joined || "";
|
|
254
|
-
} catch {
|
|
255
|
-
return "";
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
239
|
function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT) {
|
|
259
240
|
try {
|
|
260
241
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
@@ -292,74 +273,126 @@ function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT)
|
|
|
292
273
|
return "";
|
|
293
274
|
}
|
|
294
275
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
276
|
+
async function findPreviousSessionFile(sessionsDir, currentSessionId, logger) {
|
|
277
|
+
try {
|
|
278
|
+
const normalizedSessionId = currentSessionId?.trim();
|
|
279
|
+
const normalizedSessionFileName = normalizedSessionId ? `${normalizedSessionId}.jsonl` : void 0;
|
|
280
|
+
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
281
|
+
const candidatePaths = [];
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
if (!entry.isFile()) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const isPlainJsonl = entry.name.endsWith(".jsonl");
|
|
287
|
+
const isResetJsonl = entry.name.includes(".jsonl.reset.");
|
|
288
|
+
if (!isPlainJsonl && !isResetJsonl) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (entry.name.includes(".deleted.")) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (normalizedSessionId && entry.name.startsWith(normalizedSessionId)) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
candidatePaths.push(path2.join(sessionsDir, entry.name));
|
|
307
298
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
var sweepInterval = setInterval(sweepExpiredStash, 5 * 60 * 1e3);
|
|
311
|
-
if (sweepInterval !== void 0 && typeof sweepInterval.unref === "function") {
|
|
312
|
-
sweepInterval.unref();
|
|
313
|
-
}
|
|
314
|
-
function stripResetPrefix(prompt) {
|
|
315
|
-
const lower = prompt.toLowerCase();
|
|
316
|
-
for (const cmd of ["/new", "/reset"]) {
|
|
317
|
-
if (lower.startsWith(cmd + " ")) {
|
|
318
|
-
return prompt.slice(cmd.length).trim();
|
|
299
|
+
if (candidatePaths.length === 0) {
|
|
300
|
+
return null;
|
|
319
301
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
302
|
+
const statResults = await Promise.all(
|
|
303
|
+
candidatePaths.map(async (filePath) => {
|
|
304
|
+
try {
|
|
305
|
+
const fileStats = await stat(filePath);
|
|
306
|
+
return { filePath, mtimeMs: fileStats.mtimeMs };
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
const candidates = statResults.filter(
|
|
313
|
+
(result) => result !== null
|
|
314
|
+
);
|
|
315
|
+
if (candidates.length === 0) {
|
|
316
|
+
return null;
|
|
333
317
|
}
|
|
318
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
319
|
+
return candidates[0]?.filePath ?? null;
|
|
320
|
+
} catch (err) {
|
|
321
|
+
logger?.debug?.(
|
|
322
|
+
`[agenr] findPreviousSessionFile: failed to read sessions dir "${sessionsDir}": ${err instanceof Error ? err.message : String(err)}`
|
|
323
|
+
);
|
|
324
|
+
return null;
|
|
334
325
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (!
|
|
339
|
-
return
|
|
326
|
+
}
|
|
327
|
+
async function extractRecentTurns(filePath, maxTurns = DEFAULT_RECENT_TURN_LIMIT) {
|
|
328
|
+
try {
|
|
329
|
+
if (!filePath) {
|
|
330
|
+
return "";
|
|
340
331
|
}
|
|
341
|
-
|
|
342
|
-
|
|
332
|
+
const parsedMaxTurns = Number.isFinite(maxTurns) ? Math.max(0, Math.trunc(maxTurns)) : 0;
|
|
333
|
+
if (parsedMaxTurns === 0) {
|
|
334
|
+
return "";
|
|
343
335
|
}
|
|
344
|
-
|
|
336
|
+
const ring = [];
|
|
337
|
+
await new Promise((resolve, reject) => {
|
|
338
|
+
const rl = createInterface({
|
|
339
|
+
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
340
|
+
crlfDelay: Infinity
|
|
341
|
+
});
|
|
342
|
+
rl.on("line", (line) => {
|
|
343
|
+
const trimmed = line.trim();
|
|
344
|
+
if (!trimmed) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
let record;
|
|
348
|
+
try {
|
|
349
|
+
record = JSON.parse(trimmed);
|
|
350
|
+
} catch {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!isRecord(record) || record["type"] !== "message") {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const message = record["message"];
|
|
357
|
+
const userText = extractTextFromUserMessage(message);
|
|
358
|
+
if (userText) {
|
|
359
|
+
ring.push(`U: ${truncateMessageText(userText, RECENT_TURN_MAX_CHARS)}`);
|
|
360
|
+
if (ring.length > parsedMaxTurns) {
|
|
361
|
+
ring.shift();
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const assistantText = extractTextFromAssistantMessage(message);
|
|
366
|
+
if (assistantText) {
|
|
367
|
+
ring.push(`A: ${truncateMessageText(assistantText, RECENT_TURN_MAX_CHARS)}`);
|
|
368
|
+
if (ring.length > parsedMaxTurns) {
|
|
369
|
+
ring.shift();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
rl.on("close", resolve);
|
|
374
|
+
rl.on("error", reject);
|
|
375
|
+
});
|
|
376
|
+
return ring.join(" | ");
|
|
377
|
+
} catch {
|
|
378
|
+
return "";
|
|
345
379
|
}
|
|
346
|
-
return stashedText;
|
|
347
380
|
}
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
381
|
+
function buildSemanticSeed(previousTurns, firstUserMessage) {
|
|
382
|
+
const stripped = stripPromptMetadata(firstUserMessage).trim();
|
|
383
|
+
const wordCount = stripped.split(/\s+/).filter(Boolean).length;
|
|
384
|
+
const messageHasSignal = wordCount >= 5;
|
|
385
|
+
const truncatedTurns = previousTurns.slice(0, SEMANTIC_SEED_PREVIOUS_TURNS_MAX_CHARS);
|
|
386
|
+
if (truncatedTurns && messageHasSignal) {
|
|
387
|
+
return `${truncatedTurns} ${stripped}`;
|
|
351
388
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
function clearStash() {
|
|
358
|
-
sessionTopicStash.clear();
|
|
359
|
-
if (sweepInterval !== void 0) {
|
|
360
|
-
clearInterval(sweepInterval);
|
|
361
|
-
sweepInterval = void 0;
|
|
389
|
+
if (truncatedTurns) {
|
|
390
|
+
return truncatedTurns;
|
|
391
|
+
}
|
|
392
|
+
if (messageHasSignal) {
|
|
393
|
+
return stripped;
|
|
362
394
|
}
|
|
395
|
+
return void 0;
|
|
363
396
|
}
|
|
364
397
|
|
|
365
398
|
// src/db/signals.ts
|
|
@@ -967,51 +1000,88 @@ var plugin = {
|
|
|
967
1000
|
const agenrPath = resolveAgenrPath(config);
|
|
968
1001
|
const budget = resolveBudget(config);
|
|
969
1002
|
const project = config?.project?.trim() || void 0;
|
|
970
|
-
const
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
recallResult = await runRecall(agenrPath, budget, project, void 0, {
|
|
1003
|
+
const agentId = ctx.agentId?.trim() || "main";
|
|
1004
|
+
const sessionsDir = config?.sessionsDir ?? path3.join(os.homedir(), `.openclaw/agents/${agentId}/sessions`);
|
|
1005
|
+
const [previousTurns, browseResult] = await Promise.all([
|
|
1006
|
+
findPreviousSessionFile(sessionsDir, ctx.sessionId, api.logger).then(
|
|
1007
|
+
(file) => file ? extractRecentTurns(file) : Promise.resolve("")
|
|
1008
|
+
),
|
|
1009
|
+
runRecall(agenrPath, budget, project, void 0, {
|
|
979
1010
|
context: "browse",
|
|
980
|
-
since: "1d"
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1011
|
+
since: "1d",
|
|
1012
|
+
limit: 20
|
|
1013
|
+
})
|
|
1014
|
+
]);
|
|
1015
|
+
const seed = buildSemanticSeed(previousTurns, event.prompt ?? "");
|
|
1016
|
+
let semanticResult = null;
|
|
1017
|
+
if (seed) {
|
|
1018
|
+
const browseIds = /* @__PURE__ */ new Set();
|
|
1019
|
+
for (const item of browseResult?.results ?? []) {
|
|
1020
|
+
const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
|
|
1021
|
+
if (id) {
|
|
1022
|
+
browseIds.add(id);
|
|
1023
|
+
}
|
|
989
1024
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
if (!
|
|
995
|
-
|
|
1025
|
+
const rawSemantic = await runRecall(agenrPath, budget, project, seed);
|
|
1026
|
+
if (rawSemantic) {
|
|
1027
|
+
rawSemantic.results = rawSemantic.results.filter((item) => {
|
|
1028
|
+
const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
|
|
1029
|
+
if (!id) {
|
|
1030
|
+
return true;
|
|
996
1031
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1032
|
+
return !browseIds.has(id);
|
|
1033
|
+
});
|
|
1034
|
+
semanticResult = rawSemantic.results.length > 0 ? rawSemantic : null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (browseResult) {
|
|
1038
|
+
const retirePromises = [];
|
|
1039
|
+
for (const item of browseResult.results) {
|
|
1040
|
+
const entry = item.entry;
|
|
1041
|
+
const subject = typeof entry.subject === "string" ? entry.subject : "";
|
|
1042
|
+
if (!subject.toLowerCase().startsWith("session handoff")) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const entryId = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
|
|
1046
|
+
if (entryId) {
|
|
1047
|
+
retirePromises.push(
|
|
1048
|
+
runRetireTool(agenrPath, {
|
|
1000
1049
|
entry_id: entryId,
|
|
1001
1050
|
reason: "consumed at session start"
|
|
1002
|
-
}).catch((err) => {
|
|
1051
|
+
}).then(() => void 0).catch((err) => {
|
|
1003
1052
|
api.logger.debug?.(
|
|
1004
1053
|
`[agenr] session-start: retire handoff ${entryId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1005
1054
|
);
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1055
|
+
})
|
|
1056
|
+
);
|
|
1057
|
+
} else {
|
|
1058
|
+
api.logger.debug?.(
|
|
1059
|
+
"[agenr] session-start: handoff entry missing id, skipping retire"
|
|
1060
|
+
);
|
|
1012
1061
|
}
|
|
1013
1062
|
}
|
|
1063
|
+
await Promise.allSettled(retirePromises);
|
|
1064
|
+
}
|
|
1065
|
+
const sections = [];
|
|
1066
|
+
if (previousTurns.trim()) {
|
|
1067
|
+
sections.push(`## Recent session
|
|
1068
|
+
${previousTurns.trim()}`);
|
|
1069
|
+
}
|
|
1070
|
+
if (browseResult) {
|
|
1071
|
+
const formatted = formatRecallAsMarkdown(browseResult);
|
|
1072
|
+
if (formatted.trim()) {
|
|
1073
|
+
sections.push(`## Recent memory
|
|
1074
|
+
${formatted.trim()}`);
|
|
1075
|
+
}
|
|
1014
1076
|
}
|
|
1077
|
+
if (semanticResult) {
|
|
1078
|
+
const formatted = formatRecallAsMarkdown(semanticResult);
|
|
1079
|
+
if (formatted.trim()) {
|
|
1080
|
+
sections.push(`## Relevant memory
|
|
1081
|
+
${formatted.trim()}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
markdown = sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
1015
1085
|
}
|
|
1016
1086
|
let signal;
|
|
1017
1087
|
if (config?.signalsEnabled !== false) {
|
|
@@ -1054,10 +1124,6 @@ var plugin = {
|
|
|
1054
1124
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1055
1125
|
return;
|
|
1056
1126
|
}
|
|
1057
|
-
const lastUserText = extractLastUserText(messages);
|
|
1058
|
-
if (shouldStashTopic(lastUserText)) {
|
|
1059
|
-
stashSessionTopic(sessionKey, lastUserText);
|
|
1060
|
-
}
|
|
1061
1127
|
const handoffText = extractLastExchangeText(messages);
|
|
1062
1128
|
if (handoffText) {
|
|
1063
1129
|
const agenrPath = resolveAgenrPath(config);
|
|
@@ -1086,7 +1152,7 @@ var plugin = {
|
|
|
1086
1152
|
}
|
|
1087
1153
|
} catch (err) {
|
|
1088
1154
|
api.logger.warn(
|
|
1089
|
-
`agenr plugin before_reset
|
|
1155
|
+
`agenr plugin before_reset failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1090
1156
|
);
|
|
1091
1157
|
}
|
|
1092
1158
|
});
|
|
@@ -1242,9 +1308,7 @@ var plugin = {
|
|
|
1242
1308
|
}
|
|
1243
1309
|
};
|
|
1244
1310
|
var __testing = {
|
|
1245
|
-
SESSION_TOPIC_TTL_MS,
|
|
1246
1311
|
clearState() {
|
|
1247
|
-
clearStash();
|
|
1248
1312
|
seenSessions.clear();
|
|
1249
1313
|
sessionSignalState.clear();
|
|
1250
1314
|
}
|