agenr 0.8.37 → 0.8.38

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,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.38] - 2026-02-24
4
+
5
+ ### Fixed
6
+ - Handoff log line now shows model ID string instead of [object Object]
7
+ - Upgraded handoff retirement and browse debug logs from logger.debug to
8
+ console.log for production visibility
9
+ - Handoff transcript now strips OpenClaw/agenr injected context (memory
10
+ blocks, signals, conversation metadata, timestamp prefixes) before
11
+ sending to the LLM, preventing the summarizer from summarizing its own
12
+ metadata (#235)
13
+
14
+ ### Added
15
+ - Opt-in `handoff.includeBackground` config flag for handoff summarizer: when
16
+ enabled, prior session messages are included as background context with strong
17
+ section headers so the LLM can orient without blending stale facts into the
18
+ current session summary (#235)
19
+ - New system prompt variant with anti-hallucination instructions for background
20
+ context mode ("BACKGROUND CONTEXT (DO NOT SUMMARIZE)" / "SUMMARIZE THIS
21
+ SESSION ONLY" section headers)
22
+ - Optional `handoff.logDir` config: when set, writes the full LLM request
23
+ transcript and response to files for prompt tuning and debugging (#235)
24
+
25
+ ### Changed
26
+ - Default handoff behavior unchanged: current session only, no prior messages
27
+
28
+ ### Removed
29
+ - All temporary [AGENR-PROBE] debug logging from openclaw-plugin (replaced with
30
+ clean operational logs where needed)
31
+
3
32
  ## [0.8.37] - 2026-02-24
4
33
 
5
34
  ### Fixed
@@ -76,12 +76,14 @@ type HandoffMessage = {
76
76
  content: string;
77
77
  timestamp: string;
78
78
  };
79
+ declare function stripInjectedContext(text: string): string;
79
80
  declare function getBaseSessionPath(filePath: string): string;
80
81
  declare function readSessionsJson(sessionsDir: string): Promise<Record<string, unknown>>;
81
82
  declare function readAndParseSessionJsonl(sessionFile: string): Promise<unknown[]>;
82
83
  declare function getSurfaceForSessionFile(sessionFilePath: string, sessionsJson: Record<string, unknown>): string;
83
84
  declare function readMessagesFromJsonl(filePath: string): Promise<HandoffMessage[]>;
84
85
  declare function findPriorResetFile(sessionsDir: string, currentSessionFile: string): Promise<string | null>;
86
+ declare function buildTranscript(messages: HandoffMessage[], surface: string): string;
85
87
  declare function buildMergedTranscript(priorMessages: HandoffMessage[], priorSurface: string, currentMessages: HandoffMessage[], currentSurface: string): string;
86
88
  declare function capTranscriptLength(params: {
87
89
  priorMessages: HandoffMessage[];
@@ -90,7 +92,7 @@ declare function capTranscriptLength(params: {
90
92
  currentSurface: string;
91
93
  maxChars: number;
92
94
  }): string;
93
- declare function summarizeSessionForHandoff(currentRawMessages: BeforeResetEvent["messages"], sessionsDir: string, currentSessionFile: string, logger: PluginApi["logger"], streamSimpleImpl?: StreamSimpleFn): Promise<string | null>;
95
+ declare function summarizeSessionForHandoff(currentRawMessages: BeforeResetEvent["messages"], sessionsDir: string, currentSessionFile: string, logger: PluginApi["logger"], includeBackground: boolean, streamSimpleImpl?: StreamSimpleFn, logEnabled?: boolean, logDir?: string): Promise<string | null>;
94
96
  declare function runHandoffForSession(opts: {
95
97
  messages: unknown[];
96
98
  sessionFile: string | null;
@@ -102,8 +104,12 @@ declare function runHandoffForSession(opts: {
102
104
  defaultProject: string | undefined;
103
105
  storeConfig: Record<string, unknown>;
104
106
  sessionsDir: string;
107
+ includeBackground?: boolean;
108
+ logEnabled?: boolean;
109
+ logDir?: string;
105
110
  logger: PluginLogger | undefined;
106
111
  source: "before_reset" | "command" | "session_start";
112
+ dbPath?: string;
107
113
  }): Promise<void>;
108
114
  declare const plugin: {
109
115
  id: string;
@@ -118,9 +124,13 @@ declare const __testing: {
118
124
  getBaseSessionPath: typeof getBaseSessionPath;
119
125
  getSurfaceForSessionFile: typeof getSurfaceForSessionFile;
120
126
  readMessagesFromJsonl: typeof readMessagesFromJsonl;
127
+ stripInjectedContext: typeof stripInjectedContext;
121
128
  findPriorResetFile: typeof findPriorResetFile;
129
+ buildTranscript: typeof buildTranscript;
122
130
  buildMergedTranscript: typeof buildMergedTranscript;
123
131
  capTranscriptLength: typeof capTranscriptLength;
132
+ HANDOFF_SUMMARY_SYSTEM_PROMPT: string;
133
+ HANDOFF_SUMMARY_SYSTEM_PROMPT_WITH_BACKGROUND: string;
124
134
  summarizeSessionForHandoff: typeof summarizeSessionForHandoff;
125
135
  runHandoffForSession: typeof runHandoffForSession;
126
136
  };
@@ -37,7 +37,7 @@ function buildSpawnArgs(agenrPath) {
37
37
  }
38
38
  return { cmd: agenrPath, args: [] };
39
39
  }
40
- async function runRecall(agenrPath, budget, project, query, options) {
40
+ async function runRecall(agenrPath, budget, project, query, options, dbPath) {
41
41
  return await new Promise((resolve) => {
42
42
  let stdout = "";
43
43
  let settled = false;
@@ -64,7 +64,8 @@ async function runRecall(agenrPath, budget, project, query, options) {
64
64
  args.push(truncatedQuery);
65
65
  }
66
66
  }
67
- const child = spawn(spawnArgs.cmd, [...spawnArgs.args, ...args], {
67
+ const finalArgs = dbPath ? [...args, "--db", dbPath] : args;
68
+ const child = spawn(spawnArgs.cmd, [...spawnArgs.args, ...finalArgs], {
68
69
  stdio: ["ignore", "pipe", "ignore"]
69
70
  });
70
71
  const timer = setTimeout(() => {
@@ -592,7 +593,7 @@ function inferPlatformFromEntries(entries) {
592
593
  }
593
594
  return void 0;
594
595
  }
595
- async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_TIMEOUT_MS) {
596
+ async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_TIMEOUT_MS, dbPath) {
596
597
  return await new Promise((resolve) => {
597
598
  const resolvedAgenrPath = agenrPath.trim() || resolveAgenrPath();
598
599
  const spawnArgs = buildSpawnArgs(resolvedAgenrPath);
@@ -608,7 +609,8 @@ async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_T
608
609
  clearTimeout(timer);
609
610
  resolve(result);
610
611
  };
611
- const child = spawn2(spawnArgs.cmd, [...spawnArgs.args, ...args], {
612
+ const finalArgs = dbPath ? [...args, "--db", dbPath] : args;
613
+ const child = spawn2(spawnArgs.cmd, [...spawnArgs.args, ...finalArgs], {
612
614
  stdio: ["pipe", "pipe", "pipe"]
613
615
  });
614
616
  const timer = setTimeout(() => {
@@ -634,7 +636,7 @@ async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_T
634
636
  child.stdin.end();
635
637
  });
636
638
  }
637
- async function runRecallTool(agenrPath, params, defaultProject) {
639
+ async function runRecallTool(agenrPath, params, defaultProject, dbPath) {
638
640
  const args = ["recall", "--json"];
639
641
  const query = asString(params.query);
640
642
  const context = asString(params.context);
@@ -671,7 +673,7 @@ async function runRecallTool(agenrPath, params, defaultProject) {
671
673
  if (project) {
672
674
  args.push("--project", project);
673
675
  }
674
- const result = await runAgenrCommand(agenrPath, args);
676
+ const result = await runAgenrCommand(agenrPath, args, void 0, void 0, dbPath);
675
677
  if (result.timedOut) {
676
678
  return {
677
679
  content: [{ type: "text", text: "agenr_recall failed: command timed out" }]
@@ -708,7 +710,7 @@ async function runRecallTool(agenrPath, params, defaultProject) {
708
710
  };
709
711
  }
710
712
  }
711
- async function runStoreTool(agenrPath, params, pluginConfig, defaultProject) {
713
+ async function runStoreTool(agenrPath, params, pluginConfig, defaultProject, dbPath) {
712
714
  const entries = Array.isArray(params.entries) ? params.entries : [];
713
715
  const project = asString(params.project) || defaultProject;
714
716
  const warn = resolveWarn(pluginConfig);
@@ -760,7 +762,7 @@ async function runStoreTool(agenrPath, params, pluginConfig, defaultProject) {
760
762
  if (typeof dedupConfig?.threshold === "number") {
761
763
  storeArgs.push("--dedup-threshold", String(dedupConfig.threshold));
762
764
  }
763
- const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(processedEntries));
765
+ const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(processedEntries), void 0, dbPath);
764
766
  if (result.timedOut) {
765
767
  return {
766
768
  content: [{ type: "text", text: "agenr_store timed out" }]
@@ -782,7 +784,7 @@ async function runStoreTool(agenrPath, params, pluginConfig, defaultProject) {
782
784
  details: { count: entries.length }
783
785
  };
784
786
  }
785
- async function runExtractTool(agenrPath, params) {
787
+ async function runExtractTool(agenrPath, params, dbPath) {
786
788
  const text = asString(params.text);
787
789
  if (!text) {
788
790
  return {
@@ -797,7 +799,7 @@ async function runExtractTool(agenrPath, params) {
797
799
  try {
798
800
  writeFileSync(tempFile, text, "utf8");
799
801
  args.push(tempFile);
800
- const extractResult = await runAgenrCommand(agenrPath, args, void 0, EXTRACT_TIMEOUT_MS);
802
+ const extractResult = await runAgenrCommand(agenrPath, args, void 0, EXTRACT_TIMEOUT_MS, dbPath);
801
803
  if (extractResult.timedOut) {
802
804
  return {
803
805
  content: [{ type: "text", text: "agenr_extract failed: command timed out" }]
@@ -842,7 +844,7 @@ async function runExtractTool(agenrPath, params) {
842
844
  }
843
845
  }
844
846
  }
845
- const storeResult = await runAgenrCommand(agenrPath, ["store"], JSON.stringify(entries));
847
+ const storeResult = await runAgenrCommand(agenrPath, ["store"], JSON.stringify(entries), void 0, dbPath);
846
848
  if (storeResult.timedOut) {
847
849
  return { content: [{ type: "text", text: "agenr_extract store step timed out" }] };
848
850
  }
@@ -874,7 +876,7 @@ async function runExtractTool(agenrPath, params) {
874
876
  }
875
877
  }
876
878
  }
877
- async function runRetireTool(agenrPath, params) {
879
+ async function runRetireTool(agenrPath, params, dbPath) {
878
880
  const entryId = asString(params.entry_id);
879
881
  if (!entryId) {
880
882
  return {
@@ -891,7 +893,7 @@ async function runRetireTool(agenrPath, params) {
891
893
  args.push("--persist");
892
894
  }
893
895
  args.push("--force");
894
- const result = await runAgenrCommand(agenrPath, args);
896
+ const result = await runAgenrCommand(agenrPath, args, void 0, void 0, dbPath);
895
897
  if (result.timedOut) {
896
898
  return {
897
899
  content: [{ type: "text", text: "agenr_retire failed: command timed out" }]
@@ -992,15 +994,13 @@ var HANDOFF_SUMMARY_SYSTEM_PROMPT = `You are a session summarizer for an AI agen
992
994
  to produce a concise handoff note from a conversation transcript so the
993
995
  next session can orient quickly.
994
996
 
995
- The transcript may cover one or two sessions labeled with timestamps and
996
- surfaces (webchat, telegram, etc.). Use this temporal and surface context
997
- in your summary. If the sessions are separated by more than 30 minutes,
998
- begin WORKING ON with a note like "Resumed 4 hours after a telegram
999
- session." If sessions are continuous, omit the gap.
997
+ The transcript contains messages from a single session labeled with a
998
+ timestamp and surface (webchat, telegram, etc.). Only include information
999
+ explicitly present in the transcript. Do not infer or invent details.
1000
1000
 
1001
- If the two sessions cover clearly unrelated topics, label them separately
1002
- using "PRIOR SESSION TOPIC:" and "CURRENT SESSION TOPIC:" instead of a
1003
- unified WORKING ON section.
1001
+ If the session contains no substantive work (e.g. just greetings or
1002
+ small talk), write "No significant activity" for WORKING ON and "None"
1003
+ for all other sections.
1004
1004
 
1005
1005
  Produce these sections in plain text (no markdown, no bullet symbols,
1006
1006
  use plain dashes for lists):
@@ -1019,6 +1019,45 @@ IMPORTANT FACTS: Stateful facts only - file paths, version numbers,
1019
1019
  env states, config values. Do NOT repeat decisions already in KEY
1020
1020
  FINDINGS. Write "None" if nothing new.
1021
1021
 
1022
+ Keep the total response under 500 words. Plain text only.`;
1023
+ var HANDOFF_SUMMARY_SYSTEM_PROMPT_WITH_BACKGROUND = `You are a session summarizer for an AI agent memory system. Your job is
1024
+ to produce a concise handoff note so the next session can orient quickly.
1025
+
1026
+ The transcript has two clearly labeled sections:
1027
+
1028
+ 1. "BACKGROUND CONTEXT (DO NOT SUMMARIZE)" - a prior session included
1029
+ ONLY for orientation. Do NOT include facts, versions, or states from
1030
+ this section in your summary. It is there so you understand what came
1031
+ before.
1032
+
1033
+ 2. "SUMMARIZE THIS SESSION ONLY" - the session that just ended. Your
1034
+ summary must cover ONLY this section.
1035
+
1036
+ If the current session contains no substantive work (e.g. just greetings
1037
+ or small talk), write "No significant activity" for WORKING ON and
1038
+ "None" for all other sections. Do NOT fall back to summarizing the
1039
+ background context.
1040
+
1041
+ Only include information explicitly present in the current session
1042
+ transcript. Do not infer or invent details.
1043
+
1044
+ Produce these sections in plain text (no markdown, no bullet symbols,
1045
+ use plain dashes for lists):
1046
+
1047
+ WORKING ON: One to two sentences on the main task or topic, including
1048
+ the project or system being worked on (e.g. "Working on agenr plugin
1049
+ (#199) in the agenr-ai/agenr repo.").
1050
+
1051
+ KEY FINDINGS: Decisions, discoveries, or conclusions reached in the
1052
+ current session only.
1053
+
1054
+ OPEN THREADS: Unresolved questions or next steps. Write "None" if
1055
+ everything was wrapped up.
1056
+
1057
+ IMPORTANT FACTS: Stateful facts only - file paths, version numbers,
1058
+ env states, config values. Do NOT repeat decisions already in KEY
1059
+ FINDINGS. Write "None" if nothing new.
1060
+
1022
1061
  Keep the total response under 500 words. Plain text only.`;
1023
1062
  function isRecord2(value) {
1024
1063
  return typeof value === "object" && value !== null;
@@ -1049,6 +1088,27 @@ function extractTextFromContent(content, separator) {
1049
1088
  function extractHandoffContent(content) {
1050
1089
  return extractTextFromContent(content, " ");
1051
1090
  }
1091
+ function stripInjectedContext(text) {
1092
+ if (!text) {
1093
+ return "";
1094
+ }
1095
+ let cleaned = text;
1096
+ cleaned = cleaned.replace(
1097
+ /(?:^|\n)## (?:Recent session|Recent memory|Relevant memory)\b[\s\S]*?(?=(?:^|\n)(?:## (?!Recent session|Recent memory|Relevant memory|agenr Memory Context)|Conversation info|\[(?:user|assistant)\]:|AGENR SIGNAL:|=== )|$)/g,
1098
+ ""
1099
+ );
1100
+ cleaned = cleaned.replace(
1101
+ /(?:^|\n)## agenr Memory Context[\s\S]*?(?=(?:^|\n)(?:## (?!agenr Memory Context)|Conversation info|\[(?:user|assistant)\]:|AGENR SIGNAL:|=== )|$)/g,
1102
+ ""
1103
+ );
1104
+ cleaned = cleaned.replace(/^\s*AGENR SIGNAL:.*(?:\n\s*-\s*\[[^\n]*)*/gm, "");
1105
+ cleaned = cleaned.replace(
1106
+ /Conversation info \(untrusted metadata\):\s*\`\`\`json[\s\S]*?\`\`\`/g,
1107
+ ""
1108
+ );
1109
+ cleaned = cleaned.replace(/\[\w{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2} (?:[A-Z]{2,5}|GMT[+-]\d{1,2})\] /g, "");
1110
+ return cleaned.trim();
1111
+ }
1052
1112
  function formatHandoffTimestamp(timestamp) {
1053
1113
  if (!timestamp) {
1054
1114
  return "unknown time";
@@ -1060,6 +1120,9 @@ function formatHandoffTimestamp(timestamp) {
1060
1120
  const pad2 = (value) => value.toString().padStart(2, "0");
1061
1121
  return `${parsed.getFullYear()}-${pad2(parsed.getMonth() + 1)}-${pad2(parsed.getDate())} ${pad2(parsed.getHours())}:${pad2(parsed.getMinutes())}`;
1062
1122
  }
1123
+ function formatHandoffLogTimestamp(date) {
1124
+ return date.toISOString().slice(0, 19).replace(/:/g, "-");
1125
+ }
1063
1126
  function getBaseSessionPath(filePath) {
1064
1127
  const resetMatch = filePath.match(/^(.+\.jsonl)\.reset\..+$/);
1065
1128
  return resetMatch ? resetMatch[1] : filePath;
@@ -1157,13 +1220,14 @@ async function readMessagesFromJsonl(filePath) {
1157
1220
  continue;
1158
1221
  }
1159
1222
  const content = extractHandoffContent(message["content"]);
1160
- if (!content) {
1223
+ const strippedContent = stripInjectedContext(content);
1224
+ if (!strippedContent) {
1161
1225
  continue;
1162
1226
  }
1163
1227
  const timestamp = typeof parsed["timestamp"] === "string" ? parsed["timestamp"] : "";
1164
1228
  messages.push({
1165
1229
  role,
1166
- content,
1230
+ content: strippedContent,
1167
1231
  timestamp
1168
1232
  });
1169
1233
  }
@@ -1174,7 +1238,8 @@ async function readMessagesFromJsonl(filePath) {
1174
1238
  }
1175
1239
  async function findPriorResetFile(sessionsDir, currentSessionFile) {
1176
1240
  try {
1177
- const currentUuid = path3.basename(currentSessionFile, ".jsonl");
1241
+ const currentBasename = path3.basename(currentSessionFile);
1242
+ const currentUuid = currentBasename.split(".jsonl")[0];
1178
1243
  const entries = await fs.promises.readdir(sessionsDir);
1179
1244
  const resetEntries = entries.filter((entry) => /\.jsonl\.reset\./.test(entry));
1180
1245
  const candidates = resetEntries.filter((entry) => entry.split(".jsonl")[0] !== currentUuid);
@@ -1205,17 +1270,26 @@ async function findPriorResetFile(sessionsDir, currentSessionFile) {
1205
1270
  return null;
1206
1271
  }
1207
1272
  }
1273
+ function buildTranscript(messages, surface) {
1274
+ const cleanMessages = messages.map((message) => ({ ...message, content: message.content.trim() })).filter((message) => message.content.length > 0);
1275
+ const lines = [];
1276
+ lines.push(`--- Session ${formatHandoffTimestamp(cleanMessages[0]?.timestamp ?? "")} [${surface}] (ended) ---`);
1277
+ lines.push(...cleanMessages.map((message) => `[${message.role}]: ${message.content}`));
1278
+ return lines.join("\n");
1279
+ }
1208
1280
  function buildMergedTranscript(priorMessages, priorSurface, currentMessages, currentSurface) {
1209
1281
  const cleanPrior = priorMessages.map((message) => ({ ...message, content: message.content.trim() })).filter((message) => message.content.length > 0);
1210
1282
  const cleanCurrent = currentMessages.map((message) => ({ ...message, content: message.content.trim() })).filter((message) => message.content.length > 0);
1211
1283
  const lines = [];
1212
1284
  if (cleanPrior.length > 0) {
1213
- lines.push(`--- Session ${formatHandoffTimestamp(cleanPrior[0]?.timestamp ?? "")} [${priorSurface}] ---`);
1285
+ lines.push("=== BACKGROUND CONTEXT (DO NOT SUMMARIZE) ===");
1286
+ lines.push(`Prior session ${formatHandoffTimestamp(cleanPrior[0]?.timestamp ?? "")} [${priorSurface}]`);
1214
1287
  lines.push(...cleanPrior.map((message) => `[${message.role}]: ${message.content}`));
1215
1288
  lines.push("");
1216
1289
  }
1290
+ lines.push("=== SUMMARIZE THIS SESSION ONLY ===");
1217
1291
  lines.push(
1218
- `--- Session ${formatHandoffTimestamp(cleanCurrent[0]?.timestamp ?? "")} [${currentSurface}] (ended) ---`
1292
+ `Current session ${formatHandoffTimestamp(cleanCurrent[0]?.timestamp ?? "")} [${currentSurface}] (ended)`
1219
1293
  );
1220
1294
  lines.push(...cleanCurrent.map((message) => `[${message.role}]: ${message.content}`));
1221
1295
  return lines.join("\n");
@@ -1264,51 +1338,71 @@ function mapCurrentSessionMessage(message) {
1264
1338
  return null;
1265
1339
  }
1266
1340
  const content = extractHandoffContent(message["content"]);
1267
- if (!content) {
1341
+ const strippedContent = stripInjectedContext(content);
1342
+ if (!strippedContent) {
1268
1343
  return null;
1269
1344
  }
1270
1345
  const timestamp = typeof message["timestamp"] === "string" ? message["timestamp"] : "";
1271
1346
  return {
1272
1347
  role,
1273
- content,
1348
+ content: strippedContent,
1274
1349
  timestamp
1275
1350
  };
1276
1351
  }
1277
- async function summarizeSessionForHandoff(currentRawMessages, sessionsDir, currentSessionFile, logger, streamSimpleImpl) {
1352
+ async function summarizeSessionForHandoff(currentRawMessages, sessionsDir, currentSessionFile, logger, includeBackground, streamSimpleImpl, logEnabled = false, logDir) {
1278
1353
  try {
1279
1354
  const sessionsJson = await readSessionsJson(sessionsDir);
1280
1355
  const currentSurface = getSurfaceForSessionFile(currentSessionFile, sessionsJson);
1281
1356
  const currentMessages = (Array.isArray(currentRawMessages) ? currentRawMessages : []).map((message) => mapCurrentSessionMessage(message)).filter((message) => message !== null);
1282
- const currentSlice = currentMessages.slice(
1357
+ let currentSlice = currentMessages.slice(
1283
1358
  -Math.min(currentMessages.length, HANDOFF_TRANSCRIPT_MAX_MESSAGES)
1284
1359
  );
1285
- const priorBudget = HANDOFF_TRANSCRIPT_MAX_MESSAGES - currentSlice.length;
1360
+ if (currentSlice.length < 4) {
1361
+ console.log("[agenr] before_reset: skipping LLM summary - reason: too few messages");
1362
+ return null;
1363
+ }
1286
1364
  let priorSlice = [];
1287
1365
  let priorSurface = "prior session";
1288
- const priorFile = await findPriorResetFile(sessionsDir, currentSessionFile);
1289
- if (priorFile && priorBudget > 0) {
1290
- const priorAllMessages = await readMessagesFromJsonl(priorFile);
1291
- priorSlice = priorAllMessages.slice(-priorBudget);
1292
- priorSurface = getSurfaceForSessionFile(getBaseSessionPath(priorFile), sessionsJson);
1366
+ let transcript = "";
1367
+ if (includeBackground) {
1368
+ const priorBudget = HANDOFF_TRANSCRIPT_MAX_MESSAGES - currentSlice.length;
1369
+ const priorFile = await findPriorResetFile(sessionsDir, currentSessionFile);
1370
+ if (priorFile && priorBudget > 0) {
1371
+ const priorAllMessages = await readMessagesFromJsonl(priorFile);
1372
+ priorSlice = priorAllMessages.slice(-priorBudget);
1373
+ priorSurface = getSurfaceForSessionFile(getBaseSessionPath(priorFile), sessionsJson);
1374
+ }
1375
+ transcript = buildMergedTranscript(priorSlice, priorSurface, currentSlice, currentSurface);
1376
+ } else {
1377
+ transcript = buildTranscript(currentSlice, currentSurface);
1293
1378
  }
1294
- let transcript = buildMergedTranscript(priorSlice, priorSurface, currentSlice, currentSurface);
1295
1379
  const nonHeaderLineCount = countTranscriptContentLines(transcript);
1296
1380
  if (nonHeaderLineCount < 3) {
1297
1381
  console.log("[agenr] before_reset: skipping LLM summary - reason: short transcript");
1298
1382
  return null;
1299
1383
  }
1300
- if (currentSlice.length < 5 && priorSlice.length === 0) {
1301
- console.log("[agenr] before_reset: skipping LLM summary - reason: too few messages");
1302
- return null;
1303
- }
1304
- if (transcript.length > HANDOFF_TRANSCRIPT_MAX_CHARS) {
1305
- transcript = capTranscriptLength({
1306
- priorMessages: priorSlice,
1307
- priorSurface,
1308
- currentMessages: currentSlice,
1309
- currentSurface,
1310
- maxChars: HANDOFF_TRANSCRIPT_MAX_CHARS
1311
- });
1384
+ if (includeBackground) {
1385
+ if (transcript.length > HANDOFF_TRANSCRIPT_MAX_CHARS) {
1386
+ transcript = capTranscriptLength({
1387
+ priorMessages: priorSlice,
1388
+ priorSurface,
1389
+ currentMessages: currentSlice,
1390
+ currentSurface,
1391
+ maxChars: HANDOFF_TRANSCRIPT_MAX_CHARS
1392
+ });
1393
+ }
1394
+ } else if (transcript.length > HANDOFF_TRANSCRIPT_MAX_CHARS) {
1395
+ while (transcript.length > HANDOFF_TRANSCRIPT_MAX_CHARS && currentSlice.length > 1) {
1396
+ currentSlice = currentSlice.slice(1);
1397
+ transcript = buildTranscript(currentSlice, currentSurface);
1398
+ }
1399
+ if (transcript.length > HANDOFF_TRANSCRIPT_MAX_CHARS) {
1400
+ transcript = transcript.slice(-HANDOFF_TRANSCRIPT_MAX_CHARS);
1401
+ const firstNewline = transcript.indexOf("\n");
1402
+ if (firstNewline > 0) {
1403
+ transcript = transcript.slice(firstNewline + 1);
1404
+ }
1405
+ }
1312
1406
  }
1313
1407
  let llmClient;
1314
1408
  try {
@@ -1324,14 +1418,21 @@ async function summarizeSessionForHandoff(currentRawMessages, sessionsDir, curre
1324
1418
  console.log("[agenr] before_reset: no apiKey available, skipping LLM summary");
1325
1419
  return null;
1326
1420
  }
1421
+ const systemPrompt = includeBackground ? HANDOFF_SUMMARY_SYSTEM_PROMPT_WITH_BACKGROUND : HANDOFF_SUMMARY_SYSTEM_PROMPT;
1327
1422
  const context = {
1328
- systemPrompt: HANDOFF_SUMMARY_SYSTEM_PROMPT,
1423
+ systemPrompt,
1329
1424
  messages: [{ role: "user", content: transcript, timestamp: Date.now() }],
1330
1425
  tools: []
1331
1426
  };
1332
- console.log(
1333
- `[agenr] before_reset: sending to LLM model=${resolvedModel.model} chars=${transcript.length} currentMsgs=${currentSlice.length} priorMsgs=${priorSlice.length}`
1334
- );
1427
+ if (includeBackground) {
1428
+ console.log(
1429
+ `[agenr] before_reset: sending to LLM model=${resolvedModel.modelId} chars=${transcript.length} currentMsgs=${currentSlice.length} priorMsgs=${priorSlice.length}`
1430
+ );
1431
+ } else {
1432
+ console.log(
1433
+ `[agenr] before_reset: sending to LLM model=${resolvedModel.modelId} chars=${transcript.length} msgs=${currentSlice.length}`
1434
+ );
1435
+ }
1335
1436
  const assistantMsg = await runSimpleStream({
1336
1437
  model: resolvedModel.model,
1337
1438
  context,
@@ -1348,6 +1449,47 @@ async function summarizeSessionForHandoff(currentRawMessages, sessionsDir, curre
1348
1449
  console.log("[agenr] before_reset: skipping LLM summary - reason: empty summary text");
1349
1450
  return null;
1350
1451
  }
1452
+ const normalizedLogDir = typeof logDir === "string" ? logDir.trim() : "";
1453
+ if (logEnabled && normalizedLogDir) {
1454
+ const timestamp = formatHandoffLogTimestamp(/* @__PURE__ */ new Date());
1455
+ const requestPath = path3.join(normalizedLogDir, `handoff-${timestamp}-request.txt`);
1456
+ const responsePath = path3.join(normalizedLogDir, `handoff-${timestamp}-response.txt`);
1457
+ const requestPayload = [
1458
+ "=== SYSTEM PROMPT ===",
1459
+ systemPrompt,
1460
+ "",
1461
+ "=== TRANSCRIPT ===",
1462
+ transcript,
1463
+ "",
1464
+ "=== METADATA ===",
1465
+ `model: ${resolvedModel.modelId}`,
1466
+ `currentMsgs: ${currentSlice.length}`,
1467
+ `priorMsgs: ${priorSlice.length}`,
1468
+ `includeBackground: ${includeBackground}`,
1469
+ `transcriptChars: ${transcript.length}`
1470
+ ].join("\n");
1471
+ const responsePayload = [
1472
+ "=== LLM RESPONSE ===",
1473
+ summaryText,
1474
+ "",
1475
+ "=== METADATA ===",
1476
+ `model: ${resolvedModel.modelId}`,
1477
+ `stopReason: ${assistantMsg.stopReason}`,
1478
+ `responseChars: ${summaryText.length}`
1479
+ ].join("\n");
1480
+ try {
1481
+ await fs.promises.mkdir(normalizedLogDir, { recursive: true });
1482
+ await Promise.all([
1483
+ fs.promises.writeFile(requestPath, requestPayload, "utf8"),
1484
+ fs.promises.writeFile(responsePath, responsePayload, "utf8")
1485
+ ]);
1486
+ console.log(`[agenr] handoff: logged LLM request/response to ${normalizedLogDir}`);
1487
+ } catch (err) {
1488
+ console.log(
1489
+ `[agenr] handoff: failed to write LLM request/response logs: ${err instanceof Error ? err.message : String(err)}`
1490
+ );
1491
+ }
1492
+ }
1351
1493
  console.log(`[agenr] before_reset: LLM summary received chars=${summaryText.length}`);
1352
1494
  return summaryText;
1353
1495
  } catch (err) {
@@ -1393,7 +1535,7 @@ async function retireFallbackHandoffEntries(params) {
1393
1535
  context: "browse",
1394
1536
  since: "1d",
1395
1537
  limit: 100
1396
- });
1538
+ }, params.dbPath);
1397
1539
  if (!browseResult) {
1398
1540
  return;
1399
1541
  }
@@ -1423,7 +1565,9 @@ async function retireFallbackHandoffEntries(params) {
1423
1565
  runRetireTool(params.agenrPath, {
1424
1566
  entry_id: entryId,
1425
1567
  reason: "superseded by LLM handoff"
1426
- }).then(() => void 0).catch((err) => {
1568
+ }, params.dbPath).then(() => {
1569
+ console.log(`[agenr] session-start: retired handoff ${entryId}`);
1570
+ }).catch((err) => {
1427
1571
  params.logger.debug?.(
1428
1572
  `[agenr] ${params.source}: fallback retire failed for ${entryId}: ${err instanceof Error ? err.message : String(err)}`
1429
1573
  );
@@ -1458,10 +1602,7 @@ function normalizeHandoffMessages(messages) {
1458
1602
  async function runHandoffForSession(opts) {
1459
1603
  const sessionId = opts.sessionId.trim() || opts.sessionKey;
1460
1604
  if (handoffSeenSessionIds.has(sessionId)) {
1461
- process.stderr.write(
1462
- `[AGENR-PROBE] ${opts.source} hook: dedup skip sessionId=${sessionId} source=${opts.source}
1463
- `
1464
- );
1605
+ console.log(`[agenr] ${opts.source}: dedup skip sessionId=${sessionId}`);
1465
1606
  return;
1466
1607
  }
1467
1608
  handoffSeenSessionIds.add(sessionId);
@@ -1473,8 +1614,6 @@ async function runHandoffForSession(opts) {
1473
1614
  }
1474
1615
  const normalizedMessages = normalizeHandoffMessages(opts.messages);
1475
1616
  if (normalizedMessages.length === 0) {
1476
- process.stderr.write(`[AGENR-PROBE] ${opts.source} hook: no messages after normalization source=${opts.source}
1477
- `);
1478
1617
  return;
1479
1618
  }
1480
1619
  if (!opts.sessionFile) {
@@ -1497,7 +1636,7 @@ async function runHandoffForSession(opts) {
1497
1636
  ]
1498
1637
  };
1499
1638
  try {
1500
- await runStoreTool(opts.agenrPath, fallbackEntry, opts.storeConfig, opts.defaultProject);
1639
+ await runStoreTool(opts.agenrPath, fallbackEntry, opts.storeConfig, opts.defaultProject, opts.dbPath);
1501
1640
  console.log(`[agenr] ${opts.source}: fallback handoff stored`);
1502
1641
  } catch (err) {
1503
1642
  console.log(
@@ -1507,6 +1646,7 @@ async function runHandoffForSession(opts) {
1507
1646
  }
1508
1647
  }
1509
1648
  if (opts.sessionFile) {
1649
+ const includeBackground = opts.includeBackground ?? false;
1510
1650
  const summary = await testingApi.summarizeSessionForHandoff(
1511
1651
  normalizedMessages,
1512
1652
  opts.sessionsDir,
@@ -1514,7 +1654,11 @@ async function runHandoffForSession(opts) {
1514
1654
  opts.logger ?? {
1515
1655
  warn: () => void 0,
1516
1656
  error: () => void 0
1517
- }
1657
+ },
1658
+ includeBackground,
1659
+ void 0,
1660
+ opts.logEnabled ?? false,
1661
+ opts.logDir
1518
1662
  );
1519
1663
  if (summary) {
1520
1664
  if (fallbackEntrySubject) {
@@ -1522,6 +1666,7 @@ async function runHandoffForSession(opts) {
1522
1666
  agenrPath: opts.agenrPath,
1523
1667
  budget: opts.budget,
1524
1668
  defaultProject: opts.defaultProject,
1669
+ dbPath: opts.dbPath,
1525
1670
  fallbackSubject: fallbackEntrySubject,
1526
1671
  logger: opts.logger ?? {
1527
1672
  warn: () => void 0,
@@ -1543,7 +1688,7 @@ async function runHandoffForSession(opts) {
1543
1688
  ]
1544
1689
  };
1545
1690
  try {
1546
- await runStoreTool(opts.agenrPath, llmEntry, opts.storeConfig, opts.defaultProject);
1691
+ await runStoreTool(opts.agenrPath, llmEntry, opts.storeConfig, opts.defaultProject, opts.dbPath);
1547
1692
  console.log(`[agenr] ${opts.source}: LLM handoff stored`);
1548
1693
  } catch (err) {
1549
1694
  console.log(
@@ -1564,9 +1709,13 @@ var testingApi = {
1564
1709
  getBaseSessionPath,
1565
1710
  getSurfaceForSessionFile,
1566
1711
  readMessagesFromJsonl,
1712
+ stripInjectedContext,
1567
1713
  findPriorResetFile,
1714
+ buildTranscript,
1568
1715
  buildMergedTranscript,
1569
1716
  capTranscriptLength,
1717
+ HANDOFF_SUMMARY_SYSTEM_PROMPT,
1718
+ HANDOFF_SUMMARY_SYSTEM_PROMPT_WITH_BACKGROUND,
1570
1719
  summarizeSessionForHandoff,
1571
1720
  runHandoffForSession
1572
1721
  };
@@ -1576,8 +1725,10 @@ var plugin = {
1576
1725
  description: "Injects agenr long-term memory into every agent session via before_prompt_build",
1577
1726
  register(api) {
1578
1727
  const config = api.pluginConfig;
1579
- process.stderr.write(`[AGENR-PROBE] register() called pluginId=${api.id}
1580
- `);
1728
+ const includeBackground = config?.handoff?.includeBackground ?? false;
1729
+ const handoffLogEnabled = config?.handoff?.logEnabled === true;
1730
+ const handoffLogDirRaw = config?.handoff?.logDir;
1731
+ const handoffLogDir = typeof handoffLogDirRaw === "string" && handoffLogDirRaw.trim() ? handoffLogDirRaw.trim() : void 0;
1581
1732
  api.on(
1582
1733
  "before_prompt_build",
1583
1734
  async (event, ctx) => {
@@ -1613,7 +1764,7 @@ var plugin = {
1613
1764
  context: "browse",
1614
1765
  since: "1d",
1615
1766
  limit: 20
1616
- })
1767
+ }, config?.dbPath)
1617
1768
  ]);
1618
1769
  if (previousSessionFile) {
1619
1770
  let messages = null;
@@ -1623,13 +1774,8 @@ var plugin = {
1623
1774
  messages = null;
1624
1775
  }
1625
1776
  if (Array.isArray(messages) && messages.length > 0) {
1626
- process.stderr.write(
1627
- `[AGENR-PROBE] session_start: triggering handoff for prev file=${previousSessionFile} msgs=${messages.length}
1628
- `
1629
- );
1630
- console.log(
1631
- `[agenr] session_start: awaiting runHandoffForSession file=${previousSessionFile}`
1632
- );
1777
+ const sessionKey2 = ctx.sessionKey ?? "";
1778
+ console.log("[agenr] session_start: triggered sessionKey=" + sessionKey2);
1633
1779
  try {
1634
1780
  await testingApi.runHandoffForSession({
1635
1781
  messages,
@@ -1645,25 +1791,20 @@ var plugin = {
1645
1791
  logger: api.logger
1646
1792
  },
1647
1793
  sessionsDir,
1794
+ includeBackground,
1795
+ logEnabled: handoffLogEnabled,
1796
+ logDir: handoffLogDir,
1648
1797
  logger: api.logger,
1649
- source: "session_start"
1798
+ source: "session_start",
1799
+ dbPath: config?.dbPath
1650
1800
  });
1651
1801
  console.log("[agenr] session_start: runHandoffForSession completed");
1652
1802
  } catch (err) {
1653
- api.logger.debug?.(
1803
+ console.log(
1654
1804
  `[agenr] session_start: handoff failed: ${err instanceof Error ? err.message : String(err)}`
1655
1805
  );
1656
1806
  }
1657
- } else if (Array.isArray(messages)) {
1658
- process.stderr.write(
1659
- `[AGENR-PROBE] session_start: skipping handoff - no messages in prev file=${previousSessionFile}
1660
- `
1661
- );
1662
1807
  }
1663
- } else {
1664
- process.stderr.write(
1665
- "[AGENR-PROBE] session_start: skipping handoff - no previous session file found\n"
1666
- );
1667
1808
  }
1668
1809
  const seed = buildSemanticSeed(previousTurns, event.prompt ?? "");
1669
1810
  let semanticResult = null;
@@ -1675,7 +1816,7 @@ var plugin = {
1675
1816
  browseIds.add(id);
1676
1817
  }
1677
1818
  }
1678
- const rawSemantic = await runRecall(agenrPath, budget, project, seed);
1819
+ const rawSemantic = await runRecall(agenrPath, budget, project, seed, void 0, config?.dbPath);
1679
1820
  if (rawSemantic) {
1680
1821
  rawSemantic.results = rawSemantic.results.filter((item) => {
1681
1822
  const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
@@ -1688,6 +1829,7 @@ var plugin = {
1688
1829
  }
1689
1830
  }
1690
1831
  if (browseResult) {
1832
+ console.log(`[agenr] session-start: browse returned ${browseResult.results.length} entries, checking for handoffs to retire`);
1691
1833
  const retirePromises = [];
1692
1834
  for (const item of browseResult.results) {
1693
1835
  const entry = item.entry;
@@ -1701,14 +1843,16 @@ var plugin = {
1701
1843
  runRetireTool(agenrPath, {
1702
1844
  entry_id: entryId,
1703
1845
  reason: "consumed at session start"
1704
- }).then(() => void 0).catch((err) => {
1705
- api.logger.debug?.(
1846
+ }, config?.dbPath).then(() => {
1847
+ console.log(`[agenr] session-start: retired handoff ${entryId}`);
1848
+ }).catch((err) => {
1849
+ console.log(
1706
1850
  `[agenr] session-start: retire handoff ${entryId} failed: ${err instanceof Error ? err.message : String(err)}`
1707
1851
  );
1708
1852
  })
1709
1853
  );
1710
1854
  } else {
1711
- api.logger.debug?.(
1855
+ console.log(
1712
1856
  "[agenr] session-start: handoff entry missing id, skipping retire"
1713
1857
  );
1714
1858
  }
@@ -1767,35 +1911,18 @@ ${formatted.trim()}`);
1767
1911
  }
1768
1912
  }
1769
1913
  );
1770
- process.stderr.write(`[AGENR-PROBE] before_prompt_build hook registered
1771
- `);
1772
- api.on("session_start", async (_event, ctx) => {
1773
- process.stderr.write(
1774
- `[AGENR-PROBE] session_start FIRED sessionKey=${ctx.sessionKey ?? "none"}
1775
- `
1776
- );
1777
- });
1778
1914
  api.on("before_reset", async (event, ctx) => {
1779
1915
  try {
1780
- process.stderr.write(
1781
- `[AGENR-PROBE] before_reset FIRED sessionKey=${ctx.sessionKey ?? "none"} msgs=${Array.isArray(event.messages) ? event.messages.length : "non-array"} source=before_reset
1782
- `
1783
- );
1784
1916
  api.logger.info?.(`[agenr] before_reset: fired sessionKey=${ctx.sessionKey ?? "none"} agentId=${ctx.agentId ?? "none"} msgs=${Array.isArray(event.messages) ? event.messages.length : "non-array"} sessionFile=${event.sessionFile ?? "none"} source=before_reset`);
1785
1917
  const sessionKey = ctx.sessionKey;
1786
1918
  if (!sessionKey) {
1787
1919
  return;
1788
1920
  }
1789
- process.stderr.write(`[AGENR-PROBE] before_reset: sessionKey ok source=before_reset
1790
- `);
1791
1921
  const messages = event.messages;
1792
1922
  if (!Array.isArray(messages) || messages.length === 0) {
1793
1923
  return;
1794
1924
  }
1795
- process.stderr.write(
1796
- `[AGENR-PROBE] before_reset: messages ok count=${messages.length} source=before_reset
1797
- `
1798
- );
1925
+ console.log("[agenr] before_reset: triggered sessionKey=" + sessionKey + " msgs=" + messages.length);
1799
1926
  const currentSessionFile = typeof event.sessionFile === "string" && event.sessionFile.trim() ? event.sessionFile.trim() : null;
1800
1927
  const agentId = ctx.agentId?.trim() || "main";
1801
1928
  const sessionsDir = config?.sessionsDir ?? path3.join(os.homedir(), `.openclaw/agents/${agentId}/sessions`);
@@ -1818,8 +1945,12 @@ ${formatted.trim()}`);
1818
1945
  defaultProject,
1819
1946
  storeConfig,
1820
1947
  sessionsDir,
1948
+ includeBackground,
1949
+ logEnabled: handoffLogEnabled,
1950
+ logDir: handoffLogDir,
1821
1951
  logger: api.logger,
1822
- source: "before_reset"
1952
+ source: "before_reset",
1953
+ dbPath: config?.dbPath
1823
1954
  });
1824
1955
  } catch (err) {
1825
1956
  api.logger.warn(
@@ -1827,49 +1958,28 @@ ${formatted.trim()}`);
1827
1958
  );
1828
1959
  }
1829
1960
  });
1830
- process.stderr.write(`[AGENR-PROBE] before_reset hook registered
1831
- `);
1832
1961
  api.on(
1833
1962
  "command",
1834
1963
  async (event, ctx) => {
1835
1964
  try {
1836
- process.stderr.write(
1837
- `[AGENR-PROBE] command hook FIRED action=${event.action ?? "none"} sessionKey=${event.sessionKey ?? "none"} source=${String(event.context?.commandSource ?? "unknown")}
1838
- `
1839
- );
1965
+ console.log("[agenr] command: triggered action=" + event.action + " sessionKey=" + event.sessionKey);
1840
1966
  if (event.action !== "new" && event.action !== "reset") {
1841
- process.stderr.write(
1842
- `[AGENR-PROBE] command hook: skipping action=${event.action ?? "none"} source=command
1843
- `
1844
- );
1845
1967
  return;
1846
1968
  }
1847
1969
  const sessionKey = event.sessionKey;
1848
1970
  if (!sessionKey) {
1849
- process.stderr.write("[AGENR-PROBE] command hook: no sessionKey, skipping source=command\n");
1850
1971
  return;
1851
1972
  }
1852
1973
  const sessionFile = event.context?.sessionEntry?.sessionFile ?? null;
1853
1974
  const sessionId = event.context?.sessionEntry?.sessionId ?? sessionKey;
1854
- process.stderr.write(
1855
- `[AGENR-PROBE] command hook: new/reset detected sessionKey=${sessionKey} sessionFile=${sessionFile ?? "none"} source=command
1856
- `
1857
- );
1858
1975
  api.logger.info?.(
1859
1976
  `[agenr] command hook: fired action=${event.action} sessionKey=${sessionKey} sessionFile=${sessionFile ?? "none"} source=command`
1860
1977
  );
1861
1978
  let messages = [];
1862
1979
  if (sessionFile) {
1863
1980
  messages = await testingApi.readAndParseSessionJsonl(sessionFile);
1864
- process.stderr.write(
1865
- `[AGENR-PROBE] command hook: parsed ${messages.length} messages from JSONL source=command
1866
- `
1867
- );
1868
- } else {
1869
- process.stderr.write("[AGENR-PROBE] command hook: no sessionFile available source=command\n");
1870
1981
  }
1871
1982
  if (messages.length === 0) {
1872
- process.stderr.write("[AGENR-PROBE] command hook: no messages, skipping handoff source=command\n");
1873
1983
  return;
1874
1984
  }
1875
1985
  const agentId = ctx.agentId?.trim() || "main";
@@ -1892,10 +2002,14 @@ ${formatted.trim()}`);
1892
2002
  defaultProject,
1893
2003
  storeConfig,
1894
2004
  sessionsDir,
2005
+ includeBackground,
2006
+ logEnabled: handoffLogEnabled,
2007
+ logDir: handoffLogDir,
1895
2008
  logger: api.logger,
1896
- source: "command"
2009
+ source: "command",
2010
+ dbPath: config?.dbPath
1897
2011
  });
1898
- process.stderr.write("[AGENR-PROBE] command hook: handoff complete source=command\n");
2012
+ console.log("[agenr] command: handoff complete");
1899
2013
  } catch (err) {
1900
2014
  api.logger.warn(
1901
2015
  `agenr plugin command hook failed: ${err instanceof Error ? err.message : String(err)}`
@@ -1903,7 +2017,6 @@ ${formatted.trim()}`);
1903
2017
  }
1904
2018
  }
1905
2019
  );
1906
- process.stderr.write("[AGENR-PROBE] command hook registered\n");
1907
2020
  if (api.registerTool) {
1908
2021
  if (config?.enabled === false) {
1909
2022
  return;
@@ -1944,7 +2057,8 @@ ${formatted.trim()}`);
1944
2057
  }
1945
2058
  const agenrPath = resolveAgenrPath(runtimeConfig);
1946
2059
  const defaultProject = runtimeConfig?.project?.trim() || void 0;
1947
- return runRecallTool(agenrPath, params, defaultProject);
2060
+ const dbPath = runtimeConfig?.dbPath;
2061
+ return runRecallTool(agenrPath, params, defaultProject, dbPath);
1948
2062
  }
1949
2063
  }
1950
2064
  );
@@ -2003,11 +2117,13 @@ ${formatted.trim()}`);
2003
2117
  ...runtimeConfig,
2004
2118
  logger: api.logger
2005
2119
  };
2120
+ const dbPath = runtimeConfig?.dbPath;
2006
2121
  return runStoreTool(
2007
2122
  agenrPath,
2008
2123
  params,
2009
2124
  toolConfig,
2010
- defaultProject
2125
+ defaultProject,
2126
+ dbPath
2011
2127
  );
2012
2128
  }
2013
2129
  }
@@ -2028,7 +2144,8 @@ ${formatted.trim()}`);
2028
2144
  return makeDisabledToolResult();
2029
2145
  }
2030
2146
  const agenrPath = resolveAgenrPath(runtimeConfig);
2031
- return runExtractTool(agenrPath, params);
2147
+ const dbPath = runtimeConfig?.dbPath;
2148
+ return runExtractTool(agenrPath, params, dbPath);
2032
2149
  }
2033
2150
  }
2034
2151
  );
@@ -2048,7 +2165,8 @@ ${formatted.trim()}`);
2048
2165
  return makeDisabledToolResult();
2049
2166
  }
2050
2167
  const agenrPath = resolveAgenrPath(runtimeConfig);
2051
- return runRetireTool(agenrPath, params);
2168
+ const dbPath = runtimeConfig?.dbPath;
2169
+ return runRetireTool(agenrPath, params, dbPath);
2052
2170
  }
2053
2171
  }
2054
2172
  );
@@ -52,6 +52,29 @@
52
52
  "dbPath": {
53
53
  "type": "string",
54
54
  "description": "Path to agenr DB. Defaults to AGENR_DB_PATH env or ~/.agenr/knowledge.db."
55
+ },
56
+ "sessionsDir": {
57
+ "type": "string",
58
+ "description": "Path to OpenClaw sessions directory. Defaults to ~/.openclaw/agents/{agentId}/sessions."
59
+ },
60
+ "handoff": {
61
+ "type": "object",
62
+ "description": "Handoff summarizer options.",
63
+ "additionalProperties": false,
64
+ "properties": {
65
+ "includeBackground": {
66
+ "type": "boolean",
67
+ "description": "Include prior session as background context in handoff summary (default: false)."
68
+ },
69
+ "logEnabled": {
70
+ "type": "boolean",
71
+ "description": "Enable writing handoff LLM request/response logs to logDir (default: false)."
72
+ },
73
+ "logDir": {
74
+ "type": "string",
75
+ "description": "Directory to write handoff LLM request/response logs for debugging. Requires logEnabled: true."
76
+ }
77
+ }
55
78
  }
56
79
  }
57
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.8.37",
3
+ "version": "0.8.38",
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
- "scripts": {
58
- "build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
59
- "dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
60
- "test": "vitest run",
61
- "test:watch": "vitest",
62
- "typecheck": "tsc --noEmit"
64
+ "pnpm": {
65
+ "overrides": {
66
+ "fast-xml-parser": "^5.3.6"
67
+ }
63
68
  }
64
- }
69
+ }