agenr 0.8.36 → 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,42 @@
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
+
32
+ ## [0.8.37] - 2026-02-24
33
+
34
+ ### Fixed
35
+ - openclaw-plugin: await runHandoffForSession in session_start handler instead of void
36
+ fire-and-forget; webchat /new goes through sessions.reset RPC which does not trigger
37
+ before_reset, so session_start is the only hook that fires on that path - making it
38
+ void meant the LLM summary was always dropped (closes #232)
39
+
3
40
  ## [0.8.36] - 2026-02-24
4
41
 
5
42
  ### 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,41 +1774,37 @@ 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
- void testingApi.runHandoffForSession({
1631
- messages,
1632
- sessionFile: previousSessionFile,
1633
- sessionId: deriveSessionIdFromSessionFile(previousSessionFile) || (ctx.sessionId ?? ctx.sessionKey ?? ""),
1634
- sessionKey: ctx.sessionKey ?? "",
1635
- agentId,
1636
- agenrPath,
1637
- budget,
1638
- defaultProject: project,
1639
- storeConfig: {
1640
- ...config,
1641
- logger: api.logger
1642
- },
1643
- sessionsDir,
1644
- logger: api.logger,
1645
- source: "session_start"
1646
- }).catch((err) => {
1647
- api.logger.debug?.(
1648
- `[agenr] session_start: handoff fire-and-forget failed: ${err instanceof Error ? err.message : String(err)}`
1777
+ const sessionKey2 = ctx.sessionKey ?? "";
1778
+ console.log("[agenr] session_start: triggered sessionKey=" + sessionKey2);
1779
+ try {
1780
+ await testingApi.runHandoffForSession({
1781
+ messages,
1782
+ sessionFile: previousSessionFile,
1783
+ sessionId: deriveSessionIdFromSessionFile(previousSessionFile) || (ctx.sessionId ?? ctx.sessionKey ?? ""),
1784
+ sessionKey: ctx.sessionKey ?? "",
1785
+ agentId,
1786
+ agenrPath,
1787
+ budget,
1788
+ defaultProject: project,
1789
+ storeConfig: {
1790
+ ...config,
1791
+ logger: api.logger
1792
+ },
1793
+ sessionsDir,
1794
+ includeBackground,
1795
+ logEnabled: handoffLogEnabled,
1796
+ logDir: handoffLogDir,
1797
+ logger: api.logger,
1798
+ source: "session_start",
1799
+ dbPath: config?.dbPath
1800
+ });
1801
+ console.log("[agenr] session_start: runHandoffForSession completed");
1802
+ } catch (err) {
1803
+ console.log(
1804
+ `[agenr] session_start: handoff failed: ${err instanceof Error ? err.message : String(err)}`
1649
1805
  );
1650
- });
1651
- } else if (Array.isArray(messages)) {
1652
- process.stderr.write(
1653
- `[AGENR-PROBE] session_start: skipping handoff - no messages in prev file=${previousSessionFile}
1654
- `
1655
- );
1806
+ }
1656
1807
  }
1657
- } else {
1658
- process.stderr.write(
1659
- "[AGENR-PROBE] session_start: skipping handoff - no previous session file found\n"
1660
- );
1661
1808
  }
1662
1809
  const seed = buildSemanticSeed(previousTurns, event.prompt ?? "");
1663
1810
  let semanticResult = null;
@@ -1669,7 +1816,7 @@ var plugin = {
1669
1816
  browseIds.add(id);
1670
1817
  }
1671
1818
  }
