|
|
@@ -3,6 +3,8 @@ import os from "os";
|
|
|
import path from "path";
|
|
|
|
|
|
const CHAT_LOG_FILE = "/tmp/openclaw-chat.log";
|
|
|
+const SESSIONS_INDEX_PATH =
|
|
|
+ "/home/lucky/.openclaw/agents/main/sessions/sessions.json";
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
// Config — openclaw does NOT inject cfg into hook events.
|
|
|
@@ -179,7 +181,82 @@ function getAudioPath(context: any): string | undefined {
|
|
|
return undefined;
|
|
|
}
|
|
|
|
|
|
-function readLastAssistantMessage(sessionKey?: string): string | undefined {
|
|
|
+function cleanAssistantText(text: string): string {
|
|
|
+ return text.replace(/^\s*\[\[reply_to_current\]\]\s*/i, "").trim();
|
|
|
+}
|
|
|
+
|
|
|
+function resolveSessionFileFromIndex(sessionKey?: string): string | undefined {
|
|
|
+ if (!sessionKey) return undefined;
|
|
|
+ try {
|
|
|
+ if (!fs.existsSync(SESSIONS_INDEX_PATH)) return undefined;
|
|
|
+ const data = fs.readFileSync(SESSIONS_INDEX_PATH, "utf8");
|
|
|
+ const parsed = JSON.parse(data);
|
|
|
+ const entry = parsed?.[sessionKey];
|
|
|
+ if (entry?.sessionFile) {
|
|
|
+ return entry.sessionFile as string;
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
+function readLastAssistantFromSessionFile(
|
|
|
+ sessionFile: string,
|
|
|
+ beforeTimestampMs?: number
|
|
|
+): string | undefined {
|
|
|
+ try {
|
|
|
+ if (!fs.existsSync(sessionFile)) return undefined;
|
|
|
+ const data = fs.readFileSync(sessionFile, "utf8");
|
|
|
+ const lines = data.split(/\r?\n/).filter(Boolean);
|
|
|
+ for (let i = lines.length - 1; i >= 0; i--) {
|
|
|
+ try {
|
|
|
+ const entry = JSON.parse(lines[i]);
|
|
|
+ if (entry?.type !== "message") continue;
|
|
|
+ if (entry?.message?.role !== "assistant") continue;
|
|
|
+ if (beforeTimestampMs && entry?.timestamp) {
|
|
|
+ const ts = Date.parse(entry.timestamp);
|
|
|
+ if (!Number.isNaN(ts) && ts >= beforeTimestampMs) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const content = entry.message?.content;
|
|
|
+ let text = "";
|
|
|
+ if (typeof content === "string") text = content;
|
|
|
+ else if (Array.isArray(content)) {
|
|
|
+ for (const block of content) {
|
|
|
+ if (typeof block === "string") text += block;
|
|
|
+ else if (typeof block?.text === "string") text += block.text;
|
|
|
+ else if (typeof block?.value === "string") text += block.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ text = cleanAssistantText(text.trim());
|
|
|
+ if (text && text !== "HEARTBEAT_OK") {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // skip malformed lines
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
+function readLastAssistantMessage(
|
|
|
+ sessionKey?: string,
|
|
|
+ beforeTimestampMs?: number
|
|
|
+): string | undefined {
|
|
|
+ const sessionFile = resolveSessionFileFromIndex(sessionKey);
|
|
|
+ if (sessionFile) {
|
|
|
+ const fromSession = readLastAssistantFromSessionFile(
|
|
|
+ sessionFile,
|
|
|
+ beforeTimestampMs
|
|
|
+ );
|
|
|
+ if (fromSession) return fromSession;
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
if (!fs.existsSync(CHAT_LOG_FILE)) return undefined;
|
|
|
const payload = fs.readFileSync(CHAT_LOG_FILE, "utf8");
|
|
|
@@ -201,8 +278,12 @@ function readLastAssistantMessage(sessionKey?: string): string | undefined {
|
|
|
continue;
|
|
|
}
|
|
|
const assistantText = candidate?.content;
|
|
|
- if (typeof assistantText === "string" && assistantText.trim()) {
|
|
|
- return assistantText.trim();
|
|
|
+ if (
|
|
|
+ typeof assistantText === "string" &&
|
|
|
+ assistantText.trim() &&
|
|
|
+ assistantText.trim() !== "HEARTBEAT_OK"
|
|
|
+ ) {
|
|
|
+ return cleanAssistantText(assistantText.trim());
|
|
|
}
|
|
|
} catch {
|
|
|
// skip malformed lines
|
|
|
@@ -219,12 +300,16 @@ function readLastAssistantMessage(sessionKey?: string): string | undefined {
|
|
|
continue;
|
|
|
}
|
|
|
const assistantText = candidate?.content;
|
|
|
- if (typeof assistantText === "string" && assistantText.trim()) {
|
|
|
+ if (
|
|
|
+ typeof assistantText === "string" &&
|
|
|
+ assistantText.trim() &&
|
|
|
+ assistantText.trim() !== "HEARTBEAT_OK"
|
|
|
+ ) {
|
|
|
console.warn(
|
|
|
"[mem0-auto-capture] assistant lookup fallback to global latest",
|
|
|
{ requestedSessionKey: sessionKey, logSessionKey: entry?.sessionKey }
|
|
|
);
|
|
|
- return assistantText.trim();
|
|
|
+ return cleanAssistantText(assistantText.trim());
|
|
|
}
|
|
|
} catch {
|
|
|
// skip malformed lines
|
|
|
@@ -553,7 +638,13 @@ export default async function handler(event: HookEvent) {
|
|
|
}
|
|
|
if (!shouldCapture) return;
|
|
|
|
|
|
- const assistantMessage = readLastAssistantMessage(event.sessionKey);
|
|
|
+ const eventTs = event.timestamp
|
|
|
+ ? new Date(event.timestamp as any).getTime()
|
|
|
+ : Date.now();
|
|
|
+ const assistantMessage = readLastAssistantMessage(
|
|
|
+ event.sessionKey,
|
|
|
+ eventTs
|
|
|
+ );
|
|
|
const userCaptureText = recent.join("\n");
|
|
|
|
|
|
const captureMessages: Array<{ role: "user" | "assistant"; content: string }> = [
|