@badgerclaw/connect 1.3.2 → 1.3.3
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/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/matrix";
|
|
|
3
3
|
import { getMatrixRuntime } from "../../runtime.js";
|
|
4
4
|
import type { CoreConfig } from "../../types.js";
|
|
5
5
|
import { loadMatrixSdk } from "../sdk-runtime.js";
|
|
6
|
+
import { initRoomHistory } from "./chat-history.js";
|
|
6
7
|
|
|
7
8
|
// Track clients that already have auto-join registered to prevent duplicate listeners
|
|
8
9
|
const autoJoinRegistered = new WeakSet<object>();
|
|
@@ -73,6 +74,9 @@ export function registerMatrixAutoJoin(params: {
|
|
|
73
74
|
// AutojoinRoomsMixin handles the join, so listen for room.join to run post-join logic
|
|
74
75
|
client.on("room.join", async (roomId: string, _joinEvent: unknown) => {
|
|
75
76
|
logVerbose(`badgerclaw: bot joined room ${roomId} (always mode), running post-join handshake`);
|
|
77
|
+
if (cfg.chatHistory?.enabled) {
|
|
78
|
+
initRoomHistory(roomId);
|
|
79
|
+
}
|
|
76
80
|
await postJoinEncryptionHandshake(roomId);
|
|
77
81
|
});
|
|
78
82
|
return;
|
|
@@ -111,6 +115,9 @@ export function registerMatrixAutoJoin(params: {
|
|
|
111
115
|
try {
|
|
112
116
|
await client.joinRoom(roomId);
|
|
113
117
|
logVerbose(`badgerclaw: joined room ${roomId}`);
|
|
118
|
+
if (cfg.chatHistory?.enabled) {
|
|
119
|
+
initRoomHistory(roomId);
|
|
120
|
+
}
|
|
114
121
|
await postJoinEncryptionHandshake(roomId);
|
|
115
122
|
} catch (err) {
|
|
116
123
|
runtime.error?.(`badgerclaw: failed to join room ${roomId}: ${String(err)}`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
const HISTORY_DIR = path.join(os.homedir(), ".openclaw", "extensions", "badgerclaw", "history");
|
|
6
|
+
const MAX_MESSAGES = 200;
|
|
7
|
+
|
|
8
|
+
export interface HistoryEntry {
|
|
9
|
+
ts: string; // ISO timestamp
|
|
10
|
+
sender: string; // display name or username
|
|
11
|
+
text: string; // message text
|
|
12
|
+
role: "user" | "bot";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getHistoryPath(roomId: string): string {
|
|
16
|
+
// Sanitize roomId for filename: replace ! and : with safe chars
|
|
17
|
+
const safe = roomId.replace(/[!:]/g, "_").replace(/\./g, "-");
|
|
18
|
+
return path.join(HISTORY_DIR, `${safe}.md`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ensureHistoryDir(): void {
|
|
22
|
+
if (!fs.existsSync(HISTORY_DIR)) {
|
|
23
|
+
fs.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readHistory(roomId: string): HistoryEntry[] {
|
|
28
|
+
const filePath = getHistoryPath(roomId);
|
|
29
|
+
if (!fs.existsSync(filePath)) return [];
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
32
|
+
const lines = content.split("\n").filter((l: string) => l.startsWith("["));
|
|
33
|
+
return lines.map((line: string) => {
|
|
34
|
+
// Format: [2026-03-27T08:05:00Z] [user] sender: text
|
|
35
|
+
const match = line.match(/^\[([^\]]+)\] \[(user|bot)\] ([^:]+): (.+)$/);
|
|
36
|
+
if (!match) return null;
|
|
37
|
+
return { ts: match[1], role: match[2] as "user" | "bot", sender: match[3], text: match[4] };
|
|
38
|
+
}).filter(Boolean) as HistoryEntry[];
|
|
39
|
+
} catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function appendHistory(roomId: string, entry: HistoryEntry, roomName?: string): void {
|
|
45
|
+
ensureHistoryDir();
|
|
46
|
+
const filePath = getHistoryPath(roomId);
|
|
47
|
+
|
|
48
|
+
// Read existing, append, trim to 200
|
|
49
|
+
let entries = readHistory(roomId);
|
|
50
|
+
entries.push(entry);
|
|
51
|
+
if (entries.length > MAX_MESSAGES) {
|
|
52
|
+
entries = entries.slice(entries.length - MAX_MESSAGES);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Write header + entries
|
|
56
|
+
const header = `# Chat History — ${roomId}\n# Group: ${roomName || roomId} | Updated: ${new Date().toISOString()}\n# Last ${MAX_MESSAGES} messages (rolling)\n\n`;
|
|
57
|
+
const body = entries.map(e => `[${e.ts}] [${e.role}] ${e.sender}: ${e.text}`).join("\n");
|
|
58
|
+
fs.writeFileSync(filePath, header + body + "\n", "utf8");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatHistoryForContext(roomId: string): string | null {
|
|
62
|
+
const entries = readHistory(roomId);
|
|
63
|
+
if (entries.length === 0) return null;
|
|
64
|
+
const lines = entries.map(e => `${e.sender}: ${e.text}`).join("\n");
|
|
65
|
+
return `## Recent conversation history (last ${entries.length} messages):\n${lines}\n\n---\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function initRoomHistory(roomId: string, roomName?: string): void {
|
|
69
|
+
ensureHistoryDir();
|
|
70
|
+
const filePath = getHistoryPath(roomId);
|
|
71
|
+
if (!fs.existsSync(filePath)) {
|
|
72
|
+
const header = `# Chat History — ${roomId}\n# Group: ${roomName || roomId} | Created: ${new Date().toISOString()}\n# Last ${MAX_MESSAGES} messages (rolling)\n\n`;
|
|
73
|
+
fs.writeFileSync(filePath, header, "utf8");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
resolveMatrixAllowListMatch,
|
|
31
31
|
resolveMatrixAllowListMatches,
|
|
32
32
|
} from "./allowlist.js";
|
|
33
|
+
import { appendHistory, formatHistoryForContext } from "./chat-history.js";
|
|
33
34
|
import {
|
|
34
35
|
resolveMatrixBodyForAgent,
|
|
35
36
|
resolveMatrixInboundSenderLabel,
|
|
@@ -630,13 +631,33 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|
|
630
631
|
});
|
|
631
632
|
|
|
632
633
|
const groupSystemPrompt = roomConfig?.systemPrompt?.trim() || undefined;
|
|
634
|
+
|
|
635
|
+
// Append inbound user message to chat history
|
|
636
|
+
if (cfg.chatHistory?.enabled) {
|
|
637
|
+
appendHistory(roomId, {
|
|
638
|
+
ts: new Date().toISOString(),
|
|
639
|
+
role: "user",
|
|
640
|
+
sender: senderName || senderId,
|
|
641
|
+
text: bodyText,
|
|
642
|
+
}, roomName);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Inject rolling history into BodyForAgent context
|
|
646
|
+
let bodyForAgent = resolveMatrixBodyForAgent({
|
|
647
|
+
isDirectMessage,
|
|
648
|
+
bodyText,
|
|
649
|
+
senderLabel,
|
|
650
|
+
});
|
|
651
|
+
if (cfg.chatHistory?.enabled) {
|
|
652
|
+
const history = formatHistoryForContext(roomId);
|
|
653
|
+
if (history) {
|
|
654
|
+
bodyForAgent = history + bodyForAgent;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
633
658
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
634
659
|
Body: body,
|
|
635
|
-
BodyForAgent:
|
|
636
|
-
isDirectMessage,
|
|
637
|
-
bodyText,
|
|
638
|
-
senderLabel,
|
|
639
|
-
}),
|
|
660
|
+
BodyForAgent: bodyForAgent,
|
|
640
661
|
RawBody: bodyText,
|
|
641
662
|
CommandBody: bodyText,
|
|
642
663
|
From: isDirectMessage ? `badgerclaw:${senderId}` : `badgerclaw:channel:${roomId}`,
|
|
@@ -775,6 +796,15 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|
|
775
796
|
accountId: route.accountId,
|
|
776
797
|
tableMode,
|
|
777
798
|
});
|
|
799
|
+
if (cfg.chatHistory?.enabled && payload?.text) {
|
|
800
|
+
const botSender = resolveMatrixSenderUsername(selfUserId) || selfUserId;
|
|
801
|
+
appendHistory(roomId, {
|
|
802
|
+
ts: new Date().toISOString(),
|
|
803
|
+
role: "bot",
|
|
804
|
+
sender: botSender,
|
|
805
|
+
text: payload.text,
|
|
806
|
+
}, roomName);
|
|
807
|
+
}
|
|
778
808
|
didSendReply = true;
|
|
779
809
|
},
|
|
780
810
|
onError: (err, info) => {
|
package/src/types.ts
CHANGED