1672
- const rawSemantic = await runRecall(agenrPath, budget, project, seed);
1819
+ const rawSemantic = await runRecall(agenrPath, budget, project, seed, void 0, config?.dbPath);
1673
1820
  if (rawSemantic) {
1674
1821
  rawSemantic.results = rawSemantic.results.filter((item) => {
1675
1822
  const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
@@ -1682,6 +1829,7 @@ var plugin = {
1682
1829
  }
1683
1830
  }
1684
1831
  if (browseResult) {
1832
+ console.log(`[agenr] session-start: browse returned ${browseResult.results.length} entries, checking for handoffs to retire`);
1685
1833
  const retirePromises = [];
1686
1834
  for (const item of browseResult.results) {
1687
1835
  const entry = item.entry;
@@ -1695,14 +1843,16 @@ var plugin = {
1695
1843
  runRetireTool(agenrPath, {
1696
1844
  entry_id: entryId,
1697
1845
  reason: "consumed at session start"
1698
- }).then(() => void 0).catch((err) => {
1699
- api.logger.debug?.(
1846
+ }, config?.dbPath).then(() => {
1847
+ console.log(`[agenr] session-start: retired handoff ${entryId}`);
1848
+ }).catch((err) => {
1849
+ console.log(
1700
1850
  `[agenr] session-start: retire handoff ${entryId} failed: ${err instanceof Error ? err.message : String(err)}`
1701
1851
  );
1702
1852
  })
1703
1853
  );
1704
1854
  } else {
1705
- api.logger.debug?.(
1855
+ console.log(
1706
1856
  "[agenr] session-start: handoff entry missing id, skipping retire"
1707
1857
  );
1708
1858
  }
@@ -1761,35 +1911,18 @@ ${formatted.trim()}`);
1761
1911
  }
1762
1912
  }
1763
1913
  );
1764
- process.stderr.write(`[AGENR-PROBE] before_prompt_build hook registered
1765
- `);
1766
- api.on("session_start", async (_event, ctx) => {
1767
- process.stderr.write(
1768
- `[AGENR-PROBE] session_start FIRED sessionKey=${ctx.sessionKey ?? "none"}
1769
- `
1770
- );
1771
- });
1772
1914
  api.on("before_reset", async (event, ctx) => {
1773
1915
  try {
1774
- process.stderr.write(
1775
- `[AGENR-PROBE] before_reset FIRED sessionKey=${ctx.sessionKey ?? "none"} msgs=${Array.isArray(event.messages) ? event.messages.length : "non-array"} source=before_reset
1776
- `
1777
- );
1778
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`);
1779
1917
  const sessionKey = ctx.sessionKey;
1780
1918
  if (!sessionKey) {
1781
1919
  return;
1782
1920
  }
1783
- process.stderr.write(`[AGENR-PROBE] before_reset: sessionKey ok source=before_reset
1784
- `);
1785
1921
  const messages = event.messages;
1786
1922
  if (!Array.isArray(messages) || messages.length === 0) {
1787
1923
  return;
1788
1924
  }
1789
- process.stderr.write(
1790
- `[AGENR-PROBE] before_reset: messages ok count=${messages.length} source=before_reset
1791
- `
1792
- );
1925
+ console.log("[agenr] before_reset: triggered sessionKey=" + sessionKey + " msgs=" + messages.length);
1793
1926
  const currentSessionFile = typeof event.sessionFile === "string" && event.sessionFile.trim() ? event.sessionFile.trim() : null;
1794
1927
  const agentId = ctx.agentId?.trim() || "main";
1795
1928
  const sessionsDir = config?.sessionsDir ?? path3.join(os.homedir(), `.openclaw/agents/${agentId}/sessions`);
@@ -1812,8 +1945,12 @@ ${formatted.trim()}`);
1812
1945
  defaultProject,
1813
1946
  storeConfig,
1814
1947
  sessionsDir,
1948
+ includeBackground,
1949
+ logEnabled: handoffLogEnabled,
1950
+ logDir: handoffLogDir,
1815
1951
  logger: api.logger,
1816
- source: "before_reset"
1952
+ source: "before_reset",
1953
+ dbPath: config?.dbPath
1817
1954
  });
1818
1955
  } catch (err) {
1819
1956
  api.logger.warn(
@@ -1821,49 +1958,28 @@ ${formatted.trim()}`);
1821
1958
  );
1822
1959
  }
1823
1960
  });
