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 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 = 5e3;
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 (_event, ctx) => {
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 recallResult = await runRecall(agenrPath, budget, project);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"