@botcord/botcord 0.2.3 → 0.3.0-beta.20260401151650
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/index.ts +57 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.ts +98 -0
- package/src/constants.ts +1 -1
- package/src/inbound.ts +226 -19
- package/src/memory-hook.ts +71 -0
- package/src/memory-protocol.ts +117 -0
- package/src/memory.ts +142 -0
- package/src/owner-chat-stream.ts +20 -0
- package/src/poller.ts +2 -11
- package/src/room-context.ts +269 -0
- package/src/tools/room-context.ts +139 -0
- package/src/ws-client.ts +24 -16
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* botcord_room_context — Inspect room context, recent messages, and search
|
|
3
|
+
* message history within one room or across all joined rooms.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
getSingleAccountModeError,
|
|
7
|
+
resolveAccountConfig,
|
|
8
|
+
isAccountConfigured,
|
|
9
|
+
} from "../config.js";
|
|
10
|
+
import { BotCordClient } from "../client.js";
|
|
11
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
12
|
+
import { getConfig as getAppConfig } from "../runtime.js";
|
|
13
|
+
|
|
14
|
+
export function createRoomContextTool() {
|
|
15
|
+
return {
|
|
16
|
+
name: "botcord_room_context",
|
|
17
|
+
label: "Room Context",
|
|
18
|
+
description:
|
|
19
|
+
"Inspect BotCord room context, recent messages, and search message history within one room or across joined rooms. " +
|
|
20
|
+
"Use room_summary or rooms_overview first to understand what is happening, then room_messages or room_search/global_search to dig deeper.",
|
|
21
|
+
parameters: {
|
|
22
|
+
type: "object" as const,
|
|
23
|
+
properties: {
|
|
24
|
+
action: {
|
|
25
|
+
type: "string" as const,
|
|
26
|
+
enum: [
|
|
27
|
+
"room_summary",
|
|
28
|
+
"room_messages",
|
|
29
|
+
"room_search",
|
|
30
|
+
"rooms_overview",
|
|
31
|
+
"global_search",
|
|
32
|
+
],
|
|
33
|
+
description:
|
|
34
|
+
"Action to perform: " +
|
|
35
|
+
"room_summary = structured summary of a room (stats, members, topics, recent messages); " +
|
|
36
|
+
"room_messages = paginated message history for a room; " +
|
|
37
|
+
"room_search = full-text search within a single room; " +
|
|
38
|
+
"rooms_overview = summary list of all joined rooms; " +
|
|
39
|
+
"global_search = cross-room full-text search across all joined rooms.",
|
|
40
|
+
},
|
|
41
|
+
room_id: {
|
|
42
|
+
type: "string" as const,
|
|
43
|
+
description:
|
|
44
|
+
"Room ID (rm_...) — required for room_summary, room_messages, room_search; optional filter for global_search",
|
|
45
|
+
},
|
|
46
|
+
query: {
|
|
47
|
+
type: "string" as const,
|
|
48
|
+
description: "Search query text — required for room_search and global_search",
|
|
49
|
+
},
|
|
50
|
+
topic_id: {
|
|
51
|
+
type: "string" as const,
|
|
52
|
+
description: "Filter by topic ID (tp_...)",
|
|
53
|
+
},
|
|
54
|
+
sender_id: {
|
|
55
|
+
type: "string" as const,
|
|
56
|
+
description: "Filter by sender agent ID (ag_...)",
|
|
57
|
+
},
|
|
58
|
+
before: {
|
|
59
|
+
type: "string" as const,
|
|
60
|
+
description: "Cursor: return results before this hub_msg_id (for pagination)",
|
|
61
|
+
},
|
|
62
|
+
after: {
|
|
63
|
+
type: "string" as const,
|
|
64
|
+
description: "Cursor: return results after this hub_msg_id (for room_messages pagination)",
|
|
65
|
+
},
|
|
66
|
+
limit: {
|
|
67
|
+
type: "number" as const,
|
|
68
|
+
description: "Max results to return (default varies by action)",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["action"],
|
|
72
|
+
},
|
|
73
|
+
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
74
|
+
const cfg = getAppConfig();
|
|
75
|
+
if (!cfg) return { error: "No configuration available" };
|
|
76
|
+
const singleAccountError = getSingleAccountModeError(cfg);
|
|
77
|
+
if (singleAccountError) return { error: singleAccountError };
|
|
78
|
+
|
|
79
|
+
const acct = resolveAccountConfig(cfg);
|
|
80
|
+
if (!isAccountConfigured(acct)) {
|
|
81
|
+
return { error: "BotCord is not configured." };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const client = new BotCordClient(acct);
|
|
85
|
+
attachTokenPersistence(client, acct);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
switch (args.action) {
|
|
89
|
+
case "room_summary": {
|
|
90
|
+
if (!args.room_id) return { error: "room_id is required for room_summary" };
|
|
91
|
+
return await client.roomSummary(args.room_id, args.limit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case "room_messages": {
|
|
95
|
+
if (!args.room_id) return { error: "room_id is required for room_messages" };
|
|
96
|
+
return await client.roomMessages(args.room_id, {
|
|
97
|
+
limit: args.limit,
|
|
98
|
+
before: args.before,
|
|
99
|
+
after: args.after,
|
|
100
|
+
topicId: args.topic_id,
|
|
101
|
+
senderId: args.sender_id,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "room_search": {
|
|
106
|
+
if (!args.room_id) return { error: "room_id is required for room_search" };
|
|
107
|
+
if (!args.query) return { error: "query is required for room_search" };
|
|
108
|
+
return await client.roomSearch(args.room_id, args.query, {
|
|
109
|
+
limit: args.limit,
|
|
110
|
+
before: args.before,
|
|
111
|
+
topicId: args.topic_id,
|
|
112
|
+
senderId: args.sender_id,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case "rooms_overview": {
|
|
117
|
+
return await client.roomsOverview(args.limit);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case "global_search": {
|
|
121
|
+
if (!args.query) return { error: "query is required for global_search" };
|
|
122
|
+
return await client.globalSearch(args.query, {
|
|
123
|
+
limit: args.limit,
|
|
124
|
+
roomId: args.room_id,
|
|
125
|
+
topicId: args.topic_id,
|
|
126
|
+
senderId: args.sender_id,
|
|
127
|
+
before: args.before,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
default:
|
|
132
|
+
return { error: `Unknown action: ${args.action}` };
|
|
133
|
+
}
|
|
134
|
+
} catch (err: any) {
|
|
135
|
+
return { error: `Room context action failed: ${err.message}` };
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
package/src/ws-client.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import WebSocket from "ws";
|
|
13
13
|
import { BotCordClient } from "./client.js";
|
|
14
|
-
import {
|
|
14
|
+
import { handleInboxMessageBatch } from "./inbound.js";
|
|
15
15
|
import { displayPrefix } from "./config.js";
|
|
16
16
|
import { buildHubWebSocketUrl } from "./hub-url.js";
|
|
17
17
|
|
|
@@ -51,31 +51,39 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
51
51
|
let consecutiveAuthFailures = 0;
|
|
52
52
|
const MAX_AUTH_FAILURES = 5;
|
|
53
53
|
let processing = false;
|
|
54
|
+
let pendingUpdate = false;
|
|
54
55
|
let keepaliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
55
56
|
const KEEPALIVE_INTERVAL = 20_000; // 20s — well under Caddy/proxy 30s timeout
|
|
56
57
|
|
|
57
58
|
async function fetchAndDispatch() {
|
|
58
|
-
if (processing)
|
|
59
|
+
if (processing) {
|
|
60
|
+
// Signal that another fetch is needed after the current one finishes
|
|
61
|
+
pendingUpdate = true;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
59
64
|
processing = true;
|
|
60
65
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
do {
|
|
67
|
+
pendingUpdate = false;
|
|
68
|
+
const resp = await client.pollInbox({ limit: 20, ack: false });
|
|
69
|
+
const messages = resp.messages || [];
|
|
65
70
|
try {
|
|
66
|
-
await
|
|
67
|
-
ackedIds.
|
|
71
|
+
const ackedIds = await handleInboxMessageBatch(messages, accountId, cfg);
|
|
72
|
+
if (ackedIds.length > 0) {
|
|
73
|
+
try {
|
|
74
|
+
await client.ackMessages(ackedIds);
|
|
75
|
+
} catch (err: any) {
|
|
76
|
+
log?.error(`[${dp}] ws ack error: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
68
79
|
} catch (err: any) {
|
|
69
|
-
log?.error(`[${dp}] ws dispatch error
|
|
80
|
+
log?.error(`[${dp}] ws batch dispatch error: ${err.message}`);
|
|
70
81
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await client.ackMessages(ackedIds);
|
|
75
|
-
} catch (err: any) {
|
|
76
|
-
log?.error(`[${dp}] ws ack error: ${err.message}`);
|
|
82
|
+
// If we got a full batch, there may be more — keep draining
|
|
83
|
+
if (messages.length >= 20) {
|
|
84
|
+
pendingUpdate = true;
|
|
77
85
|
}
|
|
78
|
-
}
|
|
86
|
+
} while (pendingUpdate);
|
|
79
87
|
} catch (err: any) {
|
|
80
88
|
log?.error(`[${dp}] ws poll error: ${err.message}`);
|
|
81
89
|
} finally {
|