1824
- process.stderr.write(`[AGENR-PROBE] before_reset hook registered
1825
- `);
1826
1961
  api.on(
1827
1962
  "command",
1828
1963
  async (event, ctx) => {
1829
1964
  try {
1830
- process.stderr.write(
1831
- `[AGENR-PROBE] command hook FIRED action=${event.action ?? "none"} sessionKey=${event.sessionKey ?? "none"} source=${String(event.context?.commandSource ?? "unknown")}
1832
- `
1833
- );
1965
+ console.log("[agenr] command: triggered action=" + event.action + " sessionKey=" + event.sessionKey);
1834
1966
  if (event.action !== "new" && event.action !== "reset") {
1835
- process.stderr.write(
1836
- `[AGENR-PROBE] command hook: skipping action=${event.action ?? "none"} source=command
1837
- `
1838
- );
1839
1967
  return;
1840
1968
  }
1841
1969
  const sessionKey = event.sessionKey;
1842
1970
  if (!sessionKey) {
1843
- process.stderr.write("[AGENR-PROBE] command hook: no sessionKey, skipping source=command\n");
1844
1971
  return;
1845
1972
  }
1846
1973
  const sessionFile = event.context?.sessionEntry?.sessionFile ?? null;
1847
1974
  const sessionId = event.context?.sessionEntry?.sessionId ?? sessionKey;
1848
- process.stderr.write(
1849
- `[AGENR-PROBE] command hook: new/reset detected sessionKey=${sessionKey} sessionFile=${sessionFile ?? "none"} source=command
1850
- `
1851
- );
1852
1975
  api.logger.info?.(
1853
1976
  `[agenr] command hook: fired action=${event.action} sessionKey=${sessionKey} sessionFile=${sessionFile ?? "none"} source=command`
1854
1977
  );
1855
1978
  let messages = [];
1856
1979
  if (sessionFile) {
1857
1980
  messages = await testingApi.readAndParseSessionJsonl(sessionFile);
1858
- process.stderr.write(
1859
- `[AGENR-PROBE] command hook: parsed ${messages.length} messages from JSONL source=command
1860
- `
1861
- );
1862
- } else {
1863
- process.stderr.write("[AGENR-PROBE] command hook: no sessionFile available source=command\n");
1864
1981
  }
1865
1982
  if (messages.length === 0) {
1866
- process.stderr.write("[AGENR-PROBE] command hook: no messages, skipping handoff source=command\n");
1867
1983
  return;
1868
1984
  }
1869
1985
  const agentId = ctx.agentId?.trim() || "main";
@@ -1886,10 +2002,14 @@ ${formatted.trim()}`);
1886
2002
  defaultProject,
1887
2003
  storeConfig,
1888
2004
  sessionsDir,
2005
+ includeBackground,
2006
+ logEnabled: handoffLogEnabled,
2007
+ logDir: handoffLogDir,
1889
2008
  logger: api.logger,
1890
- source: "command"
2009
+ source: "command",
2010
+ dbPath: config?.dbPath
1891
2011
  });
1892
- process.stderr.write("[AGENR-PROBE] command hook: handoff complete source=command\n");
2012
+ console.log("[agenr] command: handoff complete");
1893
2013
  } catch (err) {
1894
2014
  api.logger.warn(
1895
2015
  `agenr plugin command hook failed: ${err instanceof Error ? err.message : String(err)}`
@@ -1897,7 +2017,6 @@ ${formatted.trim()}`);
1897
2017
  }
1898
2018
  }
1899
2019
  );
1900
- process.stderr.write("[AGENR-PROBE] command hook registered\n");
1901
2020
  if (api.registerTool) {
1902
2021
  if (config?.enabled === false) {
1903
2022
  return;
@@ -1938,7 +2057,8 @@ ${formatted.trim()}`);
1938
2057
  }
1939
2058
  const agenrPath = resolveAgenrPath(runtimeConfig);
1940
2059
  const defaultProject = runtimeConfig?.project?.trim() || void 0;
1941
- return runRecallTool(agenrPath, params, defaultProject);
2060
+ const dbPath = runtimeConfig?.dbPath;
2061
+ return runRecallTool(agenrPath, params, defaultProject, dbPath);
1942
2062
  }
1943
2063
  }
1944
2064
  );
@@ -1997,11 +2117,13 @@ ${formatted.trim()}`);
1997
2117
  ...runtimeConfig,
1998
2118
  logger: api.logger
1999
2119
  };
2120
+ const dbPath = runtimeConfig?.dbPath;
2000
2121
  return runStoreTool(
2001
2122
  agenrPath,
2002
2123
  params,
2003
2124
  toolConfig,
2004
- defaultProject
2125
+ defaultProject,
2126
+ dbPath
2005
2127
  );
2006
2128
  }
2007
2129
  }
@@ -2022,7 +2144,8 @@ ${formatted.trim()}`);
2022
2144
  return makeDisabledToolResult();
2023
2145
  }
2024
2146
  const agenrPath = resolveAgenrPath(runtimeConfig);
2025
- return runExtractTool(agenrPath, params);
2147
+ const dbPath = runtimeConfig?.dbPath;
2148
+ return runExtractTool(agenrPath, params, dbPath);
2026
2149
  }
2027
2150
  }
2028
2151
  );
@@ -2042,7 +2165,8 @@ ${formatted.trim()}`);
2042
2165
  return makeDisabledToolResult();
2043
2166
  }
2044
2167
  const agenrPath = resolveAgenrPath(runtimeConfig);
2045
- return runRetireTool(agenrPath, params);
2168
+ const dbPath = runtimeConfig?.dbPath;
2169
+ return runRetireTool(agenrPath, params, dbPath);
2046
2170
  }
2047
2171
  }
2048
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.36",
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
+ }