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 +29 -0
- package/dist/openclaw-plugin/index.d.ts +11 -1
- package/dist/openclaw-plugin/index.js +255 -137
- package/openclaw.plugin.json +23 -0
- package/package.json +13 -8
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
|
|
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
|
|
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
|
|
996
|
-
|
|
997
|
-
|
|
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
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1357
|
+
let currentSlice = currentMessages.slice(
|
|
1283
1358
|
-Math.min(currentMessages.length, HANDOFF_TRANSCRIPT_MAX_MESSAGES)
|
|
1284
1359
|
);
|
|
1285
|
-
|
|
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
|
-
|
|
1289
|
-
if (
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
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 (
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
|
1423
|
+
systemPrompt,
|
|
1329
1424
|
messages: [{ role: "user", content: transcript, timestamp: Date.now() }],
|
|
1330
1425
|
tools: []
|
|
1331
1426
|
};
|
|
1332
|
-
|
|
1333
|
-
|
|
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(() =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
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(() =>
|
|
1705
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2168
|
+
const dbPath = runtimeConfig?.dbPath;
|
|
2169
|
+
return runRetireTool(agenrPath, params, dbPath);
|
|
2052
2170
|
}
|
|
2053
2171
|
}
|
|
2054
2172
|
);
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"test:watch": "vitest",
|
|
62
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
+
"pnpm": {
|
|
65
|
+
"overrides": {
|
|
66
|
+
"fast-xml-parser": "^5.3.6"
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
|
-
}
|
|
69
|
+
}
|