openclaw-channel-dmwork 0.2.2 → 0.2.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 +1 -1
- package/src/channel.ts +4 -2
- package/src/inbound.ts +88 -40
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -16,7 +16,9 @@ import { registerBot, sendMessage, sendHeartbeat } from "./api-fetch.js";
|
|
|
16
16
|
import { WKSocket } from "./socket.js";
|
|
17
17
|
import { handleInboundMessage, type DmworkStatusSink } from "./inbound.js";
|
|
18
18
|
import { ChannelType, MessageType, type BotMessage, type MessagePayload } from "./types.js";
|
|
19
|
-
|
|
19
|
+
// HistoryEntry type - compatible with any version
|
|
20
|
+
type HistoryEntry = { sender: string; body: string; timestamp: number };
|
|
21
|
+
const DEFAULT_GROUP_HISTORY_LIMIT = 20;
|
|
20
22
|
|
|
21
23
|
const meta = {
|
|
22
24
|
id: "dmwork",
|
|
@@ -176,7 +178,7 @@ export const dmworkPlugin: ChannelPlugin<ResolvedDmworkAccount> = {
|
|
|
176
178
|
};
|
|
177
179
|
|
|
178
180
|
// 4. Group history map for mention gating context
|
|
179
|
-
const groupHistories = new Map<string,
|
|
181
|
+
const groupHistories = new Map<string, any[]>();
|
|
180
182
|
|
|
181
183
|
// 5. Token refresh state — detect stale cached token
|
|
182
184
|
let hasRefreshedToken = false;
|
package/src/inbound.ts
CHANGED
|
@@ -4,13 +4,33 @@ import type { ResolvedDmworkAccount } from "./accounts.js";
|
|
|
4
4
|
import type { BotMessage } from "./types.js";
|
|
5
5
|
import { ChannelType, MessageType } from "./types.js";
|
|
6
6
|
import { getDmworkRuntime } from "./runtime.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
|
|
8
|
+
// Defensive imports — these may not exist in older OpenClaw versions
|
|
9
|
+
let recordPendingHistoryEntryIfEnabled: any;
|
|
10
|
+
let buildPendingHistoryContextFromMap: any;
|
|
11
|
+
let clearHistoryEntriesIfEnabled: any;
|
|
12
|
+
let DEFAULT_GROUP_HISTORY_LIMIT = 20;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const sdk = await import("openclaw/plugin-sdk");
|
|
16
|
+
recordPendingHistoryEntryIfEnabled = sdk.recordPendingHistoryEntryIfEnabled;
|
|
17
|
+
buildPendingHistoryContextFromMap = sdk.buildPendingHistoryContextFromMap;
|
|
18
|
+
clearHistoryEntriesIfEnabled = sdk.clearHistoryEntriesIfEnabled;
|
|
19
|
+
if (sdk.DEFAULT_GROUP_HISTORY_LIMIT) {
|
|
20
|
+
DEFAULT_GROUP_HISTORY_LIMIT = sdk.DEFAULT_GROUP_HISTORY_LIMIT;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// Older OpenClaw versions may not export these
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// Re-export a minimal HistoryEntry type for when SDK doesn't have it
|
|
29
|
+
export interface HistoryEntryCompat {
|
|
30
|
+
sender: string;
|
|
31
|
+
body: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
14
34
|
|
|
15
35
|
export type DmworkStatusSink = (patch: {
|
|
16
36
|
lastInboundAt?: number;
|
|
@@ -29,7 +49,7 @@ export async function handleInboundMessage(params: {
|
|
|
29
49
|
account: ResolvedDmworkAccount;
|
|
30
50
|
message: BotMessage;
|
|
31
51
|
botUid: string;
|
|
32
|
-
groupHistories: Map<string,
|
|
52
|
+
groupHistories: Map<string, any[]>;
|
|
33
53
|
log?: ChannelLogSink;
|
|
34
54
|
statusSink?: DmworkStatusSink;
|
|
35
55
|
}) {
|
|
@@ -53,11 +73,7 @@ export async function handleInboundMessage(params: {
|
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
// --- Mention gating for group messages ---
|
|
56
|
-
|
|
57
|
-
// payload.mention.uids (structured mention from WuKongIM).
|
|
58
|
-
// Unmentioned messages are recorded as history context for when the bot
|
|
59
|
-
// IS mentioned later.
|
|
60
|
-
const requireMention = account.config.requireMention !== false; // default true
|
|
76
|
+
const requireMention = account.config.requireMention !== false;
|
|
61
77
|
let historyPrefix = "";
|
|
62
78
|
|
|
63
79
|
if (isGroup && requireMention) {
|
|
@@ -66,17 +82,34 @@ export async function handleInboundMessage(params: {
|
|
|
66
82
|
const isMentioned = mentionAll || mentionUids.includes(botUid);
|
|
67
83
|
|
|
68
84
|
if (!isMentioned) {
|
|
69
|
-
// Record as pending history for
|
|
70
|
-
recordPendingHistoryEntryIfEnabled
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
// Record as pending history — with fallback for older SDK
|
|
86
|
+
if (typeof recordPendingHistoryEntryIfEnabled === "function") {
|
|
87
|
+
recordPendingHistoryEntryIfEnabled({
|
|
88
|
+
historyMap: groupHistories,
|
|
89
|
+
historyKey: sessionId,
|
|
90
|
+
entry: {
|
|
91
|
+
sender: message.from_uid,
|
|
92
|
+
body: rawBody,
|
|
93
|
+
timestamp: message.timestamp ? message.timestamp * 1000 : Date.now(),
|
|
94
|
+
},
|
|
95
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
// Manual fallback: store history in the map directly
|
|
99
|
+
if (!groupHistories.has(sessionId)) {
|
|
100
|
+
groupHistories.set(sessionId, []);
|
|
101
|
+
}
|
|
102
|
+
const entries = groupHistories.get(sessionId)!;
|
|
103
|
+
entries.push({
|
|
74
104
|
sender: message.from_uid,
|
|
75
105
|
body: rawBody,
|
|
76
106
|
timestamp: message.timestamp ? message.timestamp * 1000 : Date.now(),
|
|
77
|
-
}
|
|
78
|
-
limit
|
|
79
|
-
|
|
107
|
+
});
|
|
108
|
+
// Trim to limit
|
|
109
|
+
while (entries.length > DEFAULT_GROUP_HISTORY_LIMIT) {
|
|
110
|
+
entries.shift();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
80
113
|
log?.info?.(
|
|
81
114
|
`dmwork: group message not mentioning bot, recorded as history context`,
|
|
82
115
|
);
|
|
@@ -84,22 +117,38 @@ export async function handleInboundMessage(params: {
|
|
|
84
117
|
}
|
|
85
118
|
|
|
86
119
|
// Bot IS mentioned — prepend history context
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
if (typeof buildPendingHistoryContextFromMap === "function") {
|
|
121
|
+
const enrichedBody = buildPendingHistoryContextFromMap({
|
|
122
|
+
historyMap: groupHistories,
|
|
123
|
+
historyKey: sessionId,
|
|
124
|
+
currentMessage: rawBody,
|
|
125
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
126
|
+
});
|
|
127
|
+
if (enrichedBody !== rawBody) {
|
|
128
|
+
historyPrefix = enrichedBody.slice(0, enrichedBody.length - rawBody.length);
|
|
129
|
+
log?.info?.(`dmwork: prepending history context (${historyPrefix.length} chars)`);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// Manual fallback: build history prefix
|
|
133
|
+
const entries = groupHistories.get(sessionId) ?? [];
|
|
134
|
+
if (entries.length > 0) {
|
|
135
|
+
historyPrefix = entries
|
|
136
|
+
.map((e: any) => `[${e.sender}]: ${e.body}`)
|
|
137
|
+
.join("\n") + "\n---\n";
|
|
138
|
+
log?.info?.(`dmwork: prepending history context (${historyPrefix.length} chars, fallback)`);
|
|
139
|
+
}
|
|
96
140
|
}
|
|
141
|
+
|
|
97
142
|
// Clear history after consuming
|
|
98
|
-
clearHistoryEntriesIfEnabled
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
143
|
+
if (typeof clearHistoryEntriesIfEnabled === "function") {
|
|
144
|
+
clearHistoryEntriesIfEnabled({
|
|
145
|
+
historyMap: groupHistories,
|
|
146
|
+
historyKey: sessionId,
|
|
147
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
groupHistories.delete(sessionId);
|
|
151
|
+
}
|
|
103
152
|
}
|
|
104
153
|
|
|
105
154
|
const core = getDmworkRuntime();
|
|
@@ -129,13 +178,15 @@ export async function handleInboundMessage(params: {
|
|
|
129
178
|
sessionKey: route.sessionKey,
|
|
130
179
|
});
|
|
131
180
|
|
|
181
|
+
const finalBody = historyPrefix ? historyPrefix + rawBody : rawBody;
|
|
182
|
+
|
|
132
183
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
133
184
|
channel: "DMWork",
|
|
134
185
|
from: fromLabel,
|
|
135
186
|
timestamp: message.timestamp ? message.timestamp * 1000 : undefined,
|
|
136
187
|
previousTimestamp,
|
|
137
188
|
envelope: envelopeOptions,
|
|
138
|
-
body:
|
|
189
|
+
body: finalBody,
|
|
139
190
|
});
|
|
140
191
|
|
|
141
192
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
@@ -172,7 +223,7 @@ export async function handleInboundMessage(params: {
|
|
|
172
223
|
const replyChannelId = isGroup ? message.channel_id! : message.from_uid;
|
|
173
224
|
const replyChannelType = isGroup ? ChannelType.Group : ChannelType.DM;
|
|
174
225
|
|
|
175
|
-
// 已读回执 + 正在输入 — fire-and-forget
|
|
226
|
+
// 已读回执 + 正在输入 — fire-and-forget
|
|
176
227
|
log?.info?.(`dmwork: sending readReceipt+typing to channel=${replyChannelId} type=${replyChannelType} apiUrl=${account.config.apiUrl}`);
|
|
177
228
|
const messageIds = message.message_id ? [message.message_id] : [];
|
|
178
229
|
sendReadReceipt({ apiUrl: account.config.apiUrl, botToken: account.config.botToken ?? "", channelId: replyChannelId, channelType: replyChannelType, messageIds })
|
|
@@ -202,9 +253,6 @@ export async function handleInboundMessage(params: {
|
|
|
202
253
|
const content = contentParts.join("\n").trim();
|
|
203
254
|
if (!content) return;
|
|
204
255
|
|
|
205
|
-
const replyChannelId = isGroup ? message.channel_id! : message.from_uid;
|
|
206
|
-
const replyChannelType = isGroup ? ChannelType.Group : ChannelType.DM;
|
|
207
|
-
|
|
208
256
|
await sendMessage({
|
|
209
257
|
apiUrl: account.config.apiUrl,
|
|
210
258
|
botToken: account.config.botToken ?? "",
|