agenr 0.8.9 → 0.8.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 +15 -0
- package/dist/openclaw-plugin/index.d.ts +12 -1
- package/dist/openclaw-plugin/index.js +162 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.10]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- feat(plugin): session-start recall now uses the inbound user message as the recall query seed, enabling vector similarity scoring instead of pure recency ranking; entries relevant to the actual conversation topic now surface at session start (issues #177, #178)
|
|
7
|
+
- feat(plugin): before_reset hook captures the last 3 substantive user messages before a /new reset and stashes them in memory; the next session-start recall uses the stash as its query seed when the opening prompt is low-signal (issues #177, #178)
|
|
8
|
+
- feat(plugin): session topic stash eviction sweep runs every 5 minutes; TTL is 1 hour
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- chore(plugin): session-start recall timeout increased from 5s to 10s to accommodate the embedding API call now required when a query is present
|
|
12
|
+
- chore(plugin): session topic stash requires a minimum of 40 characters and 5 words to filter out low-signal conversational closers
|
|
13
|
+
- refactor(plugin): session query helpers extracted from index.ts into session-query.ts
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- fix(plugin): session-start recall no longer skips vector similarity scoring when a query is available; previously RecallQuery.text was always undefined at session start (issue #177)
|
|
17
|
+
|
|
3
18
|
## [0.8.9]
|
|
4
19
|
|
|
5
20
|
### Added
|
|
@@ -23,6 +23,12 @@ type BeforePromptBuildResult = {
|
|
|
23
23
|
systemPrompt?: string;
|
|
24
24
|
prependContext?: string;
|
|
25
25
|
};
|
|
26
|
+
type BeforeResetEvent = {
|
|
27
|
+
sessionFile?: string;
|
|
28
|
+
messages?: unknown[];
|
|
29
|
+
reason?: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
26
32
|
type PluginLogger = {
|
|
27
33
|
debug?: (message: string) => void;
|
|
28
34
|
info?: (message: string) => void;
|
|
@@ -58,6 +64,7 @@ type PluginApi = {
|
|
|
58
64
|
on: {
|
|
59
65
|
(hook: "before_agent_start", handler: (event: BeforeAgentStartEvent, ctx: PluginHookAgentContext) => Promise<BeforeAgentStartResult | undefined> | BeforeAgentStartResult | undefined): void;
|
|
60
66
|
(hook: "before_prompt_build", handler: (event: BeforePromptBuildEvent, ctx: PluginHookAgentContext) => Promise<BeforePromptBuildResult | undefined> | BeforePromptBuildResult | undefined): void;
|
|
67
|
+
(hook: "before_reset", handler: (event: BeforeResetEvent, ctx: PluginHookAgentContext) => Promise<void> | void): void;
|
|
61
68
|
};
|
|
62
69
|
};
|
|
63
70
|
|
|
@@ -67,5 +74,9 @@ declare const plugin: {
|
|
|
67
74
|
description: string;
|
|
68
75
|
register(api: PluginApi): void;
|
|
69
76
|
};
|
|
77
|
+
declare const __testing: {
|
|
78
|
+
SESSION_TOPIC_TTL_MS: number;
|
|
79
|
+
clearState(): void;
|
|
80
|
+
};
|
|
70
81
|
|
|
71
|
-
export { plugin as default };
|
|
82
|
+
export { __testing, plugin as default };
|
|
@@ -13,7 +13,8 @@ import { Type } from "@sinclair/typebox";
|
|
|
13
13
|
import { spawn } from "child_process";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
|
-
var RECALL_TIMEOUT_MS =
|
|
16
|
+
var RECALL_TIMEOUT_MS = 1e4;
|
|
17
|
+
var RECALL_QUERY_MAX_CHARS = 500;
|
|
17
18
|
var DEFAULT_BUDGET = 2e3;
|
|
18
19
|
var MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
var PACKAGE_ROOT = path.resolve(MODULE_DIR, "..", "..");
|
|
@@ -30,7 +31,7 @@ function buildSpawnArgs(agenrPath) {
|
|
|
30
31
|
}
|
|
31
32
|
return { cmd: agenrPath, args: [] };
|
|
32
33
|
}
|
|
33
|
-
async function runRecall(agenrPath, budget, project) {
|
|
34
|
+
async function runRecall(agenrPath, budget, project, query) {
|
|
34
35
|
return await new Promise((resolve) => {
|
|
35
36
|
let stdout = "";
|
|
36
37
|
let settled = false;
|
|
@@ -46,6 +47,11 @@ async function runRecall(agenrPath, budget, project) {
|
|
|
46
47
|
if (project) {
|
|
47
48
|
args.push("--project", project);
|
|
48
49
|
}
|
|
50
|
+
const trimmedQuery = query?.trim() ?? "";
|
|
51
|
+
const truncatedQuery = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
|
|
52
|
+
if (truncatedQuery) {
|
|
53
|
+
args.push(truncatedQuery);
|
|
54
|
+
}
|
|
49
55
|
const child = spawn(spawnArgs.cmd, [...spawnArgs.args, ...args], {
|
|
50
56
|
stdio: ["ignore", "pipe", "ignore"]
|
|
51
57
|
});
|
|
@@ -133,6 +139,127 @@ function formatRecallAsMarkdown(result) {
|
|
|
133
139
|
return lines.join("\n").trimEnd();
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
// src/openclaw-plugin/session-query.ts
|
|
143
|
+
var SESSION_TOPIC_TTL_MS = 60 * 60 * 1e3;
|
|
144
|
+
var SESSION_TOPIC_MIN_LENGTH = 40;
|
|
145
|
+
var SESSION_QUERY_LOOKBACK = 3;
|
|
146
|
+
var sessionTopicStash = /* @__PURE__ */ new Map();
|
|
147
|
+
function isRecord(value) {
|
|
148
|
+
return typeof value === "object" && value !== null;
|
|
149
|
+
}
|
|
150
|
+
function extractTextFromUserMessage(message) {
|
|
151
|
+
if (!isRecord(message) || message["role"] !== "user") {
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
const content = message["content"];
|
|
155
|
+
if (typeof content === "string") {
|
|
156
|
+
return content.trim();
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(content)) {
|
|
159
|
+
return "";
|
|
160
|
+
}
|
|
161
|
+
const textParts = [];
|
|
162
|
+
for (const part of content) {
|
|
163
|
+
if (!isRecord(part) || part["type"] !== "text") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const partText = part["text"];
|
|
167
|
+
if (typeof partText !== "string") {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const trimmed = partText.trim();
|
|
171
|
+
if (trimmed) {
|
|
172
|
+
textParts.push(trimmed);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return textParts.join(" ").trim();
|
|
176
|
+
}
|
|
177
|
+
function isThinPrompt(prompt) {
|
|
178
|
+
const trimmed = prompt.trim().toLowerCase();
|
|
179
|
+
return trimmed === "" || trimmed === "/new" || trimmed === "/reset";
|
|
180
|
+
}
|
|
181
|
+
function extractLastUserText(messages) {
|
|
182
|
+
try {
|
|
183
|
+
const collected = [];
|
|
184
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
185
|
+
const extracted = extractTextFromUserMessage(messages[index]);
|
|
186
|
+
if (!extracted) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
collected.push(extracted);
|
|
190
|
+
if (collected.length >= SESSION_QUERY_LOOKBACK) {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const joined = collected.reverse().join(" ").trim();
|
|
195
|
+
return joined || "";
|
|
196
|
+
} catch {
|
|
197
|
+
return "";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function shouldStashTopic(text) {
|
|
201
|
+
if (text.length < SESSION_TOPIC_MIN_LENGTH) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
205
|
+
return wordCount >= 5;
|
|
206
|
+
}
|
|
207
|
+
function sweepExpiredStash() {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
for (const [key, entry] of sessionTopicStash) {
|
|
210
|
+
if (now - entry.storedAt > SESSION_TOPIC_TTL_MS) {
|
|
211
|
+
sessionTopicStash.delete(key);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
var sweepInterval = setInterval(sweepExpiredStash, 5 * 60 * 1e3);
|
|
216
|
+
if (sweepInterval !== void 0 && typeof sweepInterval.unref === "function") {
|
|
217
|
+
sweepInterval.unref();
|
|
218
|
+
}
|
|
219
|
+
function stripResetPrefix(prompt) {
|
|
220
|
+
const lower = prompt.toLowerCase();
|
|
221
|
+
for (const cmd of ["/new", "/reset"]) {
|
|
222
|
+
if (lower.startsWith(cmd + " ")) {
|
|
223
|
+
return prompt.slice(cmd.length).trim();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return prompt;
|
|
227
|
+
}
|
|
228
|
+
function resolveSessionQuery(prompt, sessionKey) {
|
|
229
|
+
let stashedText;
|
|
230
|
+
if (sessionKey) {
|
|
231
|
+
const entry = sessionTopicStash.get(sessionKey);
|
|
232
|
+
if (entry) {
|
|
233
|
+
sessionTopicStash.delete(sessionKey);
|
|
234
|
+
const expired = Date.now() - entry.storedAt > SESSION_TOPIC_TTL_MS;
|
|
235
|
+
if (!expired && entry.text.length > 0) {
|
|
236
|
+
stashedText = entry.text;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const normalized = (prompt ?? "").trim();
|
|
241
|
+
if (!isThinPrompt(normalized)) {
|
|
242
|
+
return stripResetPrefix(normalized);
|
|
243
|
+
}
|
|
244
|
+
return stashedText;
|
|
245
|
+
}
|
|
246
|
+
function stashSessionTopic(sessionKey, text) {
|
|
247
|
+
if (!shouldStashTopic(text)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
sessionTopicStash.set(sessionKey, {
|
|
251
|
+
text,
|
|
252
|
+
storedAt: Date.now()
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function clearStash() {
|
|
256
|
+
sessionTopicStash.clear();
|
|
257
|
+
if (sweepInterval !== void 0) {
|
|
258
|
+
clearInterval(sweepInterval);
|
|
259
|
+
sweepInterval = void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
136
263
|
// src/db/signals.ts
|
|
137
264
|
async function fetchNewSignalEntries(db, sinceSeq, minImportance, limit, maxAgeSec = 0) {
|
|
138
265
|
const ageArgs = maxAgeSec > 0 ? [minImportance, sinceSeq, new Date(Date.now() - maxAgeSec * 1e3).toISOString(), limit] : [minImportance, sinceSeq, limit];
|
|
@@ -712,7 +839,7 @@ var plugin = {
|
|
|
712
839
|
register(api) {
|
|
713
840
|
api.on(
|
|
714
841
|
"before_prompt_build",
|
|
715
|
-
async (
|
|
842
|
+
async (event, ctx) => {
|
|
716
843
|
try {
|
|
717
844
|
const sessionKey = ctx.sessionKey ?? "";
|
|
718
845
|
if (!sessionKey) {
|
|
@@ -735,7 +862,8 @@ var plugin = {
|
|
|
735
862
|
const agenrPath = resolveAgenrPath(config);
|
|
736
863
|
const budget = resolveBudget(config);
|
|
737
864
|
const project = config?.project?.trim() || void 0;
|
|
738
|
-
const
|
|
865
|
+
const queryText = resolveSessionQuery(event.prompt, ctx.sessionKey);
|
|
866
|
+
const recallResult = await runRecall(agenrPath, budget, project, queryText);
|
|
739
867
|
if (recallResult) {
|
|
740
868
|
const formatted = formatRecallAsMarkdown(recallResult);
|
|
741
869
|
if (formatted.trim()) {
|
|
@@ -774,6 +902,27 @@ var plugin = {
|
|
|
774
902
|
}
|
|
775
903
|
}
|
|
776
904
|
);
|
|
905
|
+
api.on("before_reset", (event, ctx) => {
|
|
906
|
+
try {
|
|
907
|
+
const sessionKey = ctx.sessionKey;
|
|
908
|
+
if (!sessionKey) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const messages = event.messages;
|
|
912
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const lastUserText = extractLastUserText(messages);
|
|
916
|
+
if (!shouldStashTopic(lastUserText)) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
stashSessionTopic(sessionKey, lastUserText);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
api.logger.warn(
|
|
922
|
+
`agenr plugin before_reset stash failed: ${err instanceof Error ? err.message : String(err)}`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
});
|
|
777
926
|
if (api.registerTool) {
|
|
778
927
|
const config = api.pluginConfig;
|
|
779
928
|
if (config?.enabled === false) {
|
|
@@ -924,7 +1073,16 @@ var plugin = {
|
|
|
924
1073
|
}
|
|
925
1074
|
}
|
|
926
1075
|
};
|
|
1076
|
+
var __testing = {
|
|
1077
|
+
SESSION_TOPIC_TTL_MS,
|
|
1078
|
+
clearState() {
|
|
1079
|
+
clearStash();
|
|
1080
|
+
seenSessions.clear();
|
|
1081
|
+
sessionSignalState.clear();
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
927
1084
|
var openclaw_plugin_default = plugin;
|
|
928
1085
|
export {
|
|
1086
|
+
__testing,
|
|
929
1087
|
openclaw_plugin_default as default
|
|
930
1088
|
};